Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
be6bc17
revive/rpc: add database schema for ethereum-substrate block mapping
lrubasze Aug 27, 2025
322267c
revive/rpc: add core ethereum-substrate block mapping infrastructure
lrubasze Aug 27, 2025
38fa8a8
revive/rpc: add ethereum block hash resolution to receipt extractor
lrubasze Aug 27, 2025
10878e1
revive/rpc: add automatic block mapping population functionality
lrubasze Aug 27, 2025
b871ce5
revive/rpc: add client abstraction layer for ethereum block hash mapping
lrubasze Aug 27, 2025
93220bc
revive/rpc: integrate ethereum block hash mapping in RPC endpoints
lrubasze Aug 27, 2025
a0c0c76
revive/rpc: add ethereum block hash support to log filtering
lrubasze Aug 27, 2025
7a98998
revive/rpc: add comprehensive test suite for ethereum-substrate block…
lrubasze Aug 27, 2025
f17a0fd
revive/rpc: update sqlx cache for ethereum-substrate block mapping
lrubasze Aug 27, 2025
162684e
revive: temporary BlockHash with u64
lrubasze Aug 27, 2025
de8524e
revive/rpc: workaround fetch_gas_price
lrubasze Aug 27, 2025
a11f36e
revive/rpc: cleanup get_ethereum_block_hash
lrubasze Aug 27, 2025
c335bc8
Revert "revive: temporary BlockHash with u64"
lrubasze Sep 1, 2025
3d93b0f
revive/rpc: regenerate revive_chain.metadata
lrubasze Sep 1, 2025
765ed31
revive/rpc: use static api to get ethereum block hash
lrubasze Sep 1, 2025
3adba58
revive/rpc: fix contract info getting after metadata update
lrubasze Sep 1, 2025
904822c
revive/rpc: add some debugging
lrubasze Sep 4, 2025
2f23734
revive/rpc: prune blocks in separate method
lrubasze Sep 4, 2025
d5c48f8
revive/rpc: debug insert block mapping
lrubasze Sep 4, 2025
e5fa0a1
revive/rpc: fix header_hash debug
lrubasze Sep 4, 2025
cf2671e
revive/rpc: use substrate block hash to fetch gas price
lrubasze Sep 5, 2025
a4555fc
revive/rpc: remove populate mappings methods
lrubasze Sep 5, 2025
72ca6da
revive/rpc: improve receipt extractor mock
lrubasze Sep 5, 2025
0340c5b
revive/rpc: update pruning
lrubasze Sep 5, 2025
0f1f09e
revive/rpc: use static api to get block and receipt info data
lrubasze Sep 5, 2025
812314d
revive/rpc: fix fetching transaction by index
lrubasze Sep 12, 2025
6d67931
revive/rpc: fix comment
lrubasze Sep 16, 2025
6836c6b
revive/rpc: order transactions properly
lrubasze Sep 17, 2025
6ae4350
Merge branch 'lexnv/revive-rpc-adjustment' into lrubasze/revive-eth-b…
lrubasze Sep 29, 2025
a66e7f5
revive/rpc: do not use effective gas price from storage
lrubasze Sep 29, 2025
07e6df6
Merge branch 'lexnv/revive-rpc-adjustment' into lrubasze/revive-eth-b…
lrubasze Sep 30, 2025
8e08f71
cargo: Update cargo lock with minimal changes
lexnv Sep 30, 2025
df52649
revive: Downgrade log to debug
lexnv Sep 30, 2025
a196516
revive/rpc: add get_trie_root method
lrubasze Oct 1, 2025
794f29c
revive: decouple EthereumBlockBuilder from pallet storage
lrubasze Oct 1, 2025
efabca6
revive/rpc: use EthereumBlockBuilder for proper block reconstruction
lrubasze Oct 1, 2025
0765c4e
revive/rpc: reconstruct genesis block on server startup
lrubasze Oct 1, 2025
3feef3f
revive/rpc: handle error when cannot fetch EVM block
lrubasze Oct 1, 2025
0b25e82
revive/rpc: return none if failed to fetch EVM block
lrubasze Oct 1, 2025
cfa979d
revive/rpc: fix price setting
lrubasze Oct 1, 2025
576bc2b
Revert "revive/rpc: return none if failed to fetch EVM block"
lrubasze Oct 1, 2025
6e16fd0
Revert "revive/rpc: handle error when cannot fetch EVM block"
lrubasze Oct 1, 2025
10c8fbe
revive/rpc: add fetch_block_gas_limit and fetch_block_author to the r…
lrubasze Oct 2, 2025
e6e4fa8
revive/rpc: add gas_limit and block_author to the ethereum-substrate …
lrubasze Oct 2, 2025
1e057f5
revive/rpc: get gas_limit and block_author from the mapping during EV…
lrubasze Oct 2, 2025
6ba63cd
Merge branch 'lexnv/revive-rpc-adjustment' into lrubasze/revive-eth-b…
lrubasze Oct 2, 2025
7cfd1a5
revive/rpc: cleanup
lrubasze Oct 2, 2025
8db0b2a
revive/rpc: EVM block reconstruction tests
lrubasze Oct 2, 2025
76b3ae1
revive/rpc: store receipt metadata for pruned state recovery
lrubasze Oct 2, 2025
e18c1a1
revive/rpc: fix some debugs
lrubasze Oct 2, 2025
fde452e
revive/rpc: further cleanup
lrubasze Oct 2, 2025
16b61f4
revive/rpc: custom deserializer for TransactionInfo
lrubasze Oct 3, 2025
e1ca942
revive/rpc: tests improvements
lrubasze Oct 3, 2025
be058ef
revive/rpc: EVM tx reconstruction tests
lrubasze Oct 3, 2025
e72a519
fmt
lrubasze Oct 3, 2025
a400f7b
cleanup
lrubasze Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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

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

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS eth_to_substrate_blocks (
ethereum_block_hash BLOB NOT NULL PRIMARY KEY,
substrate_block_hash BLOB NOT NULL,
block_number INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_substrate_block_hash ON eth_to_substrate_blocks (
substrate_block_hash
);

CREATE INDEX IF NOT EXISTS idx_block_number ON eth_to_substrate_blocks (
block_number
);
Binary file modified substrate/frame/revive/rpc/revive_chain.metadata
Binary file not shown.
59 changes: 59 additions & 0 deletions substrate/frame/revive/rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,44 @@ impl Client {
self.receipt_provider.receipts_count_per_block(block_hash).await
}

/// Get an EVM transaction receipt by Ethereum hash with automatic resolution.
pub async fn receipt_by_ethereum_hash_and_index(
&self,
ethereum_hash: &H256,
transaction_index: usize,
) -> Option<ReceiptInfo> {
if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
return self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await;
}
// Fallback: treat as Substrate hash
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gut feeling tells me that fallback is not really needed.
@pgherveou please confirm

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yep I would remove the fallback after the @pgherveou confirms

self.receipt_by_hash_and_index(ethereum_hash, transaction_index).await
}

/// Get receipts count per block using Ethereum block hash with automatic resolution.
pub async fn receipts_count_per_ethereum_block(&self, ethereum_hash: &H256) -> Option<usize> {
if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
return self.receipts_count_per_block(&substrate_hash).await;
}
// Fallback: treat as Substrate hash
self.receipts_count_per_block(ethereum_hash).await
}

/// Populate missing block mappings for existing blocks.
/// This can be used for historical data population.
pub async fn populate_missing_mappings(&self, substrate_block: &SubstrateBlock) -> Result<(), ClientError> {
self.receipt_provider.populate_missing_mappings(substrate_block).await
}

/// Batch populate missing mappings for a range of blocks.
pub async fn batch_populate_mappings(
&self,
start_block: SubstrateBlockNumber,
end_block: SubstrateBlockNumber,
batch_size: usize,
) -> Result<u32, ClientError> {
self.receipt_provider.batch_populate_mappings(start_block, end_block, batch_size).await
}

/// Get the system health.
pub async fn system_health(&self) -> Result<SystemHealth, ClientError> {
let health = self.rpc.system_health().await?;
Expand Down Expand Up @@ -526,6 +564,27 @@ impl Client {
self.block_provider.block_by_hash(hash).await
}

/// Resolve Ethereum block hash to Substrate block hash, then get the block.
/// This method provides the abstraction layer needed by the RPC APIs.
pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option<H256> {
self.receipt_provider.get_substrate_hash(ethereum_hash).await
}

/// Get a block by Ethereum hash with automatic resolution to Substrate hash.
/// Falls back to treating the hash as a Substrate hash if no mapping exists.
pub async fn block_by_ethereum_hash(
&self,
ethereum_hash: &H256,
) -> Result<Option<Arc<SubstrateBlock>>, ClientError> {
// First try to resolve the Ethereum hash to a Substrate hash
if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await {
return self.block_by_hash(&substrate_hash).await;
}

// Fallback: treat the provided hash as a Substrate hash (backward compatibility)
self.block_by_hash(ethereum_hash).await
}

/// Get a block by number
pub async fn block_by_number(
&self,
Expand Down
35 changes: 22 additions & 13 deletions substrate/frame/revive/rpc/src/client/storage_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@
// limitations under the License.

use crate::{
subxt_client::{self, runtime_types::pallet_revive::storage::ContractInfo, SrcChainConfig},
subxt_client::{
self,
runtime_types::pallet_revive::storage::{AccountType, ContractInfo},
SrcChainConfig,
},
ClientError, H160,
};
use sp_core::H256;
use sp_core::{H256, U256};
use subxt::{storage::Storage, OnlineClient};

use pallet_revive::evm::{block_hash::ReceiptGasInfo, Block};
Expand All @@ -42,12 +46,15 @@ impl StorageApi {
// TODO: remove once subxt is updated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

dq: @pgherveou do we know to which version subxt should be updated to? Was this caused by substitute-derive of the API codegen?

let contract_address: subxt::utils::H160 = contract_address.0.into();

let query = subxt_client::storage().revive().contract_info_of(contract_address);
let Some(info) = self.0.fetch(&query).await? else {
return Err(ClientError::ContractNotFound);
};

Ok(info)
let query = subxt_client::storage().revive().account_info_of(contract_address);
self.0
.fetch(&query)
.await?
.and_then(|info| match info.account_type {
AccountType::Contract(contract_info) => Some(contract_info),
_ => None,
})
.ok_or(ClientError::ContractNotFound)
}

/// Get the contract trie id for the given contract address.
Expand Down Expand Up @@ -79,13 +86,15 @@ impl StorageApi {
}

pub async fn get_ethereum_block_hash(&self, number: u64) -> Result<H256, ClientError> {
let key: subxt::dynamic::Value = number.into();
let query = subxt::dynamic::storage("Revive", "BlockHash", vec![key]);
// Convert u64 to the wrapped U256 type that subxt expects
let number = subxt::utils::Static(U256::from(number));

let Some(info) = self.0.fetch(&query).await? else {
let query = subxt_client::storage().revive().block_hash(number);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice! This is the right approach here!

I remember having a chat about the dynamic API not working. It might be possible that I renamed the BlockHash on the ETH PR branch, or that the decoding was expecting a different type


let Some(hash) = self.0.fetch(&query).await? else {
return Err(ClientError::EthereumBlockNotFound);
};
let bytes = info.into_encoded();
codec::Decode::decode(&mut &bytes[..]).map_err(|err| err.into())

Ok(hash)
}
}
6 changes: 3 additions & 3 deletions substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl EthRpcServer for EthRpcServerImpl {
block_hash: H256,
hydrated_transactions: bool,
) -> RpcResult<Option<Block>> {
let Some(block) = self.client.block_by_hash(&block_hash).await? else {
let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else {
return Ok(None);
};
let block = self.client.evm_block(block, hydrated_transactions).await;
Expand Down Expand Up @@ -275,7 +275,7 @@ impl EthRpcServer for EthRpcServerImpl {
} else {
self.client.latest_block().await.hash()
};
Ok(self.client.receipts_count_per_block(&block_hash).await.map(U256::from))
Ok(self.client.receipts_count_per_ethereum_block(&block_hash).await.map(U256::from))
}

async fn get_block_transaction_count_by_number(
Expand Down Expand Up @@ -316,7 +316,7 @@ impl EthRpcServer for EthRpcServerImpl {
) -> RpcResult<Option<TransactionInfo>> {
let Some(receipt) = self
.client
.receipt_by_hash_and_index(
.receipt_by_ethereum_hash_and_index(
&block_hash,
transaction_index.try_into().map_err(|_| EthRpcError::ConversionError)?,
)
Expand Down
16 changes: 15 additions & 1 deletion substrate/frame/revive/rpc/src/receipt_extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,14 @@ impl ReceiptExtractor {
ClientError::RecoverEthAddressFailed
})?;

let base_gas_price = (self.fetch_gas_price)(block_hash).await?;
// TODO below causes such errors:
// ```
// 2025-08-27 23:34:42.026 WARN tokio-runtime-worker eth-rpc: Error extracting extrinsic: SubxtError(Rpc(ClientError(User(UserError { code: 4003, message: "Client error: UnknownBlock: Expect block number from id: BlockId::Hash(0x69a76a3ffb732205f70236a5ac95653680a193dee3c02f489ba88af4e9cc8978)", data: None }))))
// 2025-08-27 23:34:42.026 ERROR tokio-runtime-worker eth-rpc: Failed to process block 9: SubxtError(Rpc(ClientError(User(UserError { code: 4003, message: "Client error: UnknownBlock: Expect block number from id: BlockId::Hash(0x69a76a3ffb732205f70236a5ac95653680a193dee3c02f489ba88af4e9cc8978)", data: None }))))
// 2025-08-27 23:34:42.923 DEBUG tokio-runtime-worker eth-rpc: Processing block receipts for Substrate block 0x84fcafbf84a9cce7d0aa9a6c9ef219c6f4dc1b96697a864a95bb0effeea3d4d3 (block #7)
// ```
// let base_gas_price = (self.fetch_gas_price)(block_hash).await?;
let base_gas_price = U256::zero();
let tx_info =
GenericTransaction::from_signed(signed_tx.clone(), base_gas_price, Some(from));

Expand Down Expand Up @@ -376,4 +383,11 @@ impl ReceiptExtractor {
)
.await
}

/// Get the Ethereum block hash for the given Substrate block.
pub async fn get_ethereum_block_hash(&self, block: &SubstrateBlock) -> Option<H256> {
let substrate_block_number = block.number() as u64;
let substrate_block_hash = block.hash();
(self.fetch_eth_block_hash)(substrate_block_hash, substrate_block_number).await
}
}
Loading
Loading