This is part III of a series. Part I
Diving into the code
My simple proof-of-concept app can be seen at https://github.com/Quiark/Roboth.web3 and is based on the meteor-dapp-boilerplate project. The smart contract is called Roboth and is deployed on the (currently testing) blockchain, registered with the Global Registrar under the same name. However, I'm still working on it so be prepared to encounter a broken, invalid or a stupid deployment at any time.
Thoughts on deploying beta contract versions
Now this is clearly not a best practice to push stupid code right into the public production environment. I could register the work-in-progress update with the Registrar under a different name such as "Roboth.RC-1" and config my JS frontend to interface with this instance. Alternatively, I could run geth (the ethereum client) on a private testnet using the command line switch
geth --networkid <random number here> --maxpeers 0
or by disconnecting my wifi. It would also require me to clean my blockchain database because I would be starting from scratch effectively. In this way, I could mine all ether by myself and thus have enough for funding any experiments.Simple Python compile & deploy script
If you prefer your cozy text editor over cool web based development environments, you may find my Python script for compilation and deployment mildly useful. It's included right there in the Roboth.web3 repository as tools/contract.py, for free without any hidden costs.
It can handle the following tasks:
- compile contract code on your geth node (I'm using Windows and don't have a solc binary)
- deploy compiled contract
- register the newly deployed contract's address with the Registrar
- remember compiled code, ABI and address so you can go back and use any earlier-deployed version in case you forgot some semi-important data there (you don't have any really-important data because otherwise you'd be using some more serious and stable software)
- save the new ABI as JSON to a JS file automatically loaded by Meteor
- invoke some methods of the contract after deployment so you are not testing with an empty database (must be customised for your particular contract)
- use hard-coded file paths so you know where to put your files by reading source code (ehm)
Currently it cannot do:
- watch files for changes
- report solc compilation errors very precisely
- integrate very well with your cozy text editor
- find the meaning of life
To use it, you'll need to modify the code a bit, edit the geth RPC address where EthRpc is instantiated, edit your primary account in prim_acc and possibly also the contract name variable con_name. When running, current working directory must be tools (that's where the script is located). The tool currently doesn't accept commandline arguments, it must be configured by changing the code at the end of the file.
It also has some dependencies, this one and this one too.
It also has some dependencies, this one and this one too.
Working with your contract from the JS app
By now you may already be rather familiar with the incantation that takes your contract's binary ABI and its blockchain address and creates a proxy object to call it. It looks like this
this. RegistrarABI = [{"constant":true,"inputs":[{"name":"_owner","........
this. RegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b";
this. RegistrarAPI = web3.eth.contract(this.RegistrarABI);
this. Registrar = this.RegistrarAPI.at(this.RegistrarAddr);
This is required because even though we write the contract code in Solidity, it's compiled into EVM bytecode and even though we use functions, arrays and mappings, these have a different representation on the blockchain (which is also different from linear memory layout we are used with RAM). The JSON RPC we are using is operating at the low level and it doesn't really know how to call Solidity functions. But the web3.js library knows how to call it, assuming you provide the ABI description that fell out of the solidity compiler.
So in this code snippet, there's a hardcoded ABI for the official testnet registrar contract that I stole directly from geth source code, its official testnet address which I also stole from the same place. Next, the RegistrarAPI creates a class as you know it from OOP languages (if you are coming from C++ or Java, you may not believe that a single function call can create a class but yeah, dynamic languages can do that). On the last line, we instantiate this class using its static method at() and the instance will communicate with the contract on the given blockchain address.
The same procedure would be used for our own contract except that its ABI is automatically generated by the Python script and included by Meteor from client/lib/compatibility/Roboth.abi.js because it's under rapid development and thus changing all the time. Furthermore, the address is fetched from the Registrar where it's stored by the same script on each deployment. See here for yourself.
Once you have a proxy instance, you can call methods and send transactions almost the same way as in regular OOP languages. These are the 2 ways to invoke a method and it's explained in the Frontier Guide.
The simplest way ever to store a growing mapping in Solidity
Assigning some data to an user or an address in Solidity is quite easy, just use the mapping type:
mapping (address => MyData) mydatas;
What happens, however, when you want to iterate over the keys or values to display it in your app? This is not currently supported and I believe wouldn't be so easy to implement because the data layout is not linear. A simple solution is to add an integer index
mapping (uint => address) users;
uint next_user_ix;
Now we can iterate from 0 to next_user_ix and get all users in the range. Of course this requires that you maintain the index manually, adding to it each time a value is added to the original mapping. This approach is very simple but it doesn't really work well when you also need to remove values. You can see the forum post on this problem for other people's ideas.
Ethereum values data types
I recommend always storing account balances in wei as Strings or BigNumbers. Javascript doesn't handle large integers correctly and wei balances are always going to be pretty large. Furthermore, given the number of units or denominations for ether, mixing them up in the code is a really big danger. The only way to stay sane is to stick with wei, just like the JSON RPC and only convert to human-friendly in the templates (using the toEth template helper).
Similarly with addresses, they come as hex string and should stay in that format
Reacting to data from blockchain
Meteor has a neat functionality that enables auto-refreshing your HTML DOM when source data changes. It's called being reactive™. We can use this function to some extent but keep in mind that operations on the blockchain are not instant (and also not immediately reliable until all small forks are abandoned).
The most reliable way to observe changes in the blockchain is to use Solidity events and install filters from the RPC. However, if you don't have that for whatever reason, you can just keep polling every 6 seconds or so.
The class BlockchainTracker is a simple wrapper that will fire an update on its ReactiveVar when the latest block number changes. This can be observed in an autorun function to trigger a refresh from the blockchain. See UserDataManager for an example of a dataset that needs to be updated when a new item gets added. This simple solution doesn't handle updates from other users and it may miss changes that appear 2 blocks later.
The most reliable way to observe changes in the blockchain is to use Solidity events and install filters from the RPC. However, if you don't have that for whatever reason, you can just keep polling every 6 seconds or so.
The class BlockchainTracker is a simple wrapper that will fire an update on its ReactiveVar when the latest block number changes. This can be observed in an autorun function to trigger a refresh from the blockchain. See UserDataManager for an example of a dataset that needs to be updated when a new item gets added. This simple solution doesn't handle updates from other users and it may miss changes that appear 2 blocks later.
Conclusion
The app is still very much in development with many rough edges but I hope people starting out with ÐApps may find these notes useful.
No comments:
Post a Comment