From a3fa77744ea12845c97e83cc104da26731753723 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Thu, 17 Sep 2020 14:06:56 +0300 Subject: [PATCH] fix(BREAKING): return Option for txs/receipts/blocks (#64) * fix(BREAKING): return Option for txs/receipts/blocks Otherwise if a user asks for a transaction hash or block that does not exist yet they'll get an Error * test: ensure that unmined txs return None receipts * test: fix remaining tests * chore: re-enable sparkpool gas oracle * chore: fix celo tests * fix: run the non-existing data against infura * fix: fix etherscan gas oracle tests --- ethers-contract/tests/contract.rs | 8 ++- ethers-providers/src/gas_oracle/etherscan.rs | 8 ++- ethers-providers/src/pending_transaction.rs | 8 ++- ethers-providers/src/provider.rs | 41 +++++++++++++-- ethers-providers/tests/provider.rs | 52 +++++++++++++++----- ethers-signers/tests/signer.rs | 3 +- 6 files changed, 97 insertions(+), 23 deletions(-) diff --git a/ethers-contract/tests/contract.rs b/ethers-contract/tests/contract.rs index 52296b5f..824c91f8 100644 --- a/ethers-contract/tests/contract.rs +++ b/ethers-contract/tests/contract.rs @@ -53,8 +53,12 @@ mod eth_tests { let calldata = contract_call.calldata().unwrap(); let gas_estimate = contract_call.estimate_gas().await.unwrap(); let tx_hash = contract_call.send().await.unwrap(); - let tx = client.get_transaction(tx_hash).await.unwrap(); - let tx_receipt = client.get_transaction_receipt(tx_hash).await.unwrap(); + let tx = client.get_transaction(tx_hash).await.unwrap().unwrap(); + let tx_receipt = client + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .unwrap(); assert_eq!(last_sender.clone().call().await.unwrap(), client2.address()); assert_eq!(get_value.clone().call().await.unwrap(), "hi"); assert_eq!(tx.input, calldata); diff --git a/ethers-providers/src/gas_oracle/etherscan.rs b/ethers-providers/src/gas_oracle/etherscan.rs index 5ff55623..ba142121 100644 --- a/ethers-providers/src/gas_oracle/etherscan.rs +++ b/ethers-providers/src/gas_oracle/etherscan.rs @@ -33,10 +33,13 @@ struct EtherscanResponseInner { #[serde(deserialize_with = "deserialize_number_from_string")] #[serde(rename = "ProposeGasPrice")] propose_gas_price: u64, + #[serde(deserialize_with = "deserialize_number_from_string")] + #[serde(rename = "FastGasPrice")] + fast_gas_price: u64, } impl Etherscan { - pub fn new(api_key: Option<&'static str>) -> Self { + pub fn new(api_key: Option<&str>) -> Self { let url = match api_key { Some(key) => format!("{}&apikey={}", ETHERSCAN_URL_PREFIX, key), None => ETHERSCAN_URL_PREFIX.to_string(), @@ -60,7 +63,7 @@ impl Etherscan { #[async_trait] impl GasOracle for Etherscan { async fn fetch(&self) -> Result { - if matches!(self.gas_category, GasCategory::Fast | GasCategory::Fastest) { + if matches!(self.gas_category, GasCategory::Fastest) { return Err(GasOracleError::GasCategoryNotSupported); } @@ -75,6 +78,7 @@ impl GasOracle for Etherscan { match self.gas_category { GasCategory::SafeLow => Ok(U256::from(res.result.safe_gas_price * GWEI_TO_WEI)), GasCategory::Standard => Ok(U256::from(res.result.propose_gas_price * GWEI_TO_WEI)), + GasCategory::Fast => Ok(U256::from(res.result.fast_gas_price * GWEI_TO_WEI)), _ => Err(GasOracleError::GasCategoryNotSupported), } } diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/pending_transaction.rs index 1b4a8933..6cfe5d99 100644 --- a/ethers-providers/src/pending_transaction.rs +++ b/ethers-providers/src/pending_transaction.rs @@ -73,7 +73,11 @@ impl<'a, P: JsonRpcClient> Future for PendingTransaction<'a, P> { } PendingTxState::GettingReceipt(fut) => { if let Ok(receipt) = futures_util::ready!(fut.as_mut().poll(ctx)) { - *this.state = PendingTxState::CheckingReceipt(Box::new(receipt)) + if let Some(receipt) = receipt { + *this.state = PendingTxState::CheckingReceipt(Box::new(receipt)) + } else { + *this.state = PendingTxState::PausedGettingReceipt + } } else { *this.state = PendingTxState::PausedGettingReceipt } @@ -173,7 +177,7 @@ enum PendingTxState<'a> { PausedGettingReceipt, /// Polling the blockchain for the receipt - GettingReceipt(PinBoxFut<'a, TransactionReceipt>), + GettingReceipt(PinBoxFut<'a, Option>), /// Waiting for interval to elapse before calling API again PausedGettingBlockNumber(Box), diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index 7f61388c..201dc585 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -92,7 +92,7 @@ impl Provider

{ pub async fn get_block( &self, block_hash_or_number: impl Into, - ) -> Result, ProviderError> { + ) -> Result>, ProviderError> { Ok(self .get_block_gen(block_hash_or_number.into(), false) .await?) @@ -102,7 +102,7 @@ impl Provider

{ pub async fn get_block_with_txs( &self, block_hash_or_number: impl Into, - ) -> Result, ProviderError> { + ) -> Result>, ProviderError> { Ok(self .get_block_gen(block_hash_or_number.into(), true) .await?) @@ -112,7 +112,7 @@ impl Provider

{ &self, id: BlockId, include_txs: bool, - ) -> Result, ProviderError> { + ) -> Result>, ProviderError> { let include_txs = utils::serialize(&include_txs); Ok(match id { @@ -137,7 +137,7 @@ impl Provider

{ pub async fn get_transaction>( &self, transaction_hash: T, - ) -> Result { + ) -> Result, ProviderError> { let hash = transaction_hash.into(); Ok(self .0 @@ -150,7 +150,7 @@ impl Provider

{ pub async fn get_transaction_receipt>( &self, transaction_hash: T, - ) -> Result { + ) -> Result, ProviderError> { let hash = transaction_hash.into(); Ok(self .0 @@ -614,6 +614,7 @@ mod ens_tests { #[cfg(test)] mod tests { use super::*; + use crate::Http; use ethers_core::types::H256; use futures_util::StreamExt; @@ -636,6 +637,7 @@ mod tests { let block = provider .get_block(start_block + i as u64 + 1) .await + .unwrap() .unwrap(); assert_eq!(*hash, block.hash.unwrap()); } @@ -673,4 +675,33 @@ mod tests { let hashes: Vec = stream.take(num_txs).collect::>().await; assert_eq!(tx_hashes, hashes); } + + #[tokio::test] + async fn receipt_on_unmined_tx() { + use ethers_core::{ + types::TransactionRequest, + utils::{parse_ether, Ganache}, + }; + let ganache = Ganache::new().block_time(2u64).spawn(); + let provider = Provider::::try_from(ganache.endpoint()).unwrap(); + + let accounts = provider.get_accounts().await.unwrap(); + let tx = TransactionRequest::pay(accounts[0], parse_ether(1u64).unwrap()).from(accounts[0]); + let tx_hash = provider.send_transaction(tx).await.unwrap(); + + assert!(provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .is_none()); + + // couple of seconds pass + std::thread::sleep(std::time::Duration::new(3, 0)); + + assert!(provider + .get_transaction_receipt(tx_hash) + .await + .unwrap() + .is_some()); + } } diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs index 2c320882..4d4893cc 100644 --- a/ethers-providers/tests/provider.rs +++ b/ethers-providers/tests/provider.rs @@ -10,10 +10,39 @@ mod eth_tests { use super::*; use ethers::{ providers::JsonRpcClient, - types::TransactionRequest, + types::{BlockId, TransactionRequest, H256}, utils::{parse_ether, Ganache}, }; + #[tokio::test] + async fn non_existing_data_works() { + let provider = Provider::::try_from( + "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", + ) + .unwrap(); + + assert!(provider + .get_transaction(H256::zero()) + .await + .unwrap() + .is_none()); + assert!(provider + .get_transaction_receipt(H256::zero()) + .await + .unwrap() + .is_none()); + assert!(provider + .get_block(BlockId::Hash(H256::zero())) + .await + .unwrap() + .is_none()); + assert!(provider + .get_block_with_txs(BlockId::Hash(H256::zero())) + .await + .unwrap() + .is_none()); + } + // Without TLS this would error with "TLS Support not compiled in" #[test] #[cfg(any(feature = "async-std-tls", feature = "tokio-tls"))] @@ -83,14 +112,17 @@ mod eth_tests { let data_1 = eth_gas_station_oracle.fetch().await; assert!(data_1.is_ok()); + let api_key = std::env::var("ETHERSCAN_API_KEY").unwrap(); + let api_key = Some(api_key.as_str()); + // initialize and fetch gas estimates from Etherscan // since etherscan does not support `fastest` category, we expect an error - let etherscan_oracle = Etherscan::new(None).category(GasCategory::Fastest); + let etherscan_oracle = Etherscan::new(api_key).category(GasCategory::Fastest); let data_2 = etherscan_oracle.fetch().await; assert!(data_2.is_err()); // but fetching the `standard` gas price should work fine - let etherscan_oracle_2 = Etherscan::new(None).category(GasCategory::SafeLow); + let etherscan_oracle_2 = Etherscan::new(api_key).category(GasCategory::SafeLow); let data_3 = etherscan_oracle_2.fetch().await; assert!(data_3.is_ok()); @@ -100,12 +132,10 @@ mod eth_tests { let data_4 = etherchain_oracle.fetch().await; assert!(data_4.is_ok()); - // TODO: Temporarily disabled SparkPool's GasOracle while the API is still - // evolving. - // // initialize and fetch gas estimates from SparkPool - // let gas_now_oracle = GasNow::new().category(GasCategory::Fastest); - // let data_5 = gas_now_oracle.fetch().await; - // assert!(data_5.is_ok()); + // initialize and fetch gas estimates from SparkPool + let gas_now_oracle = GasNow::new().category(GasCategory::Fastest); + let data_5 = gas_now_oracle.fetch().await; + assert!(data_5.is_ok()); } async fn generic_pending_txs_test(provider: Provider

) { @@ -140,7 +170,7 @@ mod celo_tests { let tx_hash = "c8496681d0ade783322980cce00c89419fce4b484635d9e09c79787a0f75d450" .parse::() .unwrap(); - let tx = provider.get_transaction(tx_hash).await.unwrap(); + let tx = provider.get_transaction(tx_hash).await.unwrap().unwrap(); assert!(tx.gateway_fee_recipient.is_none()); assert_eq!(tx.gateway_fee.unwrap(), 0.into()); assert_eq!(tx.hash, tx_hash); @@ -152,7 +182,7 @@ mod celo_tests { let provider = Provider::::try_from("https://alfajores-forno.celo-testnet.org").unwrap(); - let block = provider.get_block(447254).await.unwrap(); + let block = provider.get_block(447254).await.unwrap().unwrap(); assert_eq!( block.randomness, Randomness { diff --git a/ethers-signers/tests/signer.rs b/ethers-signers/tests/signer.rs index 10978e86..6f3f753f 100644 --- a/ethers-signers/tests/signer.rs +++ b/ethers-signers/tests/signer.rs @@ -114,6 +114,7 @@ mod eth_tests { .get_transaction(tx_hash) .await .unwrap() + .unwrap() .nonce .as_u64(), ); @@ -148,7 +149,7 @@ mod eth_tests { let tx = TransactionRequest::new().to(wallet2.address()).value(10000); let tx_hash = client.send_transaction(tx, None).await.unwrap(); - let tx = client.get_transaction(tx_hash).await.unwrap(); + let tx = client.get_transaction(tx_hash).await.unwrap().unwrap(); assert_eq!(tx.gas_price, expected_gas_price); } }