As you probably know, a blockchain is a sort of ledger, maintained and updated over time through a peer-to-peer consensus algorithm. The key innovation of the Ethereum blockchain is the ability to do more than just make financial transactions, effectively throwing value from one 'account' to another. Instead, through the use of the Etheruem virtual machine, peers can have their programs executed on the Ethereum network, enabling automatic peer to peer interactions without the overhead of an intermediary. Without the absence of the intermediary, this would just be another paradigm for fault-tolerant distributed computing.
There is a lot of hype (and money) being thrown around in Ethereum land right now. But from what I've observed, very few people seem to understand the technology. Nevertheless, many people advocate its utility, with little more than speculative and esoteric visions of a 'blockchain economy'. I don't mean to discourage any visionaries or theoreticians among us. Rather, I hope to, at a minumum, encourage and help facilitate experiment in blockchain applications in a setting that is not too complicated.
The aim is to set up an environment for writing smart contracts in Ethereum, while sticking with a text editor and the command-line. We will set up a private blockchain and look into some simple applications, so we can get a real, tangible sense of what this is good for, how it performs, and where we stand in the realm of practical application (not far).
I am therefore going to be asking myself a lot of questions that may seem very basic to some. I may, as well, be doing some programming that may seem complex, or, at least, contrived. The hope is that by reading along and mimicing the keystrokes, you can get some sense of what is going on.
That being said, I make no guarantee whatsoever that this will provide you with any form of satisfaction, enlightenment, or understanding - I am probably not much less confused than you.
To install geth
from PPA, run:
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum
If you don't have brew
, install it:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
To install geth
, run:
brew tap ethereum/ethereum
brew install ethereum
To keeps things simple for now, we will start by running a single node on a private network. This will allow us to inspect the basic data structures, objects, and procedures without the delay and complications of hopping onto an existing large-scale blockchain. To do this, we will use a custom genesis block. This is done by specifying the following key block details as a JSON file.
- nonce: 64-bit hash used with mixhash for proof-of-work
- timestamp: Unix-time value
- parentHash: Keccak 256-bit hash of parent block header
- gasLimit: Maximum amount of gas that can be expended in a single block
- difficulty: Determines the difficulty of mining this block
- mixhash: 256-bit hash used with nonce for proof-of-work
- coinbase: The address of the account in which mining rewards are deposited
- alloc: A specification of initial Ether allocations among accounts on the network
- extraData: Additional relevant block data, up to a maximum of 32 bytes
Save the following into a file called 'genesis.json'.
{
"nonce": "0x0000000000000123",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x0",
"gasLimit": "0x8000000",
"difficulty": "0x400",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x3333333333333333333333333333333333333333",
"alloc": {}
}
To start a node, run
geth --genesis genesis.json
This will use the above json file to write block 0 of the blockchain, the genesis block. You will see some networking logs indicating the server starting. Open another terminal and run
geth attach
This will launch the Javascript Runtime Environment included in geth
. Here, you have access to a
variety of management APIs, and you can write ordinary JavaScript code. It is instructive to keep
an eye on both terminal windows simultaneously, as the first provides immediate insights for debugging
and understanding.
Start by inspecting the state of the network.
> web3.eth.accounts
[]
> web3.eth.coinbase
null
> web3.eth.blockNumber
0
> web3.eth.getBlock(0)
{
difficulty: 1024,
extraData: "0x00",
gasLimit: 134217728,
gasUsed: 0,
hash: "0x8b1f2271ac3d51f7ca371b8e633e8f1625b64fb4bad3a158cc3da8157dfdaa14",
logsBloom: "0x00000000000000000000000000...",
miner: "0x3333333333333333333333333333333333333333",
nonce: "0x0000000000000123",
number: 0,
parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
receiptRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 507,
stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
timestamp: 0,
totalDifficulty: 1024,
transactions: [],
transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
uncles: []
}
> web3.eth.getBlock(1)
null
So, we have no accounts, our coinbase (default recipient of mined Ether) is null, we are on block number 0, and we can see within its information the data from our JSON file.
Everything in Ethereum starts with an account. The first type of account is called an externally owned account, and is created like so:
> web3.personal.newAccount("strong password that no one will guess")
"0x7a47376c00308be583e1ea5e7a610d58dce68744"
> web3.personal.newAccount("haha")
"0xd451d8e4cc7392c9a21ac8c0e23c6beac1343691"
> web3.eth.accounts
["0x7a47376c00308be583e1ea5e7a610d58dce68744", "0xd451d8e4cc7392c9a21ac8c0e23c6beac1343691"]
> web3.eth.getBalance(web3.eth.accounts[0])
0
We will see the other type of account, a contract account, when we get to writing smart contracts in Solidity.
To do anything, we need Ether.
> miner.start()
true
In the other window, you will see something like this (the timestamps and program name have been ommitted to save space):
Starting mining operation (CPU=8 TOT=9)
Automatic pregeneration of ethash DAG ON (ethash dir: /home/momo/.ethash)
commit new work on block 1 with 0 txs & 0 uncles. Took 110.399µs
checking DAG (ethash dir: /home/momo/.ethash)
Generating DAG for epoch 0 (size 1073739904) (0000000000000000000000000000000000000000000000000000000000000000)
Generating DAG: 0%
Generating DAG: 1%
Generating DAG: 2%
Generating DAG: 3%
Generating DAG: 4%
Generating DAG: 5%
Generating DAG: 6%
Generating DAG: 7%
Generating DAG: 8%
Generating DAG: 9%
Generating DAG: 10%
Once the DAG is generated, mining will start:
Generating DAG: 99%
Generating DAG: 100%
Done generating DAG for epoch 0, it took 4m18.16510184s
Starting mining operation (CPU=8 TOT=9)
commit new work on block 1 with 0 txs & 0 uncles. Took 129.44µs
🔨 Mined block (#1 / 44d47c8c). Wait 5 blocks for confirmation
commit new work on block 2 with 0 txs & 0 uncles. Took 150.156µs
🔨 Mined block (#1 / 348b5a21). Wait 5 blocks for confirmation
commit new work on block 2 with 0 txs & 0 uncles. Took 123.073µs
commit new work on block 2 with 0 txs & 0 uncles. Took 977.309µs
🔨 Mined stale block (#1 / 18c10b67).
commit new work on block 2 with 0 txs & 0 uncles. Took 191.801µs
commit new work on block 2 with 0 txs & 0 uncles. Took 142.205971ms
🔨 Mined block (#1 / d11b9cb6). Wait 5 blocks for confirmation
commit new work on block 2 with 0 txs & 0 uncles. Took 28.898533ms
commit new work on block 2 with 0 txs & 0 uncles. Took 122.455µs
🔨 Mined block (#2 / cfb278cb). Wait 5 blocks for confirmation
commit new work on block 3 with 0 txs & 0 uncles. Took 205.311µs
🔨 Mined stale block (#2 / ac569b95).
commit new work on block 3 with 0 txs & 0 uncles. Took 146.449742ms
commit new work on block 3 with 0 txs & 0 uncles. Took 156.201µs
🔨 Mined block (#3 / 06152682). Wait 5 blocks for confirmation
By default, all mining rewards are deposited into the account at web3.eth.accounts[0]
. In the console, you can verify the increasing balance:
> web3.getBalance(web3.eth.accounts[0]) //units are in wei. 1 ether = 10^18 wei
45000000000000000000
> web3.getBalance(web3.eth.accounts[1]) // still 0
0
> web3.fromWei(web3.getBalance(web3.eth.accounts[0])) // convert from wei to ether
45
To send some Ether, run:
> var sender = web3.eth.accounts[0]; var receiver = web3.eth.accounts[1]
undefined
> web3.personal.unlockAccount(sender, "strong password that no one will guess")
true
> web3.eth.sendTransaction({from: sender, to: receiver, value: 20})
"0xcd2b5724cff9afcf5236a1f49692fbc9082ffac5da6cbe176626c2f8443f07a8" //transaction hash
In the other window, you will see the transaction hash and destination address, followed by a log of a block mined with 1 txs
, a single transaction.
Tx(0x2d4e0ec822f65549116da31cb70d6d04b39d558e031b4927927a75cec87030ff) to: 0xd451d8e4cc7392c9a21ac8c0e23c6beac1343691
commit new work on block 18 with 1 txs & 0 uncles. Took 793.931µs // our transaction will be mined in block 18
🔨 Mined block (#18 / 57a17fa2). Wait 5 blocks for confirmation // our transaction has been mined
commit new work on block 19 with 0 txs & 0 uncles. Took 232.13µs
commit new work on block 19 with 0 txs & 0 uncles. Took 260.54µs
🔨 Mined block (#19 / 6eae0bae). Wait 5 blocks for confirmation
commit new work on block 20 with 0 txs & 0 uncles. Took 195.227µs
commit new work on block 20 with 0 txs & 0 uncles. Took 183.743µs
Confirm the payment has been received:
> web3.eth.getBalance(receiver)
20
To make sure the Solidity compiler is installed, run web3.eth.getCompilers()
. If this does not return an array containing 'Solidity' as an element, exit the console and run:
sudo apt-get install solc
Restart the console and verify that the compiler is available. If it is not, run:
$ which solc
/usr/bin/solc
The output of this command provides the path to the solc compiler installed. Provide this path in the console like so:
> admin.setSolc('/usr/bin/solc')
"solc, the solidity compiler commandline interface\nVersion: 0.3.2-0/Release-Linux/g++/Interpreter\n\npath: /usr/bin/solc"
Type the following contract directly into a variable called source.
> var source = 'contract adder { function add(uint a, uint b) constant returns (uint c) { return a+b;} }'
Compile this and save the output to a variable:
> var compiled = web3.eth.compile.solidity(source)
undefined
> compiled
{
adder: {
code: "0x6060604052602b8060106000396000f3606060405260e060020a6000350463771602f78114601a575b005b602435600435016060908152602090f3",
info: {
abiDefinition: [{...}],
compilerOptions: "--bin --abi --userdoc --devdoc --add-std --optimize -o /tmp/solc171650220",
compilerVersion: "0.3.2",
developerDoc: {
methods: {}
},
language: "Solidity",
languageVersion: "0.3.2",
source: "contract adder { function add(uint a, uint b) returns (uint c) {return a+b;} }",
userDoc: {
methods: {}
}
}
}
}
The code contained in the compiled.adder.code
field is the byte code that will be run on the Ethereum Vritual Machine (EVM). The info
section contains metadata on the contract, including a field abiDefinition
, which serves as an interface for creating contracts. To do this, run:
> var adder_contract = web3.eth.contract(compiled.adder.info.abiDefinition)
undefined
> adder_contract
{
abi: [{
constant: false,
inputs: [{...}, {...}],
name: "add",
outputs: [{...}],
type: "function"
}],
at: function(address, callback),
new: function()
}
Towards the bottom is a function called new
. It is this function that is used to deploy the contract to the blockchain. To do this, define the following callback function:
function tx_callback (e, contract) {
if (!e) {
if (!contract.address) {
console.log("Transaction Hash: " + contract.transactionHash + " waiting to be mined.");
} else {
console.log("Transaction mined at address " + contract.address)
}
}
}
Then, invoke the new
function like so:
> var code = compiled.adder.code
> code
"0x6060604052602b8060106000396000f3606060405260e060020a6000350463771602f78114601a575b005b602435600435016060908152602090f3"
> var primary = web3.eth.accounts[0]
> personal.unlockAccount(primary, 'strong password that no one will guess') // replace with your password
> var adder = adder_contract.new({from: primary, data: code, gas: 1000000}, tx_callback)
Transaction Hash: 0xe8ac2ba57281a8ec274993c605f44c2b854ce40adb1d3069cf7b5210b01dd0e0 waiting to be mined.
In the logs, you will see a similar set of messages to those obtained from sending Ether between accounts. After some delay, the transaction will be mined, and the resulting adder
can be invoked:
> Transaction mined at address 0x783ce9eb8d98cb3554ed34f04751f9665ecb80eb
> parseInt(adder.add(2,3))
5
> parseInt(adder.add(200,3))
203
So, after all of that, we can, with some confidence, asssert that 2+3 and 200+3 are indeed 5 and 203, respectively. As underwhelming as this is, it is not hard to automate essentially everything we did aside from writing the Solidity code, after which the process becomes one of simply building and deploying.
To recreate the environment familiar to us from traditional programming, in which one might program in an external editor, then build and run their program from a shell, we will turn to nodejs.
sudo apt-get install nodejs
npm install web3
Run geth with the arguments below. Don't worry about what all the flags mean right now. If you can't go on without understanding them, just read the information provided with `geth --help`.
$ geth --rpc --rpcapi "eth,web3,personal,net,miner,admin" --networkid 123 console
This will start an Ethereum node, expose the api specified via RPC, and start mining. To connect via Node.js, run nodejs
in another terminal, and execute the following comands:
> var web3 = require('web3')
> var web3 = new web3(new web3.providers.HttpProvider("http://localhost:8545"))
> web3.eth.accounts
[ '0x56e9f2fffc8fde5f0419a7c63e15f6d67ddbd228',
'0x14aaf671e0d9f0dec3555dcf2f572bdb05932319' ]
We now have access to the tools from the geth
console. Importantly, though, we can now use Node.js to use more advanced scripting features, interacting with our filesystem. To start, let's automate some of the commands appearing often, and save it in a file called 'startup.js'. I have included this file in the folder js
. To use them directly, just clone or download the this repository, then in your nodejs session, run .load js/startup.js
.
Before going through the details of each line in the startup.js
file, just observe the result. Instead of doing all of that nonsense from before, we can instead just write the solidity code in an ordinary external text editor, then built and deploy it from a nodejs session.
Save the source for the adder contract in a file adder.sol
.
contract adder {
function add(uint a, uint b) constant returns (uint c) { return a + b; }
}
Then, in a nodejs session,
> .load startup.js
> var adder_contract = build('adder.sol', 'adder')
> var adder = deploy(adder_contract, 0, 'strong password that no one will guess')
> Contract transaction send: TransactionHash: 0x6a142fb14216614b6d82e88d19294c870067ea83ec11c769560fa7f867d886f0 waiting to be mined...
Contract mined! Address: 0x67a62d7001cf8e5e75f5bca773b44e52608c63f6
> parseInt(adder.add(1,2))
3
Now that we finally have some basic setup, let's have a look at a simple token contract [a simplified version of the token contract described here: https://www.ethereum.org/token]. This is actually more powerful than it might seem, since in principal, a token can be used to represent such things as money, property, or voting rights. Importantly, though, is just how distant these applications are from us at the moment. Right now (Spring 2016), we are seeing proof of concepts for applications that do not actually seem anywhere close to practical application. Nevertheless, we are going to try to take this simple notion of a token to build some simple financial services, then get a sense of how a blockchain network handles these.
Save the following in a file called token.sol
.
contract token {
mapping (address => uint256) public balanceOf;
function token (uint initial_supply) {
balanceOf[msg.sender] = initial_supply;
}
function transfer(address _receiver, uint256 _value) {
if (balanceOf[msg.sender] < _value) throw;
if (balanceOf[_receiver] + _value < balanceOf[_receiver]) throw;
balanceOf[msg.sender] -= _value;
balanceOf[_receiver] += _value;
}
}