tl;dr Executing a contract storing just 1kB in a string will cost you about $14 dollars. Ouch! [20th December 2017]
Beyond the vanilla uses of blockchain, as an immutable public ledger of financial transactions, other quite interesting and very useful applications have been proposed. Take these two examples:
- Records of land ownership: Imagine a country recovering from a civil war, wanting to restore the ownership of property prior to upheaval and to do so using a public record that is known to be impartial and not tampered with.
- Chain of custody problems: Guaranteed records of origin and lifecycle for many industries, including aircraft maintenance, medical records, blood banks and farm to shelf verifiable food products.
Adding to the pile of wonderful ideas, we speculated during our morning stand-ups that using blockchain to replace the function of a Notary would be ideal. To make important conversations and documents a matter of public record that would be publicly available and verifiable on the blockchain holds great value we thought. Notarizing a document can cost up to €50 here in Ireland and surely we could do the same for 50c.
With a few strokes of the pen we had a simple Ethereum smart contract that would suit our needs:
pragma solidity ^0.4.19; contract horizonMortalContract { /* Define variable owner of the type address */ address owner; /* This function is executed at initialization and sets the owner of the contract */ function horizonMortalContract() public { owner = msg.sender; } function kill() public { if (msg.sender == owner) selfdestruct(owner); } } contract horizonNotariser is horizonMortalContract { /* Define variable to hold the notarised conversation of the type string */ string conversation; /* add another message to the notarized conversation */ function notariseMessage(string _message) public { if (msg.sender == owner) { bytes memory _bytesMessage = bytes(_message); bytes memory _bytesConversation = bytes(conversation); string memory _concatenated = new string(_bytesMessage.length + _bytesConversation.length); bytes memory _bytesConcatenated = bytes(_concatenated); uint _currentIndex = 0; for (uint i = 0; i < _bytesConversation.length; i++) _bytesConcatenated[_currentIndex++] = _bytesConversation[i]; for (i = 0; i < _bytesMessage.length; i++) _bytesConcatenated[_currentIndex++] = _bytesMessage[i]; conversation = string(_bytesConcatenated); } } /* Retrieve the current state of the conversation */ function getConversation() public constant returns (string) { if (msg.sender == owner) { return conversation; } else { return ''; } } }
Using “notariseMessage” anything we wanted to notarise for public record could be placed on the blockchain, available for anyone to query with “getConversation”
So we did a test – Appending to a conversation several times:
{"jsonrpc":"2.0","method":"eth_sendTransaction", "params": [{ "from": "0xff67c1be2629b260adbec13245a0e8685bd51a79", "to": "0x58f50c29db7ee0b46a0b93ff63dbc8d802392f58", "gas":"0x47B760", "data":"0x819982f10000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001b202e2e2e206a7573742061207365636f6e64206d657373616765200000000000"}],"id":1}
When we reached about 256 characters we noticed something worrying in the transaction receipt:
{ "jsonrpc": "2.0", "id": 1, "result": { "blockHash": "0xc2493d2496c137f74200d861e7edc4b2fff15786c4a4b1dd7f47d3225253f23d", "blockNumber": "0x42", "contractAddress": null, "cumulativeGasUsed": "0x19e35", "from": "0xff67c1be2629b260adbec13245a0e8685bd51a79", "gasUsed": "0x19e35", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x58f50c29db7ee0b46a0b93ff63dbc8d802392f58", "transactionHash": "0x8a591e24e19f73ace6fb9e3b6e222b2055b49d2239a5d9ad44264bf69169b792", "transactionIndex": "0x0" } }
Why had the gas increased to 106037, storing 256 characters? On Ethereum you pay for the size of the state to store each transaction on the blockchain. Our string incurred greater and greater costs as we appended to it [see an example here].
The amount of gas isn’t necessarily the problem, but the cost of each gas does change. Compounding the problem was the appreciating ETH:USD. At the time of writing gas prices stood at 40gwei [found here] and the exchange rate was $814.
So notarising my 256 byte sample cost me a whopping (106037*0.00000004*814)==$3.45 ! 1kB which amounts to little more than ⅓ of a page would have cost $13.81.
With the rapid appreciation of Ether<=>USD, it means that this cost has doubled from a month previous. Such a service has been rendered uncompetitive with an actual Notary. As would a whole raft of other fabulous blockchain applications that would by their nature rely on verbose text as part of their contract execution.
Fortunately, our core blockchain business here at Horizon Globex does not rely on such verboseness of contracts. But a valuable lesson is still to be learned here. Even with a contract not relying on dynamic types careful attention needs to be paid to the word size of the core types you use. E.g. do you really need an int where you could use a bool? Can entire variables be eliminated and still maintain the semantics of your contract? Because every byte will noticeably cost ever increasing amounts of money as Ethereum matures.
Epilogue
Of course, giving up on the idea was seriously bothering us. Is there another way to maintain all the semantics of a notary without incurring enormous storage costs? What if instead of storing the conversation on the blockchain, we instead stored the SHA-256 signature of that conversation, signed with our Ethereum wallet key, and link that to the actual conversation stored in our database. Now we would have a fixed size, small contract. Signatures of conversations would be a matter of public record and immutable.
Any conversation we present from our database could be independently verified by retrieving this signature from the blockchain. In fact, this mode would maintain the semantics of a real notary better than the previous solution by keeping the contents of what was notarised away from public view until it has been called into dispute. Magic! 😊