Interacting with Ethereum using Rust

Tomasz Drwięga

@tomusdrw


25 May 2018 RustFest, Paris

André Silva

@andrebeat

Parity Technologies

What we do at Parity?

  • parity - Ethereum Client
  • pbtc - Bitcoin Client
  • WASM
  • Whisper
  • IPFS/libp2p
  • Polkadot - The Internet of Blockchains
  • Substrate

 

Everything built 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 3: Rust Smart Contracts

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

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

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

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>,
);

Tasks 0

  1. Download and install parity client (link)
  2. Launch parity using:
    parity --jsonrpc-apis all --chain dev ui
  3. Explore the JSON-RPC API (link)
  4. Use curl to query the API
    1. Retrieve the latest block
    2. Retrieve the balance of your account

Setup and experiment with the parity client

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 {
    struct Balance {
        uint256 value;
    }

    mapping(address => Balance) public locked;

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

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

Part 3

Smart Contracts

IN RUST!

Interacting with Ethereum using Rust

By Tomasz Drwięga

Interacting with Ethereum using Rust

Workshop for RustFest 2018

  • 1,341