Saturday, November 18, 2017

Getting started with a private Ethereum Blockchain and Solidity Contract Development on geth 1.6 or higher

In order to take this tutorial it is assumed that you have set up your development environment as explained in the first part Installing a Solidity Development Environment for Ethereum Contract programming.

In the second part of our Ethereum programming tutorial we will set up a private Ethereum development network, and implement our first smart contract.

This tutorial is based on a geth version later than 1.6, namely 1.7.2. What makes worth mentioning it is the fact that in version 1.6 the inline Solidity compiler support was removed from geth. For whatever reason. But by the time of writing these lines (end of 2017) there is no ramp-up tutorial to be found out there which reflects this fact. Even the official Ethereum "Hello world" tutorial still refers to the Solidity compiler being included with geth!

But no worry: I'll do that for you! Here comes the first rookie walkthrough for creating, compiling, deploying, and running a Solidity Ethereum contract utilizing the command line Solidity compiler!

1. Setup the private Ethereum Development Node

First create a new data directory for your private blockchain. We do this right beneath our home directory:

$ mkdir ~/privatechain

Then start a geth development network pointing to this data directory:

$ geth --rpc --rpcaddr 127.0.0.1 --rpcport 8545 --dev --datadir ~/privatechain

If everything went right the last terminal output should read something like:

IPC endpoint opened: /Users/as/privatechain/geth.ipc 

HTTP endpoint opened: http://127.0.0.1:8545 

But what do these options mean:
  • rpc: Enable remote procedure calls via http on this geth instance, so we can connect to it from another node (i.e. terminal window)
  • rpcaddr: The IP address where RPC shall be accessible. In this case it's the local host since we are on the same machine.
  • rpcport: The port to access RPC. As 8545 is the standard port, we also could have left out this option.
  • dev: This flag creates a development network, so we do not connect to the "real" Ethereum network where we would have to spend real Ether (meaning real money) to execute our contracts. It also conveniently preconfigures the first block (the Genesis) of our network. We will cover creating our own Genesis Block in a later tutorial.
  • datadir: Guess what? It points to our private chain's data directory just created!
Just take a look into your privatechain directory. You should find the subdirs geth and keystore there!

2. Connect a geth console to the Development Node

You will have recognized that our running geth node is quite busy with its own affairs. It's not being impressed by typing commands into its terminal window. Not even by poems! So we have to get in contact:

Open another macOS terminal window. I'd suggest using a different color for it so you never mix up the terminals later on. We will use the grass style terminal throughout this tutorial - as it comes preconfigured with macOS - to reference the remote command line interface (CLI) geth console.

So just execute this in the new console window replacing the "as" with your user name:

geth attach ipc://Users/as/privatechain/geth.ipc 

Congratulations! You just opened a remote console to our Ethereum node!

But what about the "ipc" thing when we where talking about "rpc" earlier? Just that simple: IPC or Inter Process Communication can be used when separate processes need to interact on the same machine while RPC must be used when the processes run on different machines.

3. Create the first account

Now we have to create an external account on our Ethereum network. An account enables us to perform transactions on the network (i.e. make use of it). To do so we utilize geth's Management API:

personal.newAccount('password')

'password' literarily is the password here. So feel free to use your own! This command will output the address of the account created. It's a good idea to write it down somewhere!

Now check the account really exists:

personal.listAccounts

This should output an array containing the account just created. So in my case it's:
["0x839d4ed149062e6e2e7ab772167f366282c600ce"]

This first account automatically is set as the coinbase in our network. This is where all the mining rewards will got to. You can check it using the web3 API:

eth.coinbase

This should output the same account just created.

4. Create the Contract Source Code

Now it's time to write our first Ethereum contract definition. If you are familiar with object oriented programming languages it helps to think of a contract definition as a class definition.

For better maintainability we will store the contract files in a separate directory. So just go ahead an create a folder ~/ethcontracts!

Right inside this folder create a new text file named MyFirstContract.sol. This is your first Solidity source code file! Feeling great?

Now paste this code into the file and save:

pragma solidity ^0.4.8;

contract MyFirstContract { 

    function reflect(string yourself) public returns(string){
        return yourself;
        
    }
    
}

I think there isn't a detailed walkthrough required. You will have noticed that in the first line we define the smallest Solidity version supported by this contract. Then we create a contract named MyFirstContract which has one method that simply reflects its input.

But now comes the challenging part:

5. Compile the Contract

As told earlier in this tutoriel, calling the Solidity compiler right from the geth console has been removed from geth 1.6. So we have to compile our source file outside the running Ethereum network, and import it into it. In order to do so we need to have the Solidity compiler installed as explained in the first part of this tutorial.

As we have two instances of macOS terminal already running, we will need a third one. Believe it or not! So please open another terminal instance. We will use the ocean theme to identify it in this tutorial.

First we'll do a test run to check if the Solidity compiler is working as expected. So please issue:

$ solc --optimize --combined-json abi,bin,interface ~/ethcontracts/MyFirstContract.sol  

We should get something like this:

{"contracts":{"/Users/as/ethcontracts/MyFirstContract.sol:MyFirstContract":{"abi":"[{\"constant\":false,\"inputs\":[{\"name\":\"yourself\",\"type\":\"string\"}],\"name\":\"reflect\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","bin":"6060604052341561000f57600080fd5b6101578061001e6000396000f3006060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c1ce53fc8114610045575b600080fd5b341561005057600080fd5b61009660046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061010d95505050505050565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100d25780820151838201526020016100ba565b50505050905090810190601f1680156100ff5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610115610119565b5090565b602060405190810160405260008152905600a165627a7a723058207e8ac985f99f507869c23d1d1457c38e5aace7b1eafa9f31eff3e02d9aecfd450029"}},"version":"0.4.18+commit.9cf6e910.Darwin.appleclang"}

If there are some warnings ahead of this output we just can ignore them! The important thing to notice is that we have our contract source code compiled into  an abi and a bin section.

Now let's use this for real!

In order to be able to deploy the compiled contract into our Ethereum dev network, we have to create a compiled file from the source code. We do it using good old terminal commands:

echo "var MyFirstContractCompiled=`solc --optimize --combined-json abi,bin,interface ~/ethcontracts/MyFirstContract.sol`" > ~/ethcontracts/MyFirstContract.js

What happens here is that we create an actual JavaScript file MyFirstContract.js where we assign the output JSON object of the Solidity compiler to a variable called MyFirstContractCompiled.

Now we're ready for the next step:

6. Import the contract 

We want to get the precompiled contract from the compiled JavaScript file into our Ethereum node and prepare it for deployment to our development network.

So we return to our geth console and load the JavaScript file just compiled (Note: We assume that the geth console was started from your user's home directory. If not, you have to specify the absolute path to the MyFirstContract.js file):

loadScript("./ethcontracts/MyFirstContract.js")

The resulting output should be:
true

So let's check if everything went right by outputting the value of the MyFirstContractCompiled variable:

MyFirstContractCompiled

Should give you an output like this:

{
  contracts: {
    /Users/as/ethcontracts/MyFirstContract.sol:MyFirstContract: {
      abi: "[{\"constant\":false,\"inputs\":[{\"name\":\"yourself\",\"type\":\"string\"}],\"name\":\"reflect\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]",
      bin: "6060604052341561000f57600080fd5b6101578061001e6000396000f3006060604052600436106100405763ffffffff7c0100000000000000000000000000000000000000000000000000000000600035041663c1ce53fc8114610045575b600080fd5b341561005057600080fd5b61009660046024813581810190830135806020601f8201819004810201604051908101604052818152929190602084018383808284375094965061010d95505050505050565b60405160208082528190810183818151815260200191508051906020019080838360005b838110156100d25780820151838201526020016100ba565b50505050905090810190601f1680156100ff5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610115610119565b5090565b602060405190810160405260008152905600a165627a7a723058207e8ac985f99f507869c23d1d1457c38e5aace7b1eafa9f31eff3e02d9aecfd450029"
    }
  },
  version: "0.4.18+commit.9cf6e910.Darwin.appleclang"
}

Now we have to create an actual contract object from the compiled contract definition, which we can instantiate on our Ethereum network later. To achieve this, we have to select the abi part of our MyFirstContract stored in the MyFirstContractCompiled variable and pass it to the web3.eth.contract function:



MyContract = web3.eth.contract(JSON.parse(MyFirstContractCompiled.contracts["/Users/as/ethcontracts/MyFirstContract.sol:MyFirstContract"].abi))

Make sure to replace the "as" with your actual user name!
It is important to not forget the JSON.parse part! This will parse the string representation of our abi into an actual JavaScript object.

You should get an output like this:

{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "reflect",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }],
  eth: {
    accounts: ["0x839d4ed149062e6e2e7ab772167f366282c600ce"],
    blockNumber: 0,
    coinbase: "0x839d4ed149062e6e2e7ab772167f366282c600ce",
    compile: {
      lll: function(),
      serpent: function(),
      solidity: function()
    },
    defaultAccount: undefined,
    defaultBlock: "latest",
    gasPrice: 0,
    hashrate: 0,
    mining: false,
    pendingTransactions: [],
    protocolVersion: "0x3f",
    syncing: false,
    call: function(),
    contract: function(abi),
    estimateGas: function(),
    filter: function(options, callback, filterCreationErrorCallback),
    getAccounts: function(callback),
    getBalance: function(),
    getBlock: function(),
    getBlockNumber: function(callback),
    getBlockTransactionCount: function(),
    getBlockUncleCount: function(),
    getCode: function(),
    getCoinbase: function(callback),
    getCompilers: function(),
    getGasPrice: function(callback),
    getHashrate: function(callback),
    getMining: function(callback),
    getPendingTransactions: function(callback),
    getProtocolVersion: function(callback),
    getRawTransaction: function(),
    getRawTransactionFromBlock: function(),
    getStorageAt: function(),
    getSyncing: function(callback),
    getTransaction: function(),
    getTransactionCount: function(),
    getTransactionFromBlock: function(),
    getTransactionReceipt: function(),
    getUncle: function(),
    getWork: function(),
    iban: function(iban),
    icapNamereg: function(),
    isSyncing: function(callback),
    namereg: function(),
    resend: function(),
    sendIBANTransaction: function(),
    sendRawTransaction: function(),
    sendTransaction: function(),
    sign: function(),
    signTransaction: function(),
    submitTransaction: function(),
    submitWork: function()
  },
  at: function(address, callback),
  getData: function(),
  new: function()
}


7. Deploy the contract

Now that we have our MyContract object, we want to deploy it to our Ethereum development network.

As you know: There's no free lunch! Especially not on the Ethereum network. Every transaction has its cost. And contract deployment is a transaction! So we have to check first if we have enough Ether to actually do the deployment.

For convenience we define a neat variable pointing to our coinbase account (where all the mined ether goes to)..

MyAccount = eth.coinbase

..and check the funds:

eth.getBalance(MyAccount)

If this returns 0, we have to mine some Ether:

miner.start()

This will start the mining process on our initial geth development node. Just check your first terminal window (the one that is black in this tutorial, but is likely to be white on your Mac).

Meanwhile, we want to know the expenses of deploying our contract. The deployment itself is a Ethereum transaction with the compiled binary contract data as payload. We can use the web3.eth.estimateGas function to simulate this transaction and estimate the gas required to execute it.

First we create a hex string from the bin section of MyFirstContractCompiled and store it in a variable for later use. This is done by suffixing the bin string with "0x":

MyContractBin = "0x" + MyFirstContractCompiled.contracts["/Users/as/ethcontracts/MyFirstContract.sol:MyFirstContract"].bin

Then we can estimate the costs of contract deployment:

eth.estimateGas({data: MyContractBin })

This will output a number to which we will refer a little later when actually deploying the contract.

For now it's time to stop mining:

miner.stop()

Prior to be able to deploy the contract we have to authorize (or unlock) our account with our password, so nobody else than you can spend your ether for this deployment:

personal.unlockAccount(MyAccount, "password")

And now we create the contract:

MyContractInstance = MyContract.new({data: MyContractBin gas: 143940, from: MyAccount})

Supply at least as much gas as we estimated a few steps earlier!

You should get an output like this:

{
  abi: [{
      constant: false,
      inputs: [{...}],
      name: "reflect",
      outputs: [{...}],
      payable: false,
      stateMutability: "nonpayable",
      type: "function"
  }],
  address: undefined,
  transactionHash: "0x42878f04878dd33dbb6969d72ff48385ed2c050e867def4b2b1c3536d8ec9f85"
}

Now our contract is deployed to the Ethereum development network. But it's not alive yet: Its address is still undefined!

8. Activate and test the Contract

What we've done in the step before is telling the Ethereum network that we want to execute a transaction which creates our contract as an executable unit of code in the network. Now it's up to the miners to decide when and where this transaction will be executed. In real life they would take this decision based on the amount of gas we provided. But since we are in the test net, and we currently have only one miner, our transaction will be executed anyway! So let's start mining again:

miner.start()

After a few seconds check the contract instance again:

MyContractInstance

It should have an address set now! So our contract is alive and we can test it.
But first let's stop mining:

miner.stop()

Then we call the reflect method of our contract:

MyContractInstance.reflect.call("I'm alive!")

And voilĂ : Your contract tells you that it is alive!

If you wonder why we had to add call to the actual method name, you will find the answer here.

Congratulations! You just implemented your first Ethereum contract!



No comments:

Post a Comment