Introduction to Solidity: Creating a data contract [Part 1]
Introduction to Solidity: Creating an environment
To execute smart contracts, we need a system that is able to compile, deploy and call its methods. For all the three we have a very simple integrated browser based app here.
It is possible to use this tool to connect with a live Ethereum node or to form a mock environment. This environment can also easily execute some test cases on the contract methods. It doesn’t need any install and is completely browser based. I have used it on chrome over ubuntu, but I’m sure it works just as well on other platforms. The left pane of the app is where the contract code is, and the right side has deployment and test options. The app can work on contract files from own systems.
The source code of the contract we will be discussing is available here. Some parts of the contract code are going to be discussed in the later parts of the series. The code and be downloaded and loaded into Remix to view the sections that are discussed below. Solidity latest unreleased version is 4.10 nightly and we will be relying on features supported by that specific version.
DataContract (Basic data structure):
A data contract is not standard terminology. There multiple types (patterns) of smart contracts defined on Monax (earlier Eris industries) documentation. The “data” type of contract is a simple concept where data is added, updated, removed and accessed. In the process of creating data contract, we will be able to work with an Access Control List (ACL) contract which can be used to manage role-based security on all contract types, Eventing mechanism for logging and returning data and some other features of smart contracts.
To create a simple data contract, assume a user defined high-level data set for e.g. Customers
To simplify, we structure a simple Customer whom we assume to be an individual:
We are implementing the struct with the assumption that each data record will have a status. It is not feasible to delete and record and rearrange the entire collection, so status is an attribute that we will leverage to define a state for the record. Contracts in different domains will have a different set of status. As a simple assumption we are following 3 statuses (This is with the thought that a customer might be pending verification, active or might be deleted):
A deleted record for any reference data type on a blockchain should never get hard deleted, so it is an appropriate design practice to assign a status to each record.
Our objective is to enable a collection of Customer struct instances on the blockchain and facilitate the ability access a specific customer, the list of call customers, and update a specific customer instance. A cross-cutting concern with these objectives is to enable logging and implementing an Access Control Strategy.
To create a collection we will leverage the mapping construct.
mapping (uint => Customer) customers;
This mapping is the template for a map like structure that creates a map of key values where key is an unsigned int and value is an instance of a customer. This mapping has some limitations, which is that there is no way to loop through it to retrieve all the values. We can only access the elements through a specific get logic:
Due to this, we will have to maintain a shadow key to maintain a count of elements in the mapping and retrieve it based on the count.
Now we have our data structure ready that we will be used to house the customer data.
Data Contract (Data Creation):
To create a customer instance and key it off we will implement a method that will accept constituent data.
The above method adds data to the mapping and increments the shadow key count. What is to be noted is that we are using the same shadow key to key off the data.
To access data randomly, we will need to supply a specific key against which we have keyed off the data.
function getCustomer(uint index)
constant returns (uint id, string name, uint dateOfBirth, uint social, uint status)
This method is a constant one as it does not change the state of the contract data (it’s a read-only method). So to call this method we will not need any gas.
To access all the customers we will have to leverage the count shadow key. We will need to implement the looping structure on the client and reuse the index based call above. This will be explained the contract client explanation.
To access the customer based on a specific attribute in a struct, We will have to implement a loop based brute search. There are more efficient ways to reading it using parsing the transaction that was responsible for creating that data.
function getCustomerById(uint id)
constant returns (uint idRet, string name, uint dateOfBirth, uint social, uint status)
This is a very inefficient way to retrieve the customer from an attribute value. There are also other issues with this method apart from inefficiency. It’s not possible to easily match strings in solidity, so it’s not possible to match string attributes. Also, it returns the first match, it’s not possible to return a list of matches. We will go into more efficient methods to access this data in later parts of this series.
Data Contract (Data Update):
The update data method is simply reverse of the access method. The only thing to remember is that this method results in a change for the blockchain state so the changes will only reflect once the transaction is confirmed. We will discuss how to ensure that transaction is confirmed before we try to access the data in later part of our series.
It makes sense to put some checks for index value (It has to be less than the count)
The different method should be implemented for updating each attribute, here we are updating the name. The method updateCustomerStatus() should be treated as a special method that can enable or disable records.
It’s possible to add the same check on the access method that accesses the index key, but it’s not necessary as the return will null in case the invalid index is supplied, so the client should be able to validate the response and return suitable errors. Since access would be a more frequently called method set, it should be made as efficient as possible.
Testing the contract:
To test the contract, go to the environment tab on the right panel (the box icon).
This test environment gives you the ability to call contract methods individually and see the results on the data.
To start with let’s call the create Contract method to add some data onto the blockchain (not the real one yet, but mock one).
For e.g. (For simplicity we have chosen dateOfBirth to be of the unit timestamp which can fit into a unit)
This will indicate that the method call has been successful, you can confirm it by checking the count of the customers by hitting the count button (The method call count()). Remember that solidity exposes its public members through a direct call with the signature member().
The call should print out something like:
This uint256 has a value 1 which indicates that you have a single member in your recordset.
You can run the above createCustomer call multiple times and verify the result. Beware that there is no check on adding duplicate records. This has to be checked by the client.
You can retrieve the customer using the index based getCustomer(0) – remember your index starts with 0. You should see output like this:
The value being returned is the encoded data that is returned. It is decoded and displayed and parameter values in sequence below.
Now you have a complete working contract that has been tested against some test data.
There are situations when you contract does not work as expected, in which case you have to debug the contract to find out the state of the contract data as method executes and identify the problem. There is currently no solidity debugger. There is a solidity IDE – Mix which has the capability, but it is currently not being supported. You can test it out at your own risk.
Deploying the contract:
You can refer to a previous article on how to set up local ethereum node using parity. We will use this capability and assume that you have a local running ethereum instance that exposes JSON_RPC port on 8545. We also assume that you have an account created and is returned at accounts location, also this account should have enough ether balance to deploy the contract and execute transaction calls with the gas charges.
The remix IDE gives you the ability to deploy this contract directly from the IDE into the ethereum instance. To do that you have to first select the Web3 provider option from the Environment tab. If the environment is currently picked up you will not see any error below the contract.
Screen_3 (Contract deploy option)
The IDE gives you three outputs from the contract compilation process BTW the contract is automatically compiled. Ensure you see the options as shown in Screen _3. else it means that compilation has failed, and you should see the compilation error.
Running the contract from a real client.
To enable web3 in a browser supported environment, a single script file needs to be referenced within the page code to interact with the contract.
There are is a templated approach available to get a contract handle.The first thing that is needed is to instantiate web3 and supply the contract ABI to it.
The provider url is your local JSON_RPC interface. The default account setting is the ethereum wallet which will be used for as the from the account when we will execute the transactions on the contract. We need to ensure that the account is unlocked if we are using geth node instance as by default the account is locked every 30 seconds after its unlocked.
The next step is to instantiate the contract blueprint by supplying the contract ABI so web3.
var customerContract = web3.eth.contract([ABI JSON]).
The ABI JSON array is usually a long JSON string so we are just paraphrasing, but you should be copying the entire ABI array from the Interface text area as show in screen3. Once you have the customer contract blueprint, we need to instantiate it by supplying the real address at which the contract resides on the blockchain.
var customerContractObject = customerContract.at(‘0x76bd9986c5c3e00111c82e16e01e282696d2b3fb’);
This address would be what you got when you deployed the contract from the web3 deploy or from ethereum console. Once you have the contract handle linked back to the deployment address, you can execute contract methods by sending transactions to the address. For example, let’s say you want to add customer record after your html form accepts inputs for the form values from the user, then your html + web3 code would look something like this:
The above code will work out of the box if you fix the path to web3.js and jquery. Just as we have called the method on the contract handle:
It’s possible to call all other methods. The estimated gas needs to be entered from the test method calls, this is 144840 in my configuration. The above call returns a transaction Id for the transaction that is created on the blockchain to execute this method. This transaction is now a part of the blockchain and can be referred to whenever we want to audit when, how and by whom was this customer created.
There are two types of calls, as we have seen during the coding of the contract. One which updates the state of the contract like createCustomer, updateCustomer etc. And others which are read-only and are marked as constant. The former always return a transaction Id hash, transaction Id hash is a 64 byte identification of the transaction that can be used to refer back to the transaction. If we are running the contract in a synchronous way as we are doing here. The latter return the actual values that are sought in a comma separated array. For example the call:
In the next part of the series, we will see how some non-functional concerns and aspects are built into the contract-like access control, event logs, contract deletion. We will discuss parts of the contract that specifically deal with these issues.