|Home|

4th.Energy NFTC Program Loading Research

What's all this then?

This is a write up of how I worked out how to load a Nouns image into a web page using nothing but a browser and a geth node. The reason I did it was to work out an approach to loading 4th.Energy NFTC programs directly from "on-chain" smart contract calls from the end user browser. If I can do this, then decentralised users with nothing but a browser can load dApps and use them without gatekeeper intermediary dApp websites. Spoiler alert: It can be done!

What problem are we trying to solve?

The problem I want to solve is running dApps from code that is "on-chain" (accessed from smart contracts) with no intermediary web server.

Why Try?

Imagine the zombie apocalypse has occurred (again). All the web servers in the world are at risk of being pwned by zombies and any call you make to a web server is at risk of a zombie-in-the-middle (ZITM) attack. This is also known as SNAFU for many people in the world where firewalls at borders (or even corporate firewalls for that matter) let ZITMs masquerade as the trusted services you use based on root SSL certificates they control. You just don't know the ZITM profile of what you're doing... So what do you do? Use the blockchain?

Well, the promise of the mystical blockchain, and in our case, Ethereum, is that they have "decentralised and uncensorable" as attributes on their labels. These attributes promise that no one party can control them, and no one can stop them. These are great on presentation slides and theoretically true if you are:

However in the real world of dApps, "decentralised and uncensorable" are a fantasy. Most dApps are owned by the same brands that develop the smart contracts behind them, which means that as well as being in control of the keys that control the smart contract, they are also in control of the web service that offers the dApp. For as long as this is the case, dApps are centralised and censorable, and they fail to deliver what's on the web3 label.

How will we fix this problem?

The goal of 4th.Energy is to enable browser viewed NFTs to host a virtual machine and runtime software that is loaded from the blockchain. We call this the NFTC - NFT Computer. The NFTC can fix the problem of dApps being centralised by moving the dApp on-chain and removing the need for intermeriary web servers to deliver the dApp to users. Proof of Concept 1 and 2 showed the NFTC is a possible approach to fix these problems. If we can load the NFTC dApp from a browser talking to the blockchain, we also fix the problem of needing an intermediary to provide access to the blockchain and the dApp. This proof of concept #3 shows that this is possible and fixes the loading problem.

The Proof of Concept

Setup

The approach I take here is technical but simple to grok. First, I setup a standard GETH ethereum node and made it available on the public internet. I have done this on the 4th.Energy server and made it available at https://4th.energy/web3. The command I use to run geth is:
    geth --http --http.corsdomain "https://4th.energy/" --datadir [etc...]
        
The reverse proxy setup I use to make it available is:
    location /web3 {
        proxy_redirect off;
        proxy_pass http://127.0.0.1:8545/;
    }
        
This setup is completely stock standard and exactly the same as if you were running the node on your own PC/server. Ie. if you setup your own geth node and run it on your local machine, you can now perform the following steps I use against "https://4th.energy/web3" by substituting "http://localhost:8545".

The Test Case

We are going to use Nouns as our test case. The reason is that Nouns already emits a base64 encoded SVG image from the "generateSVGImage" method of the "NounsDescriptor" smart contract. Proof of Concept #2 showed we can embed an NFTC in a base64 encoded SVG image, and so if we can load the Nouns image directly from the smart contract into the browser, it follows that when the 4th.Energy smart contract exists, we will be able to load the 4th.Energy NFTC base64 encoded SVG image in the same way.

And so the test case is - load a noun SVG into a web page using the browser talking to the geth node at https://4th.energy/web3.

The Method

I had to construct a Web3 JSON-RPC request to the geth node to invoke the generateSVGImage on the NounsDescriptor smart contract, using a noun trait seed. I used etherescan.io read contract to call the "seeds" method on the Nouns DAO contract for noun 82 which caught my eye. This gave me the seed trait [0, 15, 93, 116, 20]. I then got the NounsDescriptor ABI from etherescan.io, did some ethers javascript foo, and generated the RLP encoded smart contract call data.

Next, I wrote the javascript to 1) call the smart contract using a javascript fetch at https://4th.energy/web3, 2) RLP decode the return string, 3) decode the base64 text into the SVG text, and 4) use Javascript to take code and embed an SVG image into the current document context.

The function and call data are shown in "The Code" section below.

Now we move to the fun part and you can try it yourself.

  1. Open your browser and enter https://4th.energy/web3 This will go to a blank page because geth simply returns a 200 (ok) and no content. However you need to do this so that the CORS context is set to that page.
  2. Open the developer tools using using "ctrl-shift-i" and select the console tab.
  3. Copy the code from below and paste it in the console and press return. This will create the function defined in the code.
  4. Call the function by typing "await makeCall();" and press return.
  5. Noun 82 should appear on the https://4th.energy/web3 blank page.
  6. Click on it and it will disappear. "await makeCall();" will make it appear again.
  7. Press F5 (refresh) and you will have to start again as the context will be lost.
This is shown working on my browser in the "Demo Snap" following the conclusion.

Conclusion

I was able to load the Nouns 82 SVG with the browser, a geth node, and nothing else. What does this mean? It means that in a zombie apocalypse, I can load dApps from the blockchain using the console. Practically, it also means that if 4th.Energy provides a browser plugin to automate the console process for loading dApps, the plugin can use ANY standard ethereum node that is available on the open web (like Infura for MetaMask). Users will be able to use their browser to load NFTC dApps directly from the blockchain, and dApps can be properly decentralised and uncensorable.

Demo Snap

The Code

        async function makeCall() {
            // This is the nouns ABI encode smart contract call of generateSVGImage for nouns seed [0, 15, 93, 116, 20]
            var callData = [
                "0x2ea04300000000000000000000000000000000000000000000000",
                "0000000000000000000000000000000000000000000000000000000",
                "000000000000000000000000000f000000000000000000000000000",
                "000000000000000000000000000000000005d000000000000000000",
                "0000000000000000000000000000000000000000000074000000000",
                "0000000000000000000000000000000000000000000000000000014"
            ];
            // This is the correctly formed WEB3 Json to the GETH (any) eth node.
            var callJson = {
                jsonrpc: "2.0",
                method: "eth_call",
                params: [{
                        to: "0x0Cfdb3Ba1694c2bb2CFACB0339ad7b1Ae5932B63",
                        data: callData.join(''),
                    },
                    "latest",
                ],
                id: 83,
            };
            // This makes the call to the open geth node proxied at https://4th.energy/web3
            // (abuse it, we lose it)
            const resp = await fetch("https://4th.energy/web3", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify(callJson),
            });
            // Step 1. Make the above smart contract call
            var json = await resp.json();
            // Get the result out.
            var text = json['result'];
            // Do some quick and dirty RLP decoding to get the base64 output
            text = text.substring(2);
            // Drop 32 bytes/64 chars, for redundant size of size text
            text = text.substring(64);
            // take the next 64 as string size
            var sizeText = text.substring(0, 64);
            // Move along
            text = text.substring(64);
            // Convert to radix 16 int as bytes * 2 for chars
            var size = parseInt(sizeText, 16) * 2;
            // Read 2 chars as code units (basically ascii)
            var readText = '';
            for (var i = 0; i < size; i += 2) {
                var charCode = parseInt(text.substring(i, i + 2), 16);
                readText += String.fromCharCode(charCode);
            }
            // Base64 decode to get the HTML text
            var nounHTML = atob(readText);
            // Insert as a popup on the page.
            var body = document.getElementsByTagName("body")[0];
            var wrap = document.createElement("div");
            wrap.style = "z-index: 1;position: fixed; padding-top: 120px; left: 0; top: 0; display:block; width:100%; height:100%;background-color: rgba(0, 0, 0, 0.25);";
            wrap.onclick = function() {
                body.removeChild(wrap);
            };
            var noun = document.createElement("div");
            noun.style = "width: fit-content;min-height:none;margin:auto;"
            noun.innerHTML = nounHTML;
            wrap.append(noun);
            body.append(wrap);
        }        
            

References

Nouns Information Page