Skip to content

Commit afcc7cb

Browse files
authored
[Anvil] Fix use of Ethereum hashes in RPC endpoints (#468)
* Use eth hash in eth_GetBlockByHash * Prevent docker from trying to use SSH * remove hash resolving in test utility method * Fix use of block hash across endpoints * fix * fix send_transaction * fix tests for metadata endpoints * fix test_anvil_node * Fix + index genesis block eth hash * Best-effort indexing for latest block * make tests less flaky * Subscribe and cache genesis block manually * nit - revert unnecessary changes
1 parent 5cd5759 commit afcc7cb

File tree

7 files changed

+68
-29
lines changed

7 files changed

+68
-29
lines changed

crates/anvil-polkadot/docker/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ COPY . .
2626
# Optionally switch to a specific git ref (tag/branch/commit) before building.
2727
# If RELEASE_REF is not set, the current workspace state is used.
2828
RUN if [ -n "$RELEASE_REF" ]; then \
29-
git fetch --tags origin && \
29+
git remote set-url origin https://github.com/paritytech/foundry-polkadot.git && \
30+
git fetch --tags --force origin && \
3031
git checkout "$RELEASE_REF"; \
3132
fi
3233

crates/anvil-polkadot/src/api_server/server.rs

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -689,14 +689,17 @@ impl ApiServer {
689689
Ok(code.into())
690690
}
691691

692+
/// Returns the EVM block with the given ethereum hash.
692693
async fn get_block_by_hash(
693694
&self,
694695
block_hash: B256,
695696
hydrated_transactions: bool,
696697
) -> Result<Option<Block>> {
697698
node_info!("eth_getBlockByHash");
698-
let Some(block) =
699-
self.eth_rpc_client.block_by_hash(&H256::from_slice(block_hash.as_slice())).await?
699+
let Some(block) = self
700+
.eth_rpc_client
701+
.block_by_ethereum_hash(&H256::from_slice(block_hash.as_slice()))
702+
.await?
700703
else {
701704
return Ok(None);
702705
};
@@ -795,8 +798,12 @@ impl ApiServer {
795798
return Err(Error::ReviveRpc(EthRpcError::InvalidTransaction));
796799
};
797800

798-
let latest_block = self.latest_block();
799-
let latest_block_id = Some(BlockId::hash(B256::from_slice(latest_block.as_ref())));
801+
let best_hash = self.latest_block();
802+
let best_eth_hash =
803+
self.eth_rpc_client.resolve_ethereum_hash(&best_hash).await.ok_or_else(|| {
804+
Error::InternalError("Ethereum block hash of latest block not found".to_string())
805+
})?;
806+
let latest_block_id = Some(BlockId::hash(B256::from_slice(best_eth_hash.as_ref())));
800807
let account = if self.impersonation_manager.is_impersonated(from) || unsigned_tx {
801808
None
802809
} else {
@@ -824,7 +831,7 @@ impl ApiServer {
824831

825832
if transaction.chain_id.is_none() {
826833
transaction.chain_id =
827-
Some(sp_core::U256::from_big_endian(&self.chain_id(latest_block).to_be_bytes()));
834+
Some(sp_core::U256::from_big_endian(&self.chain_id(best_hash).to_be_bytes()));
828835
}
829836

830837
let tx = transaction
@@ -935,27 +942,27 @@ impl ApiServer {
935942
node_info!("anvil_nodeInfo");
936943

937944
let best_hash = self.latest_block();
938-
let Some(current_block) =
939-
self.get_block_by_hash(B256::from_slice(best_hash.as_ref()), false).await?
940-
else {
945+
let Some(latest_evm_block) = self.get_block_by_substrate_hash(best_hash).await? else {
941946
return Err(Error::InternalError("Latest block not found".to_string()));
942947
};
943948
let current_block_number: u64 =
944-
current_block.number.try_into().map_err(|_| EthRpcError::ConversionError)?;
949+
latest_evm_block.number.try_into().map_err(|_| EthRpcError::ConversionError)?;
945950
let current_block_timestamp: u64 =
946-
current_block.timestamp.try_into().map_err(|_| EthRpcError::ConversionError)?;
951+
latest_evm_block.timestamp.try_into().map_err(|_| EthRpcError::ConversionError)?;
947952
// This is both gas price and base fee, since pallet-revive does not support tips
948953
// https://github.com/paritytech/polkadot-sdk/blob/227c73b5c8810c0f34e87447f00e96743234fa52/substrate/frame/revive/rpc/src/lib.rs#L269
949-
let base_fee: u128 =
950-
current_block.base_fee_per_gas.try_into().map_err(|_| EthRpcError::ConversionError)?;
951-
let gas_limit: u64 = current_block.gas_limit.try_into().unwrap_or(u64::MAX);
954+
let base_fee: u128 = latest_evm_block
955+
.base_fee_per_gas
956+
.try_into()
957+
.map_err(|_| EthRpcError::ConversionError)?;
958+
let gas_limit: u64 = latest_evm_block.gas_limit.try_into().unwrap_or(u64::MAX);
952959
// pallet-revive should currently support all opcodes in PRAGUE.
953960
let hard_fork: &str = SpecId::PRAGUE.into();
954961

955962
Ok(NodeInfo {
956963
current_block_number,
957964
current_block_timestamp,
958-
current_block_hash: B256::from_slice(best_hash.as_ref()),
965+
current_block_hash: B256::from_slice(latest_evm_block.hash.as_ref()),
959966
hard_fork: hard_fork.to_string(),
960967
// pallet-revive does not support tips
961968
transaction_order: "fifo".to_string(),
@@ -974,18 +981,16 @@ impl ApiServer {
974981
node_info!("anvil_metadata");
975982

976983
let best_hash = self.latest_block();
977-
let Some(latest_block) =
978-
self.get_block_by_hash(B256::from_slice(best_hash.as_ref()), false).await?
979-
else {
984+
let Some(latest_evm_block) = self.get_block_by_substrate_hash(best_hash).await? else {
980985
return Err(Error::InternalError("Latest block not found".to_string()));
981986
};
982987
let latest_block_number: u64 =
983-
latest_block.number.try_into().map_err(|_| EthRpcError::ConversionError)?;
988+
latest_evm_block.number.try_into().map_err(|_| EthRpcError::ConversionError)?;
984989

985990
Ok(AnvilMetadata {
986991
client_version: CLIENT_VERSION.to_string(),
987992
chain_id: self.chain_id(best_hash),
988-
latest_block_hash: B256::from_slice(best_hash.as_ref()),
993+
latest_block_hash: B256::from_slice(latest_evm_block.hash.as_ref()),
989994
latest_block_number,
990995
instance_id: self.instance_id,
991996
// Forking is not supported yet in anvil-polkadot
@@ -1472,12 +1477,18 @@ impl ApiServer {
14721477
Ok(())
14731478
}
14741479

1480+
// This function translates a block ethereum hash to a substrate hash if needed.
14751481
async fn maybe_get_block_hash_for_tag(
14761482
&self,
14771483
block_id: Option<BlockId>,
14781484
) -> Result<Option<H256>> {
14791485
match ReviveBlockId::from(block_id).inner() {
1480-
BlockNumberOrTagOrHash::BlockHash(hash) => Ok(Some(hash)),
1486+
BlockNumberOrTagOrHash::BlockHash(hash) => {
1487+
// Translate the ethereum hash to a substrate hash.
1488+
Ok(Some(self.eth_rpc_client.resolve_substrate_hash(&hash).await.ok_or(
1489+
Error::ReviveRpc(EthRpcError::ClientError(ClientError::EthereumBlockNotFound)),
1490+
)?))
1491+
}
14811492
BlockNumberOrTagOrHash::BlockNumber(block_number) => {
14821493
let n = block_number.try_into().map_err(|_| {
14831494
Error::InvalidParams("Block number conversion failed".to_string())
@@ -1495,6 +1506,7 @@ impl ApiServer {
14951506
}
14961507
}
14971508

1509+
/// Returns the substrate block hash for a given block id.
14981510
async fn get_block_hash_for_tag(&self, block_id: Option<BlockId>) -> Result<H256> {
14991511
self.maybe_get_block_hash_for_tag(block_id)
15001512
.await?
@@ -1516,6 +1528,19 @@ impl ApiServer {
15161528
self.backend.blockchain().info().best_hash
15171529
}
15181530

1531+
/// Returns the EVM block for a given substrate block hash.
1532+
async fn get_block_by_substrate_hash(&self, block_hash: H256) -> Result<Option<Block>> {
1533+
let Some(substrate_block) = self.eth_rpc_client.block_by_hash(&block_hash).await? else {
1534+
return Ok(None);
1535+
};
1536+
let Some(evm_block) = self.eth_rpc_client.evm_block(substrate_block, false).await else {
1537+
return Err(Error::InternalError(
1538+
"EVM block not found for substrate block".to_string(),
1539+
));
1540+
};
1541+
Ok(Some(evm_block))
1542+
}
1543+
15191544
fn set_frame_system_balance(
15201545
&self,
15211546
latest_block: H256,
@@ -1958,6 +1983,9 @@ async fn create_revive_rpc_client(
19581983
EthRpcClient::new(api, rpc_client, rpc, block_provider, receipt_provider)
19591984
.await
19601985
.map_err(Error::from)?;
1986+
// Genesis block is not imported via block import notifications, so we need to subscribe and
1987+
// cache it manually.
1988+
let _ = eth_rpc_client.subscribe_and_cache_blocks(1).await;
19611989

19621990
// Capacity is chosen using random.org
19631991
eth_rpc_client.set_block_notifier(Some(tokio::sync::broadcast::channel::<H256>(50).0));

crates/anvil-polkadot/src/api_server/trace_helpers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ use polkadot_sdk::pallet_revive::evm::{
1111
};
1212

1313
/// Builds a Parity block trace from a vector of TransactionTrace objects returned by the
14-
/// debug_traceBlockByNumber endpoint of pallet revive and a Block object recovered from pallet
15-
/// revive. The block must be "hydrated" with all the transactions details.
14+
/// debug_traceBlockByNumber endpoint of pallet revive and a Substrate Block object recovered from
15+
/// pallet revive. The block must be "hydrated" with all the transactions details.
1616
/// This is used to build the output for Parity client's RPC method `trace_block`.
1717
pub fn parity_block_trace_builder(
1818
traces: Vec<ReviveTransactionTrace>,

crates/anvil-polkadot/tests/it/mining.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ async fn test_interval_mining() {
151151
assert_with_tolerance(
152152
after_mining.duration_since(before_mining).unwrap().as_millis(),
153153
3000,
154-
500,
154+
600,
155155
"Interval between the blocks is outside of the desired range.",
156156
);
157157
let hash3 = node.block_hash_by_number(3).await.unwrap();

crates/anvil-polkadot/tests/it/revert.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ async fn test_timestmap_in_contract_after_revert() {
557557
let timestamp = multicall_get_timestamp(&mut node, alith_addr, contract_address).await;
558558
assert_eq!(timestamp, U256::from(first_timestamp.saturating_div(1000)));
559559

560-
let second_timestamp = first_timestamp.saturating_add(3000);
560+
let second_timestamp = first_timestamp.saturating_add(3500);
561561
assert_with_tolerance(
562562
unwrap_response::<u64>(
563563
node.eth_rpc(EthRequest::EvmSetTime(U256::from(second_timestamp.saturating_div(1000))))

crates/anvil-polkadot/tests/it/standard_rpc.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,15 @@ async fn test_get_block_by_hash() {
8181
let tx_hash2 = node.send_transaction(transaction.nonce(2)).await.unwrap();
8282
unwrap_response::<()>(node.eth_rpc(EthRequest::Mine(None, None)).await.unwrap()).unwrap();
8383

84-
let hash1 = node.block_hash_by_number(1).await.unwrap();
85-
let hash2 = node.block_hash_by_number(2).await.unwrap();
84+
let hash0 = node.eth_block_hash_by_number(0).await.unwrap();
85+
let hash1 = node.eth_block_hash_by_number(1).await.unwrap();
86+
let hash2 = node.eth_block_hash_by_number(2).await.unwrap();
87+
let block0 = node.get_block_by_hash(hash0).await;
8688
let block1 = node.get_block_by_hash(hash1).await;
8789
let block2 = node.get_block_by_hash(hash2).await;
90+
assert_eq!(block0.hash, hash0);
91+
assert_eq!(block1.hash, hash1);
92+
assert_eq!(block2.hash, hash2);
8893
assert!(is_transaction_in_block(&block1.transactions, tx_hash0));
8994
assert!(is_transaction_in_block(&block1.transactions, tx_hash1));
9095
assert!(is_transaction_in_block(&block2.transactions, tx_hash2));
@@ -902,7 +907,7 @@ async fn test_anvil_node_info() {
902907
assert_eq!(node_info.fork_config.fork_block_number, None);
903908
assert_eq!(node_info.fork_config.fork_retry_backoff, None);
904909

905-
let genesis_block_hash = node.block_hash_by_number(0).await.unwrap();
910+
let genesis_block_hash = node.eth_block_hash_by_number(0).await.unwrap();
906911
assert_eq!(node_info.current_block_hash, B256::from_slice(genesis_block_hash.as_ref()));
907912
let block = node.get_block_by_hash(genesis_block_hash).await;
908913
assert_eq!(block.gas_limit, node_info.environment.gas_limit.into());
@@ -946,7 +951,7 @@ async fn test_anvil_metadata() {
946951
assert!(metadata.snapshots.is_empty());
947952

948953
// Get current block hash for comparison
949-
let block_hash = node.block_hash_by_number(0).await.unwrap();
954+
let block_hash = node.eth_block_hash_by_number(0).await.unwrap();
950955
assert_eq!(metadata.latest_block_hash, B256::from_slice(block_hash.as_ref()));
951956

952957
// Create a snapshot and verify it appears in metadata

crates/anvil-polkadot/tests/it/utils.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ impl TestNode {
268268
.unwrap()
269269
}
270270

271+
/// Retrieves a block allowing both ethereum and substrate hashes.
272+
///
273+
/// This relies on the `GetBlockByHash` anvil RPC call, which is based on pallet revive
274+
/// `get_block_by_hash` function. As a consequence, the block is first retrieved using the
275+
/// input hash as an ethereum hash, and then as a substrate hash if not found.
271276
pub async fn get_block_by_hash(&mut self, hash: H256) -> Block {
272277
unwrap_response::<Block>(
273278
self.eth_rpc(EthRequest::EthGetBlockByHash(hash.as_fixed_bytes().into(), false))

0 commit comments

Comments
 (0)