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