Returning a `PendingTransaction` after sending a tx (#107)
* feat(providers): return a PendingTransaction from send_tx calls * feat(providers): expose the internal provider to all middlewares * fix(middleware): use the returned PendingTx instead of using a hash * fix(contract): use the pending tx returned value Note1: To support that, we need to clone the tx when sending in order to make lifetimes work out Note2: Multicall does not support that feature * fix(ethers): adjust examples * chore: fix provider test * chore: fix celo test BREAKING CHANGE
This commit is contained in:
parent
5b7578296b
commit
3a2fd3e814
|
@ -1,9 +1,9 @@
|
||||||
use super::base::{decode_fn, AbiError};
|
use super::base::{decode_fn, AbiError};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Detokenize, Function, InvalidOutputType},
|
abi::{Detokenize, Function, InvalidOutputType},
|
||||||
types::{Address, BlockNumber, Bytes, TransactionRequest, TxHash, U256},
|
types::{Address, BlockNumber, Bytes, TransactionRequest, U256},
|
||||||
};
|
};
|
||||||
use ethers_providers::Middleware;
|
use ethers_providers::{Middleware, PendingTransaction};
|
||||||
|
|
||||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
|
@ -126,9 +126,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs and broadcasts the provided transaction
|
/// Signs and broadcasts the provided transaction
|
||||||
pub async fn send(self) -> Result<TxHash, ContractError<M>> {
|
pub async fn send(&self) -> Result<PendingTransaction<'_, M::Provider>, ContractError<M>> {
|
||||||
self.client
|
self.client
|
||||||
.send_transaction(self.tx, self.block)
|
.send_transaction(self.tx.clone(), self.block)
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)
|
.map_err(ContractError::MiddlewareError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use super::{
|
||||||
|
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
|
abi::{Abi, Detokenize, Error, EventExt, Function, Tokenize},
|
||||||
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest, TxHash},
|
types::{Address, Filter, NameOrAddress, Selector, TransactionRequest},
|
||||||
};
|
};
|
||||||
use ethers_providers::{Middleware, PendingTransaction};
|
use ethers_providers::Middleware;
|
||||||
|
|
||||||
use rustc_hex::ToHex;
|
use rustc_hex::ToHex;
|
||||||
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
|
@ -90,11 +90,12 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
|
||||||
/// .await?;
|
/// .await?;
|
||||||
///
|
///
|
||||||
/// // Non-constant methods are executed via the `send()` call on the method builder.
|
/// // Non-constant methods are executed via the `send()` call on the method builder.
|
||||||
/// let tx_hash = contract
|
/// let call = contract
|
||||||
/// .method::<_, H256>("setValue", "hi".to_owned())?.send().await?;
|
/// .method::<_, H256>("setValue", "hi".to_owned())?;
|
||||||
|
/// let pending_tx = call.send().await?;
|
||||||
///
|
///
|
||||||
/// // `await`ing on the pending transaction resolves to a transaction receipt
|
/// // `await`ing on the pending transaction resolves to a transaction receipt
|
||||||
/// let receipt = contract.pending_transaction(tx_hash).confirmations(6).await?;
|
/// let receipt = pending_tx.confirmations(6).await?;
|
||||||
///
|
///
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
@ -283,8 +284,4 @@ impl<M: Middleware> Contract<M> {
|
||||||
pub fn client(&self) -> &M {
|
pub fn client(&self) -> &M {
|
||||||
&self.client
|
&self.client
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, M::Provider> {
|
|
||||||
self.client.pending_transaction(tx_hash)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,16 +35,14 @@ impl<M: Middleware> Deployer<M> {
|
||||||
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
|
/// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
|
||||||
/// struct at the deployed contract's address.
|
/// struct at the deployed contract's address.
|
||||||
pub async fn send(self) -> Result<Contract<M>, ContractError<M>> {
|
pub async fn send(self) -> Result<Contract<M>, ContractError<M>> {
|
||||||
let tx_hash = self
|
let pending_tx = self
|
||||||
.client
|
.client
|
||||||
.send_transaction(self.tx, Some(self.block))
|
.send_transaction(self.tx, Some(self.block))
|
||||||
.await
|
.await
|
||||||
.map_err(ContractError::MiddlewareError)?;
|
.map_err(ContractError::MiddlewareError)?;
|
||||||
|
|
||||||
// TODO: Should this be calculated "optimistically" by address/nonce?
|
// TODO: Should this be calculated "optimistically" by address/nonce?
|
||||||
let receipt = self
|
let receipt = pending_tx
|
||||||
.client
|
|
||||||
.pending_transaction(tx_hash)
|
|
||||||
.confirmations(self.confs)
|
.confirmations(self.confs)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| ContractError::ContractNotDeployed)?;
|
.map_err(|_| ContractError::ContractNotDeployed)?;
|
||||||
|
|
|
@ -61,7 +61,7 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
/// use ethers::{
|
/// use ethers::{
|
||||||
/// abi::Abi,
|
/// abi::Abi,
|
||||||
/// contract::{Contract, Multicall},
|
/// contract::{Contract, Multicall},
|
||||||
/// providers::{Middleware, Http, Provider},
|
/// providers::{Middleware, Http, Provider, PendingTransaction},
|
||||||
/// types::{Address, H256, U256},
|
/// types::{Address, H256, U256},
|
||||||
/// };
|
/// };
|
||||||
/// use std::{convert::TryFrom, sync::Arc};
|
/// use std::{convert::TryFrom, sync::Arc};
|
||||||
|
@ -110,7 +110,7 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
|
||||||
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
|
/// // `await`ing the `send` method waits for the transaction to be broadcast, which also
|
||||||
/// // returns the transaction hash
|
/// // returns the transaction hash
|
||||||
/// let tx_hash = multicall.send().await?;
|
/// let tx_hash = multicall.send().await?;
|
||||||
/// let _tx_receipt = client.pending_transaction(tx_hash).await?;
|
/// let _tx_receipt = PendingTransaction::new(tx_hash, &client).await?;
|
||||||
///
|
///
|
||||||
/// // you can also query ETH balances of multiple addresses
|
/// // you can also query ETH balances of multiple addresses
|
||||||
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
|
/// let address_1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".parse::<Address>()?;
|
||||||
|
@ -345,7 +345,9 @@ impl<M: Middleware> Multicall<M> {
|
||||||
let contract_call = self.as_contract_call();
|
let contract_call = self.as_contract_call();
|
||||||
|
|
||||||
// Broadcast transaction and return the transaction hash
|
// Broadcast transaction and return the transaction hash
|
||||||
let tx_hash = contract_call.send().await?;
|
// TODO: Can we make this return a PendingTransaction directly instead?
|
||||||
|
// Seems hard due to `returns a value referencing data owned by the current function`
|
||||||
|
let tx_hash = *contract_call.send().await?;
|
||||||
|
|
||||||
Ok(tx_hash)
|
Ok(tx_hash)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod eth_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ethers::{
|
use ethers::{
|
||||||
contract::Multicall,
|
contract::Multicall,
|
||||||
providers::{Http, Middleware, Provider, StreamExt},
|
providers::{Http, Middleware, PendingTransaction, Provider, StreamExt},
|
||||||
types::{Address, U256},
|
types::{Address, U256},
|
||||||
utils::Ganache,
|
utils::Ganache,
|
||||||
};
|
};
|
||||||
|
@ -51,13 +51,9 @@ mod eth_tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let calldata = contract_call.calldata().unwrap();
|
let calldata = contract_call.calldata().unwrap();
|
||||||
let gas_estimate = contract_call.estimate_gas().await.unwrap();
|
let gas_estimate = contract_call.estimate_gas().await.unwrap();
|
||||||
let tx_hash = contract_call.send().await.unwrap();
|
let pending_tx = contract_call.send().await.unwrap();
|
||||||
let tx = client.get_transaction(tx_hash).await.unwrap().unwrap();
|
let tx = client.get_transaction(*pending_tx).await.unwrap().unwrap();
|
||||||
let tx_receipt = client
|
let tx_receipt = pending_tx.await.unwrap();
|
||||||
.get_transaction_receipt(tx_hash)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(last_sender.clone().call().await.unwrap(), client2.address());
|
assert_eq!(last_sender.clone().call().await.unwrap(), client2.address());
|
||||||
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
assert_eq!(get_value.clone().call().await.unwrap(), "hi");
|
||||||
assert_eq!(tx.input, calldata);
|
assert_eq!(tx.input, calldata);
|
||||||
|
@ -88,6 +84,8 @@ mod eth_tests {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +97,7 @@ mod eth_tests {
|
||||||
let contract = deploy(client.clone(), abi, bytecode).await;
|
let contract = deploy(client.clone(), abi, bytecode).await;
|
||||||
|
|
||||||
// make a call with `client2`
|
// make a call with `client2`
|
||||||
let _tx_hash = contract
|
let _tx_hash = *contract
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
|
@ -143,13 +141,11 @@ mod eth_tests {
|
||||||
|
|
||||||
// and we make a few calls
|
// and we make a few calls
|
||||||
for i in 0..num_calls {
|
for i in 0..num_calls {
|
||||||
let tx_hash = contract
|
let call = contract
|
||||||
.method::<_, H256>("setValue", i.to_string())
|
.method::<_, H256>("setValue", i.to_string())
|
||||||
.unwrap()
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
let pending_tx = call.send().await.unwrap();
|
||||||
|
let _receipt = pending_tx.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..num_calls {
|
for i in 0..num_calls {
|
||||||
|
@ -180,13 +176,14 @@ mod eth_tests {
|
||||||
let contract = deploy(client, abi, bytecode).await;
|
let contract = deploy(client, abi, bytecode).await;
|
||||||
|
|
||||||
// make a call without the signer
|
// make a call without the signer
|
||||||
let tx_hash = contract
|
let _receipt = contract
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let _receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
|
||||||
let value: String = contract
|
let value: String = contract
|
||||||
.method::<_, String>("getValue", ())
|
.method::<_, String>("getValue", ())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -313,7 +310,9 @@ mod eth_tests {
|
||||||
|
|
||||||
// broadcast the transaction and wait for it to be mined
|
// broadcast the transaction and wait for it to be mined
|
||||||
let tx_hash = multicall_send.send().await.unwrap();
|
let tx_hash = multicall_send.send().await.unwrap();
|
||||||
let _tx_receipt = client4.pending_transaction(tx_hash).await.unwrap();
|
let _tx_receipt = PendingTransaction::new(tx_hash, client.provider())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Do another multicall to check the updated return values
|
// Do another multicall to check the updated return values
|
||||||
// The `getValue` calls should return the last value we set in the batched broadcast
|
// The `getValue` calls should return the last value we set in the batched broadcast
|
||||||
|
@ -385,14 +384,14 @@ mod celo_tests {
|
||||||
assert_eq!(value, "initial value");
|
assert_eq!(value, "initial value");
|
||||||
|
|
||||||
// make a state mutating transaction
|
// make a state mutating transaction
|
||||||
let tx_hash = contract
|
let call = contract
|
||||||
.method::<_, H256>("setValue", "hi".to_owned())
|
.method::<_, H256>("setValue", "hi".to_owned())
|
||||||
.unwrap()
|
.unwrap();
|
||||||
|
let pending_tx = call
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let receipt = contract.pending_transaction(tx_hash).await.unwrap();
|
let _receipt = pending_tx.await.unwrap();
|
||||||
assert_eq!(receipt.status.unwrap(), 1.into());
|
|
||||||
|
|
||||||
let value: String = contract
|
let value: String = contract
|
||||||
.method("getValue", ())
|
.method("getValue", ())
|
||||||
|
|
|
@ -6,7 +6,7 @@ pub use linear::LinearGasPrice;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers_core::types::{BlockNumber, TransactionRequest, TxHash, U256};
|
use ethers_core::types::{BlockNumber, TransactionRequest, TxHash, U256};
|
||||||
use ethers_providers::{interval, FromErr, Middleware, StreamExt};
|
use ethers_providers::{interval, FromErr, Middleware, PendingTransaction, StreamExt};
|
||||||
use futures_util::lock::Mutex;
|
use futures_util::lock::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{pin::Pin, time::Instant};
|
use std::{pin::Pin, time::Instant};
|
||||||
|
@ -97,8 +97,8 @@ where
|
||||||
&self,
|
&self,
|
||||||
tx: TransactionRequest,
|
tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, Self::Error> {
|
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
|
||||||
let tx_hash = self
|
let pending_tx = self
|
||||||
.inner()
|
.inner()
|
||||||
.send_transaction(tx.clone(), block)
|
.send_transaction(tx.clone(), block)
|
||||||
.await
|
.await
|
||||||
|
@ -106,9 +106,9 @@ where
|
||||||
|
|
||||||
// insert the tx in the pending txs
|
// insert the tx in the pending txs
|
||||||
let mut lock = self.txs.lock().await;
|
let mut lock = self.txs.lock().await;
|
||||||
lock.push((tx_hash, tx, Instant::now(), block));
|
lock.push((*pending_tx, tx, Instant::now(), block));
|
||||||
|
|
||||||
Ok(tx_hash)
|
Ok(pending_tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,7 +188,7 @@ where
|
||||||
.send_transaction(replacement_tx.clone(), priority)
|
.send_transaction(replacement_tx.clone(), priority)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(tx_hash) => tx_hash,
|
Ok(tx_hash) => *tx_hash,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.to_string().contains("nonce too low") {
|
if err.to_string().contains("nonce too low") {
|
||||||
// ignore "nonce too low" errors because they
|
// ignore "nonce too low" errors because they
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{GasOracle, GasOracleError};
|
use super::{GasOracle, GasOracleError};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers_core::types::*;
|
use ethers_core::types::*;
|
||||||
use ethers_providers::{FromErr, Middleware};
|
use ethers_providers::{FromErr, Middleware, PendingTransaction};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -60,7 +60,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, Self::Error> {
|
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
|
||||||
if tx.gas_price.is_none() {
|
if tx.gas_price.is_none() {
|
||||||
tx.gas_price = Some(self.get_gas_price().await?);
|
tx.gas_price = Some(self.get_gas_price().await?);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use ethers_core::types::*;
|
use ethers_core::types::*;
|
||||||
use ethers_providers::{FromErr, Middleware};
|
use ethers_providers::{FromErr, Middleware, PendingTransaction};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, Self::Error> {
|
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
|
||||||
if tx.nonce.is_none() {
|
if tx.nonce.is_none() {
|
||||||
tx.nonce = Some(self.get_transaction_count_with_manager(block).await?);
|
tx.nonce = Some(self.get_transaction_count_with_manager(block).await?);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
types::{
|
types::{
|
||||||
Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest,
|
Address, BlockNumber, Bytes, NameOrAddress, Signature, Transaction, TransactionRequest,
|
||||||
TxHash, U256,
|
U256,
|
||||||
},
|
},
|
||||||
utils::keccak256,
|
utils::keccak256,
|
||||||
};
|
};
|
||||||
use ethers_providers::{FromErr, Middleware};
|
use ethers_providers::{FromErr, Middleware, PendingTransaction};
|
||||||
use ethers_signers::Signer;
|
use ethers_signers::Signer;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -44,11 +44,11 @@ use thiserror::Error;
|
||||||
///
|
///
|
||||||
/// // ...and sign transactions
|
/// // ...and sign transactions
|
||||||
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
|
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
|
||||||
/// let tx_hash = client.send_transaction(tx, None).await?;
|
/// let pending_tx = client.send_transaction(tx, None).await?;
|
||||||
///
|
///
|
||||||
/// // You can `await` on the pending transaction to get the receipt with a pre-specified
|
/// // You can `await` on the pending transaction to get the receipt with a pre-specified
|
||||||
/// // number of confirmations
|
/// // number of confirmations
|
||||||
/// let receipt = client.pending_transaction(tx_hash).confirmations(6).await?;
|
/// let receipt = pending_tx.confirmations(6).await?;
|
||||||
///
|
///
|
||||||
/// // You can connect with other wallets at runtime via the `with_signer` function
|
/// // You can connect with other wallets at runtime via the `with_signer` function
|
||||||
/// let wallet2: LocalWallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
|
/// let wallet2: LocalWallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
|
||||||
|
@ -242,7 +242,7 @@ where
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, Self::Error> {
|
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
|
||||||
if let Some(ref to) = tx.to {
|
if let Some(ref to) = tx.to {
|
||||||
if let NameOrAddress::Name(ens_name) = to {
|
if let NameOrAddress::Name(ens_name) = to {
|
||||||
let addr = self
|
let addr = self
|
||||||
|
|
|
@ -27,7 +27,7 @@ async fn using_gas_oracle() {
|
||||||
.value(10000);
|
.value(10000);
|
||||||
let tx_hash = provider.send_transaction(tx, None).await.unwrap();
|
let tx_hash = provider.send_transaction(tx, None).await.unwrap();
|
||||||
|
|
||||||
let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap();
|
let tx = provider.get_transaction(*tx_hash).await.unwrap().unwrap();
|
||||||
assert_eq!(tx.gas_price, expected_gas_price);
|
assert_eq!(tx.gas_price, expected_gas_price);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ async fn nonce_manager() {
|
||||||
.send_transaction(TransactionRequest::pay(address, 100u64), None)
|
.send_transaction(TransactionRequest::pay(address, 100u64), None)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
tx_hashes.push(tx);
|
tx_hashes.push(*tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sleep a bit to ensure there's no flakiness in the test
|
// sleep a bit to ensure there's no flakiness in the test
|
||||||
|
|
|
@ -58,9 +58,10 @@ async fn test_send_transaction() {
|
||||||
|
|
||||||
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
let balance_before = client.get_balance(client.address(), None).await.unwrap();
|
||||||
let tx = TransactionRequest::pay(client.address(), 100);
|
let tx = TransactionRequest::pay(client.address(), 100);
|
||||||
let tx_hash = client.send_transaction(tx, None).await.unwrap();
|
|
||||||
let _receipt = client
|
let _receipt = client
|
||||||
.pending_transaction(tx_hash)
|
.send_transaction(tx, None)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
.confirmations(3)
|
.confirmations(3)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -58,7 +58,6 @@ mod tests {
|
||||||
|
|
||||||
// the base provider
|
// the base provider
|
||||||
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
|
||||||
let provider_clone = provider.clone();
|
|
||||||
|
|
||||||
// the Gas Price escalator middleware is the first middleware above the provider,
|
// the Gas Price escalator middleware is the first middleware above the provider,
|
||||||
// so that it receives the transaction last, after all the other middleware
|
// so that it receives the transaction last, after all the other middleware
|
||||||
|
@ -77,24 +76,21 @@ mod tests {
|
||||||
let provider = NonceManagerMiddleware::new(provider, address);
|
let provider = NonceManagerMiddleware::new(provider, address);
|
||||||
|
|
||||||
let tx = TransactionRequest::new();
|
let tx = TransactionRequest::new();
|
||||||
let mut tx_hash = None;
|
let mut pending_txs = Vec::new();
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
tx_hash = Some(provider.send_transaction(tx.clone(), None).await.unwrap());
|
let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
|
||||||
dbg!(
|
let hash = *pending;
|
||||||
provider
|
let gas_price = provider
|
||||||
.get_transaction(tx_hash.unwrap())
|
.get_transaction(hash)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.gas_price
|
.gas_price;
|
||||||
);
|
dbg!(gas_price);
|
||||||
|
pending_txs.push(pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
let receipt = provider_clone
|
let receipts = futures_util::future::join_all(pending_txs);
|
||||||
.pending_transaction(tx_hash.unwrap())
|
dbg!(receipts.await);
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
dbg!(receipt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
#![deny(broken_intra_doc_links)]
|
||||||
//! # Clients for interacting with Ethereum nodes
|
//! # Clients for interacting with Ethereum nodes
|
||||||
//!
|
//!
|
||||||
//! This crate provides asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
//! This crate provides asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||||
|
@ -153,6 +154,10 @@ pub trait Middleware: Sync + Send + Debug {
|
||||||
|
|
||||||
fn inner(&self) -> &Self::Inner;
|
fn inner(&self) -> &Self::Inner;
|
||||||
|
|
||||||
|
fn provider(&self) -> &Provider<Self::Provider> {
|
||||||
|
self.inner().provider()
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_block_number(&self) -> Result<U64, Self::Error> {
|
async fn get_block_number(&self) -> Result<U64, Self::Error> {
|
||||||
self.inner().get_block_number().await.map_err(FromErr::from)
|
self.inner().get_block_number().await.map_err(FromErr::from)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +166,7 @@ pub trait Middleware: Sync + Send + Debug {
|
||||||
&self,
|
&self,
|
||||||
tx: TransactionRequest,
|
tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, Self::Error> {
|
) -> Result<PendingTransaction<'_, Self::Provider>, Self::Error> {
|
||||||
self.inner()
|
self.inner()
|
||||||
.send_transaction(tx, block)
|
.send_transaction(tx, block)
|
||||||
.await
|
.await
|
||||||
|
@ -268,7 +273,10 @@ pub trait Middleware: Sync + Send + Debug {
|
||||||
self.inner().get_accounts().await.map_err(FromErr::from)
|
self.inner().get_accounts().await.map_err(FromErr::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, Self::Error> {
|
async fn send_raw_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
tx: &Transaction,
|
||||||
|
) -> Result<PendingTransaction<'a, Self::Provider>, Self::Error> {
|
||||||
self.inner()
|
self.inner()
|
||||||
.send_raw_transaction(tx)
|
.send_raw_transaction(tx)
|
||||||
.await
|
.await
|
||||||
|
@ -357,10 +365,6 @@ pub trait Middleware: Sync + Send + Debug {
|
||||||
.map_err(FromErr::from)
|
.map_err(FromErr::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, Self::Provider> {
|
|
||||||
self.inner().pending_transaction(tx_hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mempool inspection for Geth's API
|
// Mempool inspection for Geth's API
|
||||||
|
|
||||||
async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
|
async fn txpool_content(&self) -> Result<TxpoolContent, Self::Error> {
|
||||||
|
|
|
@ -133,6 +133,10 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
unreachable!("There is no inner provider here")
|
unreachable!("There is no inner provider here")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn provider(&self) -> &Provider<Self::Provider> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
////// Blockchain Status
|
////// Blockchain Status
|
||||||
//
|
//
|
||||||
// Functions for querying the state of the blockchain
|
// Functions for querying the state of the blockchain
|
||||||
|
@ -299,7 +303,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
_: Option<BlockNumber>,
|
_: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, ProviderError> {
|
) -> Result<PendingTransaction<'_, P>, ProviderError> {
|
||||||
if tx.from.is_none() {
|
if tx.from.is_none() {
|
||||||
tx.from = self.3;
|
tx.from = self.3;
|
||||||
}
|
}
|
||||||
|
@ -318,22 +322,28 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(self
|
let tx_hash = self
|
||||||
.0
|
.0
|
||||||
.request("eth_sendTransaction", [tx])
|
.request("eth_sendTransaction", [tx])
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)?)
|
.map_err(Into::into)?;
|
||||||
|
|
||||||
|
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash
|
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash
|
||||||
/// This will consume gas from the account that signed the transaction.
|
/// This will consume gas from the account that signed the transaction.
|
||||||
async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, ProviderError> {
|
async fn send_raw_transaction<'a>(
|
||||||
|
&'a self,
|
||||||
|
tx: &Transaction,
|
||||||
|
) -> Result<PendingTransaction<'a, P>, ProviderError> {
|
||||||
let rlp = utils::serialize(&tx.rlp());
|
let rlp = utils::serialize(&tx.rlp());
|
||||||
Ok(self
|
let tx_hash = self
|
||||||
.0
|
.0
|
||||||
.request("eth_sendRawTransaction", [rlp])
|
.request("eth_sendRawTransaction", [rlp])
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)?)
|
.map_err(Into::into)?;
|
||||||
|
Ok(PendingTransaction::new(tx_hash, self).interval(self.get_interval()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs data using a specific account. This account needs to be unlocked.
|
/// Signs data using a specific account. This account needs to be unlocked.
|
||||||
|
@ -510,12 +520,6 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper which creates a pending transaction object from a transaction hash
|
|
||||||
/// using the provider's polling interval
|
|
||||||
fn pending_transaction(&self, tx_hash: TxHash) -> PendingTransaction<'_, P> {
|
|
||||||
PendingTransaction::new(tx_hash, self).interval(self.get_interval())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the details of all transactions currently pending for inclusion in the next
|
/// Returns the details of all transactions currently pending for inclusion in the next
|
||||||
/// block(s), as well as the ones that are being scheduled for future execution only.
|
/// block(s), as well as the ones that are being scheduled for future execution only.
|
||||||
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
|
/// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content)
|
||||||
|
@ -979,22 +983,17 @@ mod tests {
|
||||||
|
|
||||||
let accounts = provider.get_accounts().await.unwrap();
|
let accounts = provider.get_accounts().await.unwrap();
|
||||||
let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]);
|
let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]);
|
||||||
let tx_hash = provider.send_transaction(tx, None).await.unwrap();
|
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
||||||
|
|
||||||
assert!(provider
|
assert!(provider
|
||||||
.get_transaction_receipt(tx_hash)
|
.get_transaction_receipt(*pending_tx)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none());
|
.is_none());
|
||||||
|
|
||||||
// couple of seconds pass
|
let hash = *pending_tx;
|
||||||
std::thread::sleep(std::time::Duration::new(3, 0));
|
let receipt = pending_tx.await.unwrap();
|
||||||
|
assert_eq!(receipt.transaction_hash, hash);
|
||||||
assert!(provider
|
|
||||||
.get_transaction_receipt(tx_hash)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.is_some());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -136,8 +136,8 @@ mod eth_tests {
|
||||||
|
|
||||||
async fn generic_pending_txs_test<M: Middleware>(provider: M, who: ethers::types::Address) {
|
async fn generic_pending_txs_test<M: Middleware>(provider: M, who: ethers::types::Address) {
|
||||||
let tx = TransactionRequest::new().to(who).from(who);
|
let tx = TransactionRequest::new().to(who).from(who);
|
||||||
let tx_hash = provider.send_transaction(tx, None).await.unwrap();
|
let pending_tx = provider.send_transaction(tx, None).await.unwrap();
|
||||||
let pending_tx = provider.pending_transaction(tx_hash);
|
let tx_hash = *pending_tx;
|
||||||
let receipt = pending_tx.confirmations(3).await.unwrap();
|
let receipt = pending_tx.confirmations(3).await.unwrap();
|
||||||
// got the correct receipt
|
// got the correct receipt
|
||||||
assert_eq!(receipt.transaction_hash, tx_hash);
|
assert_eq!(receipt.transaction_hash, tx_hash);
|
||||||
|
|
|
@ -56,8 +56,8 @@ async fn main() -> Result<()> {
|
||||||
let contract = SimpleContract::new(addr, client.clone());
|
let contract = SimpleContract::new(addr, client.clone());
|
||||||
|
|
||||||
// 10. call the `setValue` method
|
// 10. call the `setValue` method
|
||||||
let tx_hash = contract.set_value("hi".to_owned()).send().await?;
|
// (first `await` returns a PendingTransaction, second one waits for it to be mined)
|
||||||
let _receipt = client.pending_transaction(tx_hash).await?;
|
let _receipt = contract.set_value("hi".to_owned()).send().await?.await?;
|
||||||
|
|
||||||
// 11. get all events
|
// 11. get all events
|
||||||
let logs = contract
|
let logs = contract
|
||||||
|
|
|
@ -18,9 +18,7 @@ async fn main() -> Result<()> {
|
||||||
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
|
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
|
||||||
|
|
||||||
// send it!
|
// send it!
|
||||||
let tx_hash = client.send_transaction(tx, None).await?;
|
let receipt = client.send_transaction(tx, None).await?.await?;
|
||||||
|
|
||||||
let receipt = client.pending_transaction(tx_hash).await?;
|
|
||||||
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
||||||
|
|
||||||
println!("{}", serde_json::to_string(&tx)?);
|
println!("{}", serde_json::to_string(&tx)?);
|
||||||
|
|
|
@ -17,10 +17,10 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let tx = TransactionRequest::new()
|
let tx = TransactionRequest::new()
|
||||||
.to("vitalik.eth")
|
.to("vitalik.eth")
|
||||||
.value(parse_ether(10)?);
|
.value(parse_ether(10)?);
|
||||||
let tx_hash = client.send_transaction(tx, None).await?;
|
let pending_tx = client.send_transaction(tx, None).await?;
|
||||||
|
|
||||||
// Get the receipt
|
// Get the receipt
|
||||||
let _receipt = client.pending_transaction(tx_hash).confirmations(3).await?;
|
let _receipt = pending_tx.confirmations(3).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,10 @@ async fn main() -> Result<()> {
|
||||||
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
||||||
|
|
||||||
// send it!
|
// send it!
|
||||||
let tx_hash = client.send_transaction(tx, None).await?;
|
let pending_tx = client.send_transaction(tx, None).await?;
|
||||||
|
|
||||||
// get the mined tx
|
// get the mined tx
|
||||||
let receipt = client.pending_transaction(tx_hash).await?;
|
let receipt = pending_tx.await?;
|
||||||
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
let tx = client.get_transaction(receipt.transaction_hash).await?;
|
||||||
|
|
||||||
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
|
println!("Sent tx: {}\n", serde_json::to_string(&tx)?);
|
||||||
|
|
|
@ -18,9 +18,7 @@ async fn main() -> Result<()> {
|
||||||
let balance_before = provider.get_balance(from, None).await?;
|
let balance_before = provider.get_balance(from, None).await?;
|
||||||
|
|
||||||
// broadcast it via the eth_sendTransaction API
|
// broadcast it via the eth_sendTransaction API
|
||||||
let tx_hash = provider.send_transaction(tx, None).await?;
|
let tx = provider.send_transaction(tx, None).await?.await?;
|
||||||
|
|
||||||
let tx = provider.pending_transaction(tx_hash).await?;
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string(&tx)?);
|
println!("{}", serde_json::to_string(&tx)?);
|
||||||
|
|
||||||
|
|
|
@ -20,10 +20,10 @@ async fn main() -> anyhow::Result<()> {
|
||||||
let tx = TransactionRequest::new()
|
let tx = TransactionRequest::new()
|
||||||
.to("vitalik.eth")
|
.to("vitalik.eth")
|
||||||
.value(parse_ether(10)?);
|
.value(parse_ether(10)?);
|
||||||
let tx_hash = client.send_transaction(tx, None).await?;
|
let pending_tx = client.send_transaction(tx, None).await?;
|
||||||
|
|
||||||
// Get the receipt
|
// Get the receipt
|
||||||
let _receipt = client.pending_transaction(tx_hash).confirmations(3).await?;
|
let _receipt = pending_tx.confirmations(3).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
rust_2018_idioms,
|
rust_2018_idioms,
|
||||||
unreachable_pub
|
unreachable_pub
|
||||||
)]
|
)]
|
||||||
|
#![deny(broken_intra_doc_links)]
|
||||||
#![doc(test(
|
#![doc(test(
|
||||||
no_crate_inject,
|
no_crate_inject,
|
||||||
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
|
||||||
|
|
Loading…
Reference in New Issue