本教程將指導如何使用rust實現發送以太坊交易所需的代碼。
先決條件
我們假設您已經擁有Rust IDE,并且具有Rust編程的合理知識。我們還假設一些關于以太坊的基本知識,并且不涉及以太坊事務的內容等概念。
· Rust入門
· 以太坊101
庫的使用
本教程使用MIT許可的rust-web3庫。要在您的應用程序中使用此庫,請將其添加到Cargo.toml文件中:
[dependencies]
web3 = { git = “https://github.com/tomusdrw/rust-web3” }
然后,您可以將庫添加到您的包中:
extern crate web3;
啟動以太坊節點
我們需要訪問我們可以發送事務的節點。在本教程中,我們使用ganache-cli,它允許您啟動個人以太坊網絡,其中有許多未鎖定的和已資助的帳戶。
從ganache-cli安裝文檔中獲取,要使用npm進行安裝,請使用以下命令:
npm install -g ganache-cli
或者如果你喜歡用yarn命令
yarn global add ganache-cli
安裝后,運行下面的命令以啟動專用以太坊測試網絡:
ganache-cli -d
注意,-d參數指示ganache cli始終以預先填充eth的相同帳戶開始。這在本教程的原始事務部分很有用,因為我們將知道這些帳戶的私鑰。
從節點管理帳戶發送事務
發送事務的最簡單方法是依靠連接的以太坊節點執行事務簽名。這通常是一種不太安全的方法,因為它依賴于在節點上“unlock”帳戶。
use聲明
use web3::futures::Future;
use web3::types::{TransactionRequest, U256};
節點連接
let (_eloop, transport) = web3::transports::Http::new(
“http://localhost:8545”).unwrap();
let web3 = web3::Web3::new(transport);
首先,我們創建一個用于連接節點的傳輸對象。在這個例子中,我們通過http連接到端口8545上的localhost,這是Ganache的默認端口,以及大多數(如果不是全部)以太坊客戶端。
注意:還會返回EventLoop,但這超出了本指南的范圍。
接下來,我們構造一個web3對象,傳入先前創建的傳輸變量,就是這樣!我們現在已連接到以太坊節點!
獲取帳戶詳細信息
GANACHE CLI自動解鎖多個賬戶,并使用100ETH為其提供資金,這對測試很有用。每次重新啟動時帳戶都不同,因此我們需要一種以編程方式獲取帳戶信息的方法:
let accounts = web3.eth().accounts().wait().unwrap();
通過web3.eth()獲得的Eth命名空間包含許多用于與以太坊節點交互的有用函數。通過accounts()獲取管理帳戶列表就是其中之一。它返回異步的未來,所以我們等待任務完成(wait()),并獲得結果(unwrap())。
發送交易
我們定義要通過TransactionRequest結構發送的事務的參數:
let tx = TransactionRequest {
from: accounts[0],
to: Some(accounts[1]),
gas: None,
gas_price: None,
value: Some(U256::from(10000)),
data: None,
nonce: None,
condition: None
};
此結構中的大多數字段都是可選的,如果不手動指定,則使用合理的默認值。當我們發送簡單的ETH轉移事務時,數據字段為空,在此示例中,我們使用默認的gas和gas_price值。我們也沒有指定nonce,因為rust-web3庫默認情況下會向以太坊客戶端查詢最新的nonce值。該條件是rust-web3特定字段,允許您延遲發送事務直到滿足某個條件,例如達到特定的塊編號。
一旦啟動TransactionRequest,它就是一個發送交易的單行:
let tx_hash = web3.eth().send_transaction(tx).wait().unwrap();
TransactionRequest傳遞給Eth命名空間中的send_transaction(。.)函數,該函數返回一個在廣播到網絡后完成的Future。完成后,Promise返回事務哈希Result,然后我們可以unwrap。
全部放在一起。..。..
extern crate web3;
use web3::futures::Future;
use web3::types::{TransactionRequest, U256};
fn main() {
let (_eloop, transport) = web3::transports::Http::new(“http://localhost:8545”).unwrap();
let web3 = web3::Web3::new(transport);
let accounts = web3.eth().accounts().wait().unwrap();
let balance_before = web3.eth().balance(accounts[1], None).wait().unwrap();
let tx = TransactionRequest {
from: accounts[0],
to: Some(accounts[1]),
gas: None,
gas_price: None,
value: Some(U256::from(10000)),
data: None,
nonce: None,
condition: None
};
let tx_hash = web3.eth().send_transaction(tx).wait().unwrap();
let balance_after = web3.eth().balance(accounts[1], None).wait().unwrap();
println!(“TX Hash: {:?}”, tx_hash);
println!(“Balance before: {}”, balance_before);
println!(“Balance after: {}”, balance_after);
}
我們使用web3.eth()。balance(。.)函數來獲取轉移前后收件人帳戶的余額,以證明轉移發生。運行此代碼,您應該看到在事務發送后帳戶[1]余額超過10000 wei 。..成功的以太轉移!
發送原始交易
發送原始事務意味著在Rust端而不是在節點上使用私鑰對事務進行簽名。然后,該節點將此事務轉發到以太坊網絡。
ethereum-tx-sign庫可以幫助我們進行這種脫鏈簽名,但由于缺少共享結構,因此不容易與rust-web3一起使用。在本指南的這一部分中,我將解釋如何讓這些庫很好地協同工作。
使用的其他庫
在構造RawTransaction時,ethereum-tx-sign庫依賴于以太它類型庫。我們還使用十六進制庫將十六進制私鑰轉換為字節。
將這些條目添加到cargo.toml文件中:
ethereum-tx-sign = “0.0.2”
ethereum-types = “0.4”
hex = “0.3.1”
然后,您可以將它們添加到您的包中:
extern crate ethereum_tx_sign;
extern crate ethereum_types;
extern crate hex;
簽署交易
ethereum_tx_sign庫包含一個RawTransaction結構,我們可以在初始化后用它來簽署以太坊事務。初始化是棘手的部分,因為我們需要在rust-web3和ethereum_types結構之間進行轉換。
一些轉換函數可以將由rust-web3函數返回的web3 ::類型的H160(對于以太坊帳戶地址)和U256(對于nonce值)結構轉換為由ethereum-tx-sign預期的theherehere_types:
fn convert_u256(value: web3::types::U256) -》 U256 {
let web3::types::U256(ref arr) = value;
let mut ret = [0; 4];
ret[0] = arr[0];
ret[1] = arr[1];
U256(ret)
}
fn convert_account(value: web3::types::H160) -》 H160 {
let ret = H160::from(value.0);
ret
}
我們現在可以構造一個RawTransaction對象(替換下面的代碼,讓balance_before):
let nonce = web3.eth().transaction_count(accounts[0], None).wait().unwrap();
let tx = RawTransaction {
nonce: convert_u256(nonce),
to: Some(convert_account(accounts[1])),
value: U256::from(10000),
gas_price: U256::from(1000000000),
gas: U256::from(21000),
data: Vec::new()
};
請注意,構造RawTransaction時不會自動計算nonce。我們需要通過調用Eth命名空間中的transaction_count函數來獲取發送帳戶的nonce。隨后需要將此值轉換為RawTransaction期望的格式。
與TransactionRequest結構不同,我們還必須手動提供一些合理的gas和gas_price值。
獲取私鑰
簽名之前,我們需要訪問用于簽名的私鑰。在這個例子中,我們硬編碼ganache中第一個ETH填充帳戶的私鑰(記得以-d參數開頭)。這可以用于測試,但是您不應該在生產環境中公開私鑰!
fn get_private_key() -》 H256 {
// Remember to change the below
let private_key = hex::decode(
“4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7
d21715b23b1d”).unwrap();
return H256(to_array(private_key.as_slice()));
}
fn to_array(bytes: &[u8]) -》 [u8; 32] {
let mut array = [0; 32];
let bytes = &bytes[。.array.len()];
array.copy_from_slice(bytes);
array
}
hex:decode函數將十六進制字符串(確保刪除0x前綴)轉換為Vec 《u8》,但RawTransction的sign函數采用ethereum_types :: H256格式的私鑰。不幸的是,h256在構建期間采用的是[u8;32]而不是vec《t》,因此我們需要進行另一個轉換!
私鑰作為切片傳遞給to_array,然后將此切片轉換為[u8:32]。
簽名
既然我們有了一個以正確格式返回私鑰的函數,那么我們可以通過調用以下命令來對事務進行簽名:
let signed_tx = tx.sign(&get_private_key());
發送交易
簽署后,向以太坊網絡廣播交易也是一條一行程序:
let tx_hash = web3.eth().send_raw_transaction(Bytes::from(signed_tx)).wait().unwrap()
注意,我們必須在這里進行另一次轉換!send_raw_transaction將Bytes值作為參數,而RawTransaction的sign函數返回Vec 《u8》。 幸運的是,這種轉換很容易,因為bytes結構有一個現成的from特性,可以從vec《u8》轉換。
與send_transaction等效項一樣,此函數返回Future,后者又返回一個Result對象,該對象包含完成時廣播事務的事務哈希。
把它們放在一起
extern crate web3;
extern crate ethereum_tx_sign;
extern crate ethereum_types;
extern crate hex;
use web3::futures::Future;
use web3::types::Bytes;
use ethereum_tx_sign::RawTransaction;
use ethereum_types::{H160,H256,U256};
fn main() {
let (_eloop, transport) = web3::transports::Http::new(“http://localhost:8545”).unwrap();
let web3 = web3::Web3::new(transport);
let accounts = web3.eth().accounts().wait().unwrap();
let balance_before = web3.eth().balance(accounts[1], None).wait().unwrap();
let nonce = web3.eth().transaction_count(accounts[0], None).wait().unwrap();
let tx = RawTransaction {
nonce: convert_u256(nonce),
to: Some(convert_account(accounts[1])),
value: U256::from(10000),
gas_price: U256::from(1000000000),
gas: U256::from(21000),
data: Vec::new()
};
let signed_tx = tx.sign(&get_private_key());
let tx_hash = web3.eth().send_raw_transaction(Bytes::from(signed_tx)).wait().unwrap();
let balance_after = web3.eth().balance(accounts[1], None).wait().unwrap();
println!(“TX Hash: {:?}”, tx_hash);
println!(“Balance before: {}”, balance_before);
println!(“Balance after: {}”, balance_after);
}
fn get_private_key() -》 H256 {
let private_key = hex::decode(
“4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d”).unwrap();
return H256(to_array(private_key.as_slice()));
}
fn convert_u256(value: web3::types::U256) -》 U256 {
let web3::types::U256(ref arr) = value;
let mut ret = [0; 4];
ret[0] = arr[0];
ret[1] = arr[1];
U256(ret)
}
fn convert_account(value: web3::types::H160) -》 H160 {
let ret = H160::from(value.0);
ret
}
fn to_array(bytes: &[u8]) -》 [u8; 32] {
let mut array = [0; 32];
let bytes = &bytes[。.array.len()];
array.copy_from_slice(bytes);
array
}
總結
在本教程中,我們學習了如何使用Rust將基本以太網值轉移事務從一個帳戶發送到另一個帳戶。我們解釋了兩種簽名方法:通過解鎖帳戶在節點上簽名,以及在Rust端簽署一個事務。
評論