Skip to main content

RS IoT Blockchain Demonstrators Part 4: Blockchain Network

Main25_ee73fbb2934c0cb4128dffe2723449559ac0a4e1.jpg

Creating a private blockchain network and deploying distributed applications to it.

This series of posts looks at the design and build of a set of demonstrators for the bi-annual Electronica trade fair and conference, which show how blockchain technology can be used to create a secure, decentralised data platform and more for the Internet of Things.

In this post, we take a look at how a private Ethereum network was established that uses proof-of-authority (PoA) to secure it, following which distributed applications (DApps) were deployed to this new network that will support the four different IoT use cases.

Proof-of-Authority

Blockchain networks are peer-to-peer and as such there are no central servers. However, they’re kept secure by cryptography and nodes which “seal” blocks that contain one or more transactions. With most public blockchain networks the right to seal blocks is earned via proof-of-work, whereby a node elects to operate as a miner and performs some highly computationally intensive task.

In a private blockchain network, it would make little sense to have nodes needlessly wasting energy, performing some ultimately futile task in order to earn the right to seal new blocks. Indeed, many public networks are also looking to more environmentally friendly and frankly, more sensible alternatives, with one of these mechanisms being proof-of-authority.

With PoA all we need to do is, at the point of blockchain initialisation, specify the address of the accounts that are authorised to seal new blocks. These accounts will then be configured on network nodes that are set up to run as a miner. In our case, this will be just a single account and a single node.

Node and accounts

There are a number of Ethereum node implementations available and we decided to use the Go language Ethereum client, geth. With this installed we created accounts on each node with:

geth --datadir /data/bc/ account new

The address of each new account would then be required when it came to initialising the network.

The genesis block

{
  "config": {
    "chainId": 555,
    "homesteadBlock": 1,
    "eip150Block": 2,
    "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "eip155Block": 3,
    "eip158Block": 3,
    "byzantiumBlock": 4,
    "clique": {
      "period": 5,
      "epoch": 30000
    }
  },
  "nonce": "0x0",
  "timestamp": "0x5bb8bea5",
  "extraData": "0x00000000000000000000000000000000000000000000000000000000000000004ae82f919e03741ce8e69c108ea25438b218d6680000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "gasLimit": "0x59A5380",
  "difficulty": "0x1",
  "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": {
    "4ae82f919e03741ce8e69c108ea25438b218d668": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    },
    "478f2c61e4eb1c09596a7af138b94ae598de32c0": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    },
    "2bc19750cdf3991d0a27d45304276cd4d71f6975": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    },
    "3adb96d017239642e7b87ea816dd6171581f1d3a": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    },
    "7b0975783ee29b5416acac1d47b29908a06322fe": {
      "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
    }
  },
  "number": "0x0",
  "gasUsed": "0x0",
  "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

A blockchain has to start somewhere and it is with block 0, the genesis block. This is where we specify basic network parameters, including with PoA the addresses of the accounts that are authorised to seal new blocks, along with those which we would like to preallocate funds to.

Above can be seen the JSON configuration for our genesis block. The chainId is important and is used to uniquely identify our network. The address embedded in the extraData parameter is that of the account that we wish to authorise to seal new blocks.

A period of 5 means that we would like to seal a new block every 5 seconds. This may seem like a long time to wait for pending transactions to be committed and when compared with e.g. pub/sub messaging or a traditional database, it is. However, in public networks, it can at times take hours or days, even. What’s important to remember is that this is not intended to be instantaneous and any real-time requirements should be met via other mechanisms, e.g. MQTT pub/sub messaging. Relying instead on blockchain to provide a long-term, cryptographically secure, immutable record.

Finally, the addresses and balances specified in alloc are used to pre-allocate funds.

Each node that wishes to participate in our network would then initialise the genesis block with:

geth --datadir /data/bc init iotbc.json

As nodes are brought up and start to communicate with each other, they will not form or join a network unless they have been initialised with the exact same genesis block.

Funds?

Despite operating a private blockchain network and not being interested in the accumulation of wealth, we still need to have funds or Ether, since this is fundamental to the operation of Ethereum networks. The reason is quite simple and that, if transactions were entirely free, it would be trivial to cause denial of service, with the blockchain database growing to a great size in no time at all — filled with bogus transactions and all nodes participating in the network spending all of their time verifying said transactions. Hence, transactions must have a cost.

If a transaction cost were to be specified in Ether this would directly link this to the highly dynamic — in public networks, at least — value of the cryptocurrency. So instead we have a unit called gas, that is decoupled from Ether and which is much more stable. Of course, a given amount of gas will still have an Ether cost and so we require funds in order to be able to transact.

Just to be clear, we can allocate as much or as little Ether to accounts as we like when we initialise the blockchain network. Furthermore, those accounts could act as banks of a sort, subsequently distributing Ether to other accounts, as and when required. So for example, in a production network that is being operated by a service provider, they may hold all the Ether initially, then over time allocate it to users according to a service level agreement, subscription or credits purchased etc.

Just to be absolutely clear, this currency only has value on our private network and no real/fiat currency is required in order to obtain or allocate it.

Distributed applications

The Ethereum platform provides much more than simply a means of logging transactions and its distributed applications, aka smart contracts, open up a whole world of possibilities. In short, these are applications developed in a language called Solidity, which itself is based on JavaScript, and that is mined onto the blockchain. In other words, the blockchain becomes a sort of distributed “app store”, where applications can take full advantage of its capabilities.

Just like an end user account, smart contracts have an address and this is generated at the point they are deployed and mined onto the blockchain. If we know the address and its application binary interface (ABI) definition, we can interact with the smart contract. Reading state, e.g. a variable, is free since it does not update the blockchain. However, any operation that does involve updating the blockchain has a gas cost associated with it and the more complex this is, the higher the cost.

Smart contracts are a whole topic unto themselves and here we will just consider the very basics.

IoT smart contracts

We will have one smart contract per use case, although there is no reason why you could not have a single smart contract that has a variety of functions to support different use cases.

Our smart contracts are incredibly simple and each allows us to set and get the value of a single variable. Hence they do not provide direct access to historical values, but of course, these are preserved in the blockchain and it is possible to read variable state at any given block number.

An alternative would be to use arrays and each time add a new value to the array.

Car Crash

pragma solidity ^0.4.10;

contract CarCrash {
    address owner;
    address insurer;
    int impact;

    function CarCrash() {
        owner = 0x2BC19750cdf3991D0A27d45304276Cd4D71F6975;
        insurer = 0x4aE82F919e03741cE8E69C108ea25438b218d668;
    }

    function setImpact(int _impact) {
        require(msg.sender == owner);
        impact = _impact;
    }

    function getImpact() constant returns (int) {
        require(msg.sender == insurer);
        return impact;
    }
}

Above is the Solidity code for the CarCrash smart contract. In this, we define two address variables, with the names owner and insurer, which are set to the addresses of the accounts configured on the Car Crash demonstrator unit and Miner. We then define functions that allow us to set and get a variable name impact, which only the owner account can set and the insurer account is permitted to get.

We don’t have to configure such constraints on these functions and of course an insurer would likely have an account, or actually, a number of accounts configured on a system other than a miner. This is really just to give an idea of the sort of things that are possible.

In practice, a smart contract may be a great deal more complex and possibly even depend upon and interact with, other smart contracts.

Machine Failure

The MachineFailure contract is pretty much the same, only the functions are called setLastFailure and getLastFailure, which operate on the variable lastFailure. Also, the value for the owner address is different and that of the account configured on the Machine Failure demo unit.

Temperature Alert & LeakKiller

The changes here are very similar, with contracts instead named TemperatureAlert and LeakKiller, and appropriately named functions and variables.

Compiling and deploying

var CarCrash = artifacts.require("./CarCrash.sol");
var MachineFailure = artifacts.require("./MachineFailure.sol");
var TemperatureAlert = artifacts.require("./TemperatureAlert.sol");
var LeakKiller = artifacts.require("./LeakKiller.sol");

module.exports = function(deployer) {
  deployer.deploy(CarCrash)
    .then(() => console.log(CarCrash.address));
  deployer.deploy(MachineFailure)
    .then(() => console.log(MachineFailure.address));
  deployer.deploy(TemperatureAlert)
    .then(() => console.log(TemperatureAlert.address));
  deployer.deploy(LeakKiller)
    .then(() => console.log(LeakKiller.address));

The contracts were compiled and subsequently deployed to the blockchain using Truffle, a development environment, test framework and more for smart contracts. To use this we:

  1. Created a file containing each smart contract
  2. Created a simple file called 2_deploy_contracts.js (see above)
  3. Edited the truffle.js file to provide details for our iotbc network (see below)
module.exports = {
  networks: {
    iotbc: {
      host: "127.0.0.1",
      port: "8545",
      network_id: "555"
    }
  }
};

With this, we could then simply type truffle compile to compile the smart contracts. Then assuming that our Ethereum node was running and the configured account had Ether, we could type truffle migrate to deploy these and have them mined onto the blockchain. Why do we need Ether? Since we’re actually writing to the blockchain we need to spend gas.

As each smart contract is deployed its address is logged to the console and this must be noted.

ABI

  "abi": [
    {
      "inputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "constant": false,
      "inputs": [
        {
          "name": "_impact",
          "type": "int256"
        }
      ],
      "name": "setImpact",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "constant": true,
      "inputs": [],
      "name": "getImpact",
      "outputs": [
        {
          "name": "",
          "type": "int256"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    }
  ]

In order to interact with a smart contract, we require not only its address but its application binary interface. Above can be seen the ABI for the CarCrash smart contract. With both of these, we can then write the user application in whatever language we like, provided that it’s possible to communicate over TCP via localhost with the Ethereum node.

Summing up

So to recap we’ve:

  • Created a private blockchain network that doesn’t needlessly waste energy

  • Authorised a miner to seal blocks (in reality there could be many)

  • Pre-allocated funds to the account on the miner plus demo units

  • Created four smart contracts

  • Compiled the contracts and deployed them to the blockchain

  • Noted the address and ABI for each smart contract

In the next post we’ll take a look at the Python application code which reads sensors and buttons, drives the Ethereum LEDs and, of course, invokes functions in our smart contracts in order to update the blockchain.

Other posts in this series

The design and build of the demonstrators is covered over the course of a total of five posts:

Andrew Back

Find how our connected stand was built

Open source (hardware and software!) advocate, Treasurer and Director of the Free and Open Source Silicon Foundation, organiser of Wuthering Bytes technology festival and founder of the Open Source Hardware User Group.