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
This commit is contained in:
parent
fb8f5a8ec9
commit
a3fa77744e
|
@ -53,8 +53,12 @@ mod eth_tests {
|
||||||
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 tx_hash = contract_call.send().await.unwrap();
|
||||||
let tx = client.get_transaction(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();
|
let tx_receipt = client
|
||||||
|
.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);
|
||||||
|
|
|
@ -33,10 +33,13 @@ struct EtherscanResponseInner {
|
||||||
#[serde(deserialize_with = "deserialize_number_from_string")]
|
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||||
#[serde(rename = "ProposeGasPrice")]
|
#[serde(rename = "ProposeGasPrice")]
|
||||||
propose_gas_price: u64,
|
propose_gas_price: u64,
|
||||||
|
#[serde(deserialize_with = "deserialize_number_from_string")]
|
||||||
|
#[serde(rename = "FastGasPrice")]
|
||||||
|
fast_gas_price: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Etherscan {
|
impl Etherscan {
|
||||||
pub fn new(api_key: Option<&'static str>) -> Self {
|
pub fn new(api_key: Option<&str>) -> Self {
|
||||||
let url = match api_key {
|
let url = match api_key {
|
||||||
Some(key) => format!("{}&apikey={}", ETHERSCAN_URL_PREFIX, key),
|
Some(key) => format!("{}&apikey={}", ETHERSCAN_URL_PREFIX, key),
|
||||||
None => ETHERSCAN_URL_PREFIX.to_string(),
|
None => ETHERSCAN_URL_PREFIX.to_string(),
|
||||||
|
@ -60,7 +63,7 @@ impl Etherscan {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl GasOracle for Etherscan {
|
impl GasOracle for Etherscan {
|
||||||
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
async fn fetch(&self) -> Result<U256, GasOracleError> {
|
||||||
if matches!(self.gas_category, GasCategory::Fast | GasCategory::Fastest) {
|
if matches!(self.gas_category, GasCategory::Fastest) {
|
||||||
return Err(GasOracleError::GasCategoryNotSupported);
|
return Err(GasOracleError::GasCategoryNotSupported);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +78,7 @@ impl GasOracle for Etherscan {
|
||||||
match self.gas_category {
|
match self.gas_category {
|
||||||
GasCategory::SafeLow => Ok(U256::from(res.result.safe_gas_price * GWEI_TO_WEI)),
|
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::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),
|
_ => Err(GasOracleError::GasCategoryNotSupported),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,10 +73,14 @@ impl<'a, P: JsonRpcClient> Future for PendingTransaction<'a, P> {
|
||||||
}
|
}
|
||||||
PendingTxState::GettingReceipt(fut) => {
|
PendingTxState::GettingReceipt(fut) => {
|
||||||
if let Ok(receipt) = futures_util::ready!(fut.as_mut().poll(ctx)) {
|
if let Ok(receipt) = futures_util::ready!(fut.as_mut().poll(ctx)) {
|
||||||
|
if let Some(receipt) = receipt {
|
||||||
*this.state = PendingTxState::CheckingReceipt(Box::new(receipt))
|
*this.state = PendingTxState::CheckingReceipt(Box::new(receipt))
|
||||||
} else {
|
} else {
|
||||||
*this.state = PendingTxState::PausedGettingReceipt
|
*this.state = PendingTxState::PausedGettingReceipt
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
*this.state = PendingTxState::PausedGettingReceipt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
PendingTxState::CheckingReceipt(receipt) => {
|
PendingTxState::CheckingReceipt(receipt) => {
|
||||||
// If we requested more than 1 confirmation, we need to compare the receipt's
|
// If we requested more than 1 confirmation, we need to compare the receipt's
|
||||||
|
@ -173,7 +177,7 @@ enum PendingTxState<'a> {
|
||||||
PausedGettingReceipt,
|
PausedGettingReceipt,
|
||||||
|
|
||||||
/// Polling the blockchain for the receipt
|
/// Polling the blockchain for the receipt
|
||||||
GettingReceipt(PinBoxFut<'a, TransactionReceipt>),
|
GettingReceipt(PinBoxFut<'a, Option<TransactionReceipt>>),
|
||||||
|
|
||||||
/// Waiting for interval to elapse before calling API again
|
/// Waiting for interval to elapse before calling API again
|
||||||
PausedGettingBlockNumber(Box<TransactionReceipt>),
|
PausedGettingBlockNumber(Box<TransactionReceipt>),
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
pub async fn get_block(
|
pub async fn get_block(
|
||||||
&self,
|
&self,
|
||||||
block_hash_or_number: impl Into<BlockId>,
|
block_hash_or_number: impl Into<BlockId>,
|
||||||
) -> Result<Block<TxHash>, ProviderError> {
|
) -> Result<Option<Block<TxHash>>, ProviderError> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_block_gen(block_hash_or_number.into(), false)
|
.get_block_gen(block_hash_or_number.into(), false)
|
||||||
.await?)
|
.await?)
|
||||||
|
@ -102,7 +102,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
pub async fn get_block_with_txs(
|
pub async fn get_block_with_txs(
|
||||||
&self,
|
&self,
|
||||||
block_hash_or_number: impl Into<BlockId>,
|
block_hash_or_number: impl Into<BlockId>,
|
||||||
) -> Result<Block<Transaction>, ProviderError> {
|
) -> Result<Option<Block<Transaction>>, ProviderError> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.get_block_gen(block_hash_or_number.into(), true)
|
.get_block_gen(block_hash_or_number.into(), true)
|
||||||
.await?)
|
.await?)
|
||||||
|
@ -112,7 +112,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
&self,
|
&self,
|
||||||
id: BlockId,
|
id: BlockId,
|
||||||
include_txs: bool,
|
include_txs: bool,
|
||||||
) -> Result<Block<Tx>, ProviderError> {
|
) -> Result<Option<Block<Tx>>, ProviderError> {
|
||||||
let include_txs = utils::serialize(&include_txs);
|
let include_txs = utils::serialize(&include_txs);
|
||||||
|
|
||||||
Ok(match id {
|
Ok(match id {
|
||||||
|
@ -137,7 +137,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
||||||
&self,
|
&self,
|
||||||
transaction_hash: T,
|
transaction_hash: T,
|
||||||
) -> Result<Transaction, ProviderError> {
|
) -> Result<Option<Transaction>, ProviderError> {
|
||||||
let hash = transaction_hash.into();
|
let hash = transaction_hash.into();
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
|
@ -150,7 +150,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
pub async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
|
pub async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
|
||||||
&self,
|
&self,
|
||||||
transaction_hash: T,
|
transaction_hash: T,
|
||||||
) -> Result<TransactionReceipt, ProviderError> {
|
) -> Result<Option<TransactionReceipt>, ProviderError> {
|
||||||
let hash = transaction_hash.into();
|
let hash = transaction_hash.into();
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
|
@ -614,6 +614,7 @@ mod ens_tests {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::Http;
|
||||||
use ethers_core::types::H256;
|
use ethers_core::types::H256;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
|
@ -636,6 +637,7 @@ mod tests {
|
||||||
let block = provider
|
let block = provider
|
||||||
.get_block(start_block + i as u64 + 1)
|
.get_block(start_block + i as u64 + 1)
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(*hash, block.hash.unwrap());
|
assert_eq!(*hash, block.hash.unwrap());
|
||||||
}
|
}
|
||||||
|
@ -673,4 +675,33 @@ mod tests {
|
||||||
let hashes: Vec<H256> = stream.take(num_txs).collect::<Vec<H256>>().await;
|
let hashes: Vec<H256> = stream.take(num_txs).collect::<Vec<H256>>().await;
|
||||||
assert_eq!(tx_hashes, hashes);
|
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::<Http>::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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,39 @@ mod eth_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ethers::{
|
use ethers::{
|
||||||
providers::JsonRpcClient,
|
providers::JsonRpcClient,
|
||||||
types::TransactionRequest,
|
types::{BlockId, TransactionRequest, H256},
|
||||||
utils::{parse_ether, Ganache},
|
utils::{parse_ether, Ganache},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn non_existing_data_works() {
|
||||||
|
let provider = Provider::<Http>::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"
|
// Without TLS this would error with "TLS Support not compiled in"
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "async-std-tls", feature = "tokio-tls"))]
|
#[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;
|
let data_1 = eth_gas_station_oracle.fetch().await;
|
||||||
assert!(data_1.is_ok());
|
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
|
// initialize and fetch gas estimates from Etherscan
|
||||||
// since etherscan does not support `fastest` category, we expect an error
|
// 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;
|
let data_2 = etherscan_oracle.fetch().await;
|
||||||
assert!(data_2.is_err());
|
assert!(data_2.is_err());
|
||||||
|
|
||||||
// but fetching the `standard` gas price should work fine
|
// 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;
|
let data_3 = etherscan_oracle_2.fetch().await;
|
||||||
assert!(data_3.is_ok());
|
assert!(data_3.is_ok());
|
||||||
|
@ -100,12 +132,10 @@ mod eth_tests {
|
||||||
let data_4 = etherchain_oracle.fetch().await;
|
let data_4 = etherchain_oracle.fetch().await;
|
||||||
assert!(data_4.is_ok());
|
assert!(data_4.is_ok());
|
||||||
|
|
||||||
// TODO: Temporarily disabled SparkPool's GasOracle while the API is still
|
// initialize and fetch gas estimates from SparkPool
|
||||||
// evolving.
|
let gas_now_oracle = GasNow::new().category(GasCategory::Fastest);
|
||||||
// // initialize and fetch gas estimates from SparkPool
|
let data_5 = gas_now_oracle.fetch().await;
|
||||||
// let gas_now_oracle = GasNow::new().category(GasCategory::Fastest);
|
assert!(data_5.is_ok());
|
||||||
// let data_5 = gas_now_oracle.fetch().await;
|
|
||||||
// assert!(data_5.is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn generic_pending_txs_test<P: JsonRpcClient>(provider: Provider<P>) {
|
async fn generic_pending_txs_test<P: JsonRpcClient>(provider: Provider<P>) {
|
||||||
|
@ -140,7 +170,7 @@ mod celo_tests {
|
||||||
let tx_hash = "c8496681d0ade783322980cce00c89419fce4b484635d9e09c79787a0f75d450"
|
let tx_hash = "c8496681d0ade783322980cce00c89419fce4b484635d9e09c79787a0f75d450"
|
||||||
.parse::<H256>()
|
.parse::<H256>()
|
||||||
.unwrap();
|
.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!(tx.gateway_fee_recipient.is_none());
|
||||||
assert_eq!(tx.gateway_fee.unwrap(), 0.into());
|
assert_eq!(tx.gateway_fee.unwrap(), 0.into());
|
||||||
assert_eq!(tx.hash, tx_hash);
|
assert_eq!(tx.hash, tx_hash);
|
||||||
|
@ -152,7 +182,7 @@ mod celo_tests {
|
||||||
let provider =
|
let provider =
|
||||||
Provider::<Http>::try_from("https://alfajores-forno.celo-testnet.org").unwrap();
|
Provider::<Http>::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!(
|
assert_eq!(
|
||||||
block.randomness,
|
block.randomness,
|
||||||
Randomness {
|
Randomness {
|
||||||
|
|
|
@ -114,6 +114,7 @@ mod eth_tests {
|
||||||
.get_transaction(tx_hash)
|
.get_transaction(tx_hash)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
.nonce
|
.nonce
|
||||||
.as_u64(),
|
.as_u64(),
|
||||||
);
|
);
|
||||||
|
@ -148,7 +149,7 @@ mod eth_tests {
|
||||||
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
let tx = TransactionRequest::new().to(wallet2.address()).value(10000);
|
||||||
let tx_hash = client.send_transaction(tx, None).await.unwrap();
|
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);
|
assert_eq!(tx.gas_price, expected_gas_price);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue