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).
Leave your e-mail:
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
- (basic) Monitor a single address and print a line if balance changes.
- (basic) Read the list of addresses from a file.
- (advanced) Convert balance to decimals in ETH (use bigint library)
- (advanced) Use tokio-timer instead of a loop.
- (advanced) Send an e-mail using lettre.
- (extra) Check `latest - 2` instead of `latest` (wait for confirmations)
- (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
- (basic) Write a contract that stores some data only if you pay the defined fee.
- (basic) Print the state of smart contract using rust-web3.
- (advanced) Create a web server in hyper returning "Hello World".
- (advanced) Allow access to the server only if an address that payed is passed in header: X-Auth
- (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,842