Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: rpc result serde and avoid rate limits #4325

Merged
merged 2 commits into from
Aug 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/rethnet_defaults/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ pub const PRIVATE_KEYS: [&str; 20] = [
/// The default cache directory. Cache dirs for specific subsystems such as the RPC Client are
/// subdirectories of this directory.
pub const CACHE_DIR: &str = "./edr-cache";

/// Maximum concurrent requests to a remote blockchain node to avoid getting rate limited.
pub const MAX_CONCURRENT_REQUESTS: usize = 5;
3 changes: 3 additions & 0 deletions crates/rethnet_eth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ log = { version = "0.4.17", default-features = false }
open-fastrlp = { version = "0.1.2", default-features = false, features = ["derive"], optional = true }
primitive-types = { version = "0.11.1", default-features = false, features = ["rlp"] }
reqwest = { version = "0.11", features = ["blocking", "json"] }
rethnet_defaults = { version = "0.1.0-dev", path = "../rethnet_defaults" }
revm-primitives = { git = "https://github.com/Wodann/revm", rev = "9fdf446", version = "1.1", default-features = false }
# revm-primitives = { path = "../../../revm/crates/primitives", version = "1.0", default-features = false }
rlp = { version = "0.5.2", default-features = false, features = ["derive"] }
Expand All @@ -28,9 +29,11 @@ tokio = { version = "1.21.2", default-features = false, features = ["fs", "sync"
triehash = { version = "0.8.4", default-features = false }

[dev-dependencies]
lazy_static = "1.4.0"
mockito = { version = "1.0.2", default-features = false }
paste = { version = "1.0.14", default-features = false }
rethnet_test_utils = { version = "0.1.0-dev", path = "../rethnet_test_utils" }
serial_test = "2.0.0"
tempfile = { version = "3.7.1", default-features = false }
tokio = { version = "1.23.0", features = ["macros"] }
walkdir = { version = "2.3.3", default-features = false }
Expand Down
15 changes: 11 additions & 4 deletions crates/rethnet_eth/src/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ where
MapAccessT: serde::de::MapAccess<'deserializer>,
{
use serde::de::Error;
let mut transaction_type = None;
let mut status_code = None;
// These are `String` to support deserializing from `serde_json::Value`
let mut transaction_type: Option<String> = None;
let mut status_code: Option<String> = None;
let mut state_root = None;
let mut cumulative_gas_used = None;
let mut logs_bloom = None;
Expand Down Expand Up @@ -188,14 +189,14 @@ where
let data = if let Some(state_root) = state_root {
TypedReceiptData::PreEip658Legacy { state_root }
} else if let Some(status_code) = status_code {
let status = match status_code {
let status = match status_code.as_str() {
"0x0" => 0u8,
"0x1" => 1u8,
_ => return Err(Error::custom(format!("unknown status: {status_code}"))),
};

if let Some(transaction_type) = transaction_type {
match transaction_type {
match transaction_type.as_str() {
"0x0" => TypedReceiptData::PostEip658Legacy { status },
"0x1" => TypedReceiptData::Eip2930 { status },
"0x2" => TypedReceiptData::Eip1559 { status },
Expand Down Expand Up @@ -394,6 +395,12 @@ mod tests {
for receipt in receipts {
let serialized = serde_json::to_string(&receipt).unwrap();
let deserialized: TypedReceipt<Log> = serde_json::from_str(&serialized).unwrap();
assert_eq!(receipt, deserialized);

// This is necessary to ensure that the deser implementation doesn't expect a &str
// where a String can be passed.
let serialized = serde_json::to_value(&receipt).unwrap();
let deserialized: TypedReceipt<Log> = serde_json::from_value(serialized).unwrap();

assert_eq!(receipt, deserialized);
}
Expand Down
77 changes: 46 additions & 31 deletions crates/rethnet_eth/src/remote/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,24 @@ impl RpcClient {
.await
}

/// Methods for retrieving multiple transaction receipts in one batch
pub async fn get_transaction_receipts(
&self,
hashes: impl IntoIterator<Item = &B256>,
) -> Result<Option<Vec<BlockReceipt>>, RpcClientError> {
let requests: Vec<MethodInvocation> = hashes
.into_iter()
.map(|transaction_hash| MethodInvocation::GetTransactionReceipt(*transaction_hash))
.collect();

let responses = self.batch_call(&requests).await?;

responses
.into_iter()
.map(Self::parse_response_value::<Option<BlockReceipt>>)
.collect::<Result<Option<Vec<BlockReceipt>>, _>>()
}

/// Calls `eth_getStorageAt`.
pub async fn get_storage_at(
&self,
Expand Down Expand Up @@ -1070,13 +1088,27 @@ mod tests {
}

#[tokio::test]
async fn get_earliest_block() {
async fn get_block_with_transaction_data() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be renamed to signal that you're testing caching?

let alchemy_url = get_alchemy_url();
let client = TestRpcClient::new(&alchemy_url);

let _block = TestRpcClient::new(&alchemy_url)
.get_block_by_number(BlockSpec::earliest())
let block_spec = BlockSpec::Number(U256::from(16220843));

assert_eq!(client.files_in_cache().len(), 0);

let block_from_remote = client
.get_block_by_number_with_transaction_data(block_spec.clone())
.await
.expect("should have succeeded");
.expect("should have from remote");

assert_eq!(client.files_in_cache().len(), 1);

let block_from_cache = client
.get_block_by_number_with_transaction_data(block_spec.clone())
.await
.expect("should have from remote");

assert_eq!(block_from_remote, block_from_cache)
}

#[tokio::test]
Expand Down Expand Up @@ -1248,9 +1280,9 @@ mod tests {
U256::from_str_radix("1e449a99b8", 16).expect("couldn't parse data")
);
assert_eq!(
tx.input,
Bytes::from(hex::decode("a9059cbb000000000000000000000000e2c1e729e05f34c07d80083982ccd9154045dcc600000000000000000000000000000000000000000000000000000004a817c800").unwrap())
);
tx.input,
Bytes::from(hex::decode("a9059cbb000000000000000000000000e2c1e729e05f34c07d80083982ccd9154045dcc600000000000000000000000000000000000000000000000000000004a817c800").unwrap())
);
assert_eq!(
tx.nonce,
u64::from_str_radix("653b", 16).expect("couldn't parse data")
Expand Down Expand Up @@ -1548,44 +1580,27 @@ mod tests {
async fn switching_provider_doesnt_invalidate_cache() {
let alchemy_url = get_alchemy_url();
let infura_url = get_infura_url();
let dai_address = Address::from_str("0x6b175474e89094c44da98b954eedeac495271d0f")
.expect("failed to parse address");
let block_spec = Some(BlockSpec::Number(U256::from(16220843)));

let alchemy_client = TestRpcClient::new(&alchemy_url);
alchemy_client
.network_id()
let total_supply = alchemy_client
.get_storage_at(&dai_address, U256::from(1), block_spec.clone())
.await
.expect("should have succeeded");
let alchemy_cached_files = alchemy_client.files_in_cache();
assert_eq!(alchemy_cached_files.len(), 1);

let infura_client = TestRpcClient::new_with_dir(&infura_url, alchemy_client.cache_dir);
infura_client
.network_id()
.get_storage_at(&dai_address, U256::from(1), block_spec)
.await
.expect("should have succeeded");
let infura_cached_files = infura_client.files_in_cache();
assert_eq!(alchemy_cached_files, infura_cached_files);
}

#[tokio::test]
async fn stores_result_in_cache() {
let alchemy_url = get_alchemy_url();
let client = TestRpcClient::new(&alchemy_url);
let dai_address = Address::from_str("0x6b175474e89094c44da98b954eedeac495271d0f")
.expect("failed to parse address");

let total_supply = client
.get_storage_at(
&dai_address,
U256::from(1),
Some(BlockSpec::Number(U256::from(16220843))),
)
.await
.expect("should have succeeded");

let cached_files = client.files_in_cache();
assert_eq!(cached_files.len(), 1);

let mut file = File::open(&cached_files[0]).expect("failed to open file");
let mut file = File::open(&infura_cached_files[0]).expect("failed to open file");
let cached_result: U256 = serde_json::from_reader(&mut file).expect("failed to parse");

assert_eq!(total_supply, cached_result);
Expand Down
17 changes: 13 additions & 4 deletions crates/rethnet_eth/src/remote/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub struct Transaction {
/// block number where this transaction was in
pub block_number: Option<U256>,
/// integer of the transactions index position in the block. null when its pending
#[serde(deserialize_with = "crate::serde::optional_u64_from_hex")]
#[serde(with = "crate::serde::optional_u64")]
pub transaction_index: Option<u64>,
/// address of the sender
pub from: Address,
Expand All @@ -52,14 +52,23 @@ pub struct Transaction {
#[serde(with = "crate::serde::bytes")]
pub input: Bytes,
/// ECDSA recovery id
#[serde(alias = "yParity", with = "crate::serde::u64")]
#[serde(with = "crate::serde::u64")]
pub v: u64,
/// Y-parity for EIP-2930 and EIP-1559 transactions. In theory these transactions types
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did this cause a crash when deserializing?

/// shouldn't have a `v` field, but in practice they are returned by nodes.
#[serde(
default,
rename = "yParity",
skip_serializing_if = "Option::is_none",
with = "crate::serde::optional_u64"
)]
pub y_parity: Option<u64>,
/// ECDSA signature r
pub r: U256,
/// ECDSA signature s
pub s: U256,
/// chain ID
#[serde(default, deserialize_with = "crate::serde::optional_u64_from_hex")]
#[serde(default, with = "crate::serde::optional_u64")]
pub chain_id: Option<u64>,
/// integer of the transaction type, 0x0 for legacy transactions, 0x1 for access list types, 0x2 for dynamic fees
#[serde(rename = "type", default, with = "crate::serde::u64")]
Expand Down Expand Up @@ -139,7 +148,7 @@ pub struct Block<TX> {
/// mix hash
pub mix_hash: B256,
/// hash of the generated proof-of-work. null when its pending block.
#[serde(deserialize_with = "crate::serde::optional_u64_from_hex")]
#[serde(with = "crate::serde::optional_u64")]
pub nonce: Option<u64>,
/// base fee per gas
pub base_fee_per_gas: Option<U256>,
Expand Down