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:
Georgios Konstantopoulos 2020-12-17 13:26:01 +02:00 committed by GitHub
parent 5b7578296b
commit 3a2fd3e814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 114 additions and 121 deletions

View File

@ -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)
} }

View File

@ -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)
}
} }

View File

@ -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)?;

View File

@ -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)
} }

View File

@ -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", ())

View File

@ -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

View File

@ -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?);
} }

View File

@ -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?);
} }

View File

@ -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

View File

@ -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);
} }

View File

@ -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

View File

@ -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();

View File

@ -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);
} }
} }

View File

@ -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> {

View File

@ -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]

View File

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

View File

@ -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

View File

@ -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)?);

View File

@ -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(())
} }

View File

@ -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)?);

View File

@ -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)?);

View File

@ -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(())
} }

View File

@ -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))