Interacting with Ethereum using Rust

Tomasz Drwięga

@tomusdrw

Parity Technologies

WIFI: rustfest / Zurich2017

01.10.2017 RustFest, Zurich

What we do at Parity?

  • parity - Ethereum Client
  • pbtc - Bitcoin Client
  • Polkadot - The Internet of Blockchains

 

Everything build with Rust (+ some JS).

Agenda

Part 1: Blocks & Transactions

  • What is blockchain?
  • JSON-RPC APIs
  • rust-web3

Part 2: Smart Contracts

  • Writing contracts in Solidity
  • Smart Contract Events
  • ETHABI

Part 1

Blocks & Transactions

What is Blockchain?

  • Distributed data structure - to organize "transactions"/events
  • Consensus Algorithm - to decide who is allowed to modify that structure
  • Some crypto - to make it secure/tamperproof
  • Incentives - to make it run by itself

List of changes

List of changes

List of changes

Metadata

/ Previous state

Metadata

/ Previous state

Metadata

/ Previous state

Genesis State

Blockchain

Immutable data structure containing all the changes that were applied in any point in time

Blockchain

Hashes - prevent tampering (e.g. KECCAK256)

Signatures - authorize the actions (e.g. ECDSA)

Parent = hash(B0)
Timestamp = 150..000
Number = 1
Hash = hash(B1)
transfer(A, B, 5)
sig(A)
transfer(C, B, 1)
sig(C)
Parent = hash(B2)
Timestamp = 150..000
Number = 2
Hash = hash(B1)
transfer(B, A, 5)
sig(B)

Consensus Algorithm

Who is allowed to create new blocks?

sig(Authority1)
hash(B0)
hash(B1)
sig(Authority2)
hash(B2)
sig(Authority1)

Proof of Authority

We only accept blocks signed by a hardcoded list of authorities.

Blocks need to be signed in turns at most 1 block per 3 seconds.

Consensus Algorithm

Who is allowed to create new blocks?

Difficulty=2
Sol.=0b001..
SolvedBy=A
hash(B0)
hash(B1)
hash(B2)

Proof of Work

We only accept blocks with a solution to a puzzle.

The difficulty of the puzzle can be adjusted to have a stable rate of new blocks.

Difficulty=4
Sol.=0b00001..
SolvedBy=B
Difficulty=3
Sol.=0b0001..
SolvedBy=A

Why would you waste energy to create new blocks?

It's incentivised

=

You get a reward

How does it work?

(Boot) Node 1

Node 2

New Node

Hey! Could you give me all your peers?

How does it work?

(Boot) Node 1

Node 2

New Node

Hey! Send me all them blocks, will ya?

Block 5

Block 4

Block 0

How does it work?

(Boot) Node 1

Node 2

New Node

Hey! I've got a transaction to include in block.

Block 5

Block 5

Block 5

transfer(N, B, 5)
sig(N)

How does it work?

(Boot) Node 1

Node 2

New Node

Block 5

Block 5

Block 5

transfer(N, B, 5)
sig(N)
transfer(N, B, 5)
sig(N)

Cool, I'm mining and will include the tx for a small fee.

How does it work?

(Boot) Node 1

Node 2

New Node

Block 6

Block 5

Block 5

transfer(N, B, 5)
sig(N)
transfer(N, B, 5)
sig(N)
Block 6

Managed to mine new block, here it is guys!

How does it work?

(Boot) Node 1

Node 2

New Node

Block 6

Block 6

Block 6

Canonical Chain

What if two different blocks are produced with the same parent hash?

Which one should you choose?

Block 1

Block 2

Block 3

Block 3

Fork

Canonical Chain

We use "the longest" chain.

Ethereum re-organizes to a chain with the highest difficulty.

Block 1

Block 2

Block 3

Block 3

Block 4

Take away note: The latest state you see can sometimes be reverted - wait for confirmations.

Questions?

Blockchains allow for trustless transactions between multiple parties.

How to query the blockchain?

Running the node

$ parity ui --chain=dev --mode=offline --ws-origins=all
# or 
$ parity ui --chain=dev --network-id=<random-number> --ws-origins=all


$ cd ~/.local/share/io.parity.ethereum/dapps
# ~/Library/Application Support/io.parity.ethereum/dapps
# %AppData%\Parity\Ethereum\dapps
$ git clone --depth=1 -b built https://github.com/tomusdrw/etherdisplay

JSON-RPC

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_getBlockByNumber",
  "params": ["latest", false]
}
# apt install httpie
$ http localhost:8545 jsonrpc=2.0 id=1 method=eth_getBlockByNumber params:='["latest",false]'

# Or with CURL
$ curl localhost:8545 -H "Content-Type:application/json" -X POST --data \
  '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]}'
{
    "id": 1, 
    "jsonrpc": "2.0", 
    "result": {
        "author": "0x05a56e2d52c817161883f50c441c3228cfe54d9f", 
        "difficulty": "0x3ff800000", 
        "extraData": "0x476574682f76312e302e302f6c696e75782f676f312e342e32", 
        "gasLimit": "0x1388", 
        "gasUsed": "0x0", 
        "hash": "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6", 
        "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", 
        "miner": "0x05a56e2d52c817161883f50c441c3228cfe54d9f", 
        "mixHash": "0x969b900de27b6ac6a67742365dd65f55a0526c41fd18e1b16f1a1215c2e66f59", 
        "nonce": "0x539bd4979fef1ec4", 
        "number": "0x1", 
        "parentHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", 
        "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", 
        "sealFields": [
            "0xa0969b900de27b6ac6a67742365dd65f55a0526c41fd18e1b16f1a1215c2e66f59", 
            "0x88539bd4979fef1ec4"
        ], 
        "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", 
        "size": "0x219", 
        "stateRoot": "0xd67e4d450343046425ae4271474353857ab860dbc0a1dde64b41b5cd3a532bf3", 
        "timestamp": "0x55ba4224", 
        "totalDifficulty": "0x7ff800000", 
        "transactions": [], 
        "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", 
        "uncles": []
    }
}

Documentation of all methods:

https://github.com/paritytech/parity/wiki/JSONRPC-eth-module

JSON-RPC

Some useful methods for today.

// Returns a block data 
// and transactions in it
eth_getBlockByNumber(
    <block-number>,
    <include-transactions>,
);


// Returns a balance of an account.
eth_getBalance(
    <address>,
);

How to query the blockchain?

IN RUST!

Rust-Web3

docs.rs/web3

extern crate web3;

use web3::futures::Future;
use web3::types::BlockNumber;

fn main() {
  // Initialize the transport (can use Ipc transport as well)
  let (_eloop, transport) = web3::transports::Http::new(
    "http://localhost:8545",
  ).unwrap();
  let web3 = web3::Web3::new(transport);

  // Invoke the RPC method and deserialize the result
  let block = web3.eth().block_with_txs(
    BlockNumber::Latest.into(),
  ).wait().unwrap();

  // Print the result.
  println!("Latest block: {:?}", block);
}

Rust-Web3

Address: FromStr

extern crate web3;

use web3::futures::Future;

fn main() {
  // Initialize the transport (can use Ipc transport as well)
  let (_eloop, transport) = web3::transports::Http::new(
    "http://localhost:8545",
  ).unwrap();
  let web3 = web3::Web3::new(transport);

  // Invoke the RPC method and deserialize the result
  let address = "0x00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap();
  let nonce = web3.eth().transaction_count(address, None).wait().unwrap();

  // Print the result.
  println!("Number of transactions sent from {:?}: {:?}", address, nonce);
}

Tasks 1

  1. (basic) Monitor a single address and print a line if balance changes.
  2. (basic) Read the list of addresses from a file.
  3. (advanced) Convert balance to decimals in ETH (use bigint library)
  4. (advanced) Use tokio-timer instead of a loop.
  5. (advanced) Send an e-mail using lettre.
  6. (extra) Check `latest - 2` instead of `latest` (wait for confirmations)
  7. (extra) Play a sound (rodio), pitch should depend on the balance.

Notify yourself whenever a balance of some account changes.

https://github.com/tomusdrw/rustfest-2017

Part 2

Smart Contracts

Solidity

Smart Contract is like an agent behind a regular address.

pragma solidity ^0.4.11;

contract Burn {
    uint256 public value;
    address public owner;
    
    function Burn() public payable {
        value = msg.value;
        owner = msg.sender;
    }
}

Compile Program:
Solidity -> EVM

Run Program:
Transactions (ABI + RLP)

Smart Contracts @ Ethereum

pragma solidity ^0.4.11;

contract Parity {
    uint256 public value;
    address public owner;
    
    function export() payable {
        value += msg.value;
        owner = msg.sender;
    }
}
0xc0de15de4d... at 0x123..456
binary at /usr/bin/parity
$ parity export blocks
from:      0x456..def
to:        0x123..456
value:     5 * 10^18 wei (5 ETH)
gas:       100,000
gasPrice:  4 * 10^9 wei (4 shannon)
nonce:     0
data:      0x444...
           ("call function export")


0x123456... (Transaction RLP)

Lock ether

(For some time)

pragma solidity ^0.4.17;
contract Lock {
    uint256 public value;
    address public owner;
    
    function Lock() public payable {
        value = msg.value;
        owner = msg.sender;
    }

    function withdraw() public {
        require(msg.sender == owner);
        msg.sender.transfer(value);
    }
}

Lock ether

(For many users)

pragma solidity ^0.4.17;
contract Lock {

    mapping(address => uint256) public locked;

    function lock() public payable {
        locked[msg.sender] = msg.value;
    }
    
    function unlock() public {
        var value = locked[msg.sender];
        require(value != 0);
        
        delete locked[msg.sender];
        msg.sender.transfer(value);
    }
}

Can you spot a bug in this contract?

Let's talk to contracts

IN RUST!

Contract API

extern crate web3;

use web3::futures::Future;
use web3::contract::{Contract, Options};
use web3::types::{Address, U256};

fn main() {
    let (_eloop, http) = web3::transports::Http::new(
        "http://localhost:8545"
    ).unwrap();
    let web3 = web3::Web3::new(http);

    // The contract address.
    let address: Address = "0x00a329c0648769a73afac7f9381e08fb43dbea72"
        .parse().unwrap();

    // Access the contract
    let contract = Contract::from_json(web3.eth(), address, include_bytes!("./abi.json")).unwrap();

    // Query the contract instance
    let result = contract.query("locked", (address, ), None, Options::default(), None);
    let balance_of: U256 = result.wait().unwrap();
    assert_eq!(balance_of, 0.into());
}

Contract API

extern crate web3;
extern crate rustc_hex;

use web3::futures::Future;
use web3::contract::{Contract, Options};
use web3::types::{Address, U256};
use rustc_hex::FromHex;

fn main() {
    let (_eloop, http) = web3::transports::Http::new("http://localhost:8545").unwrap();
    let web3 = web3::Web3::new(http);

    // The account you deploy from.
    let my_account: Address = "0x00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap();
    // Get the contract bytecode for instance from Solidity compiler.
    let bytecode = include_str!("./bytecode.bin").from_hex().unwrap();
    // Deploying a contract
    let contract = Contract::deploy(web3.eth(), include_bytes!("./abi.json")).unwrap()
        .confirmations(0)
        .options(Options::with(|mut opt| opt.value = Some(5.into())))
        .execute(bytecode, (), my_account)
        .expect("Correct parameters are passed to the constructor.")
        .wait()
        .unwrap();

    // Query the contract instance
    let result = contract.query("locked", (my_account, ), None, Options::default(), None);
    let balance_of: U256 = result.wait().unwrap();
    assert_eq!(balance_of, 0.into());
}

Contract Deployment

You can also deploy using Parity and just access the contract with Rust.

Web Server

Hyper

A server in hyper

extern crate hyper;
extern crate web3;

use web3::futures::{self, Future};
use hyper::header::ContentLength;
use hyper::server::{Http, Request, Response, Service};

struct HelloWorld;
impl Service for HelloWorld {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;

    fn call(&self, _req: Request) -> Self::Future {
        let phrase = "Hello World!";
        Box::new(futures::future::ok(
            Response::new()
                .with_header(ContentLength(phrase.len() as u64))
                .with_body(phrase)
        ))
    }
}
fn main() {
    let addr = "127.0.0.1:3000".parse().unwrap();
    let server = Http::new().bind(&addr, || Ok(HelloWorld)).unwrap();
    server.run().unwrap();
}

hyper + web3

extern crate hyper;
extern crate web3;
use std::sync::Arc;
use hyper::header::ContentLength;
use hyper::server::{Http, Request, Response, Service};
use web3::futures::Future;
use web3::types::Address;

#[derive(Clone)]
struct HelloBlockchain { web3: Arc<web3::Web3<web3::transports::Http>> }
impl Service for HelloBlockchain {
    type Request = Request;
    type Response = Response;
    type Error = hyper::Error;
    type Future = Box<Future<Item=Self::Response, Error=Self::Error>>;

    fn call(&self, _req: Request) -> Self::Future {
        let address: Address = "0x00a329c0648769a73afac7f9381e08fb43dbea72".parse().unwrap();
        Box::new(self.web3.eth().balance(address, None)
            .then(|balance| {
                let body = format!("Balance: {:?}", balance);
                Ok(Response::new()
                    .with_header(ContentLength(body.len() as u64))
                    .with_body(body))
            }))
    }
}

hyper + web3

fn main() {
    let (_eloop, http) = web3::transports::Http::new("http://localhost:8545").unwrap();
    let web3 = Arc::new(web3::Web3::new(http));

    let addr = "127.0.0.1:3000".parse().unwrap();
    let handler = HelloBlockchain { web3 };
    let server = Http::new().bind(&addr, move || Ok(handler.clone())).unwrap();
    server.run().unwrap();
}

Tasks 2

  1. (basic) Write a contract that stores some data only if you pay the defined fee.
  2. (basic) Print the state of smart contract using rust-web3.
  3. (advanced) Create a web server in hyper returning "Hello World".
  4. (advanced) Allow access to the server only if an address that payed is passed in header: X-Auth
  5. (extra) Store hash in the contract and check if sha3(X-Auth) matches the data in contract.

Create a WebServer and limit the access to it only for users who payed.

https://github.com/tomusdrw/rustfest-2017

Interacting with Ethereum using Rust

By Tomasz Drwięga

Interacting with Ethereum using Rust

Workshop for RustFest2017

  • 1,852