Skip to content

Commit 67a4103

Browse files
committed
Fix everything to make it work with real assethub westend
1 parent d606699 commit 67a4103

File tree

11 files changed

+452
-75
lines changed

11 files changed

+452
-75
lines changed

crates/anvil-polkadot/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,5 +163,4 @@ op-alloy-rpc-types.workspace = true
163163

164164
[features]
165165
default = []
166-
asm-keccak = ["alloy-primitives/asm-keccak"]
167-
forking-tests = []
166+
asm-keccak = ["alloy-primitives/asm-keccak"]

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

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,13 @@ use sqlx::sqlite::SqlitePoolOptions;
8787
use std::{collections::BTreeSet, sync::Arc, time::Duration};
8888
use substrate_runtime::{Balance, constants::NATIVE_TO_ETH_RATIO};
8989
use subxt::{
90-
Metadata as SubxtMetadata, OnlineClient, backend::rpc::RpcClient,
91-
client::RuntimeVersion as SubxtRuntimeVersion, config::substrate::H256,
92-
ext::subxt_rpcs::LegacyRpcMethods, utils::H160,
90+
Metadata as SubxtMetadata, OnlineClient,
91+
backend::rpc::RpcClient,
92+
client::RuntimeVersion as SubxtRuntimeVersion,
93+
config::substrate::H256,
94+
dynamic::{Value as DynamicValue, tx as dynamic_tx},
95+
ext::subxt_rpcs::LegacyRpcMethods,
96+
utils::H160,
9397
};
9498
use subxt_signer::eth::Keypair;
9599
use tokio::try_join;
@@ -113,6 +117,12 @@ pub struct ApiServer {
113117
/// Tracks all active filters
114118
filters: Filters,
115119
hardcoded_chain_id: u64,
120+
/// RPC methods for submitting transactions
121+
rpc: LegacyRpcMethods<SrcChainConfig>,
122+
/// Subxt OnlineClient for dynamic transaction building.
123+
/// When forking, the metadata comes from the forked chain's WASM (loaded via lazy loading),
124+
/// so pallet indices will be correct for the forked runtime.
125+
api: OnlineClient<SrcChainConfig>,
116126
}
117127

118128
/// Fetch the chain ID from the substrate chain.
@@ -140,7 +150,7 @@ impl ApiServer {
140150
let eth_rpc_client = create_revive_rpc_client(
141151
api.clone(),
142152
rpc_client.clone(),
143-
rpc,
153+
rpc.clone(),
144154
block_provider.clone(),
145155
substrate_service.spawn_handle.clone(),
146156
revive_rpc_block_limit,
@@ -176,6 +186,8 @@ impl ApiServer {
176186
instance_id: B256::random(),
177187
filters,
178188
hardcoded_chain_id: chain_id,
189+
rpc,
190+
api,
179191
})
180192
}
181193

@@ -797,8 +809,32 @@ impl ApiServer {
797809

798810
async fn send_raw_transaction(&self, transaction: Bytes) -> Result<H256> {
799811
let hash = H256(keccak_256(&transaction.0));
800-
let call = subxt_client::tx().revive().eth_transact(transaction.0);
801-
self.eth_rpc_client.submit(call).await?;
812+
813+
// Prefetch storage keys for the sender to speed up transaction validation.
814+
// This is especially important when forking from a remote chain, as each storage
815+
// read would otherwise require a separate RPC call. When not forking, this is a no-op.
816+
if let Ok(signed_tx) = TransactionSigned::decode(&transaction.0) {
817+
if let Ok(sender) = recover_maybe_impersonated_address(&signed_tx) {
818+
self.backend.prefetch_eth_transaction_keys(sender);
819+
}
820+
}
821+
822+
// Use dynamic transaction building to ensure the correct pallet index is used.
823+
// The metadata in self.api comes from the runtime's WASM (via runtime API call),
824+
// which is the forked chain's WASM when forking. This ensures correct pallet indices.
825+
let payload_value = DynamicValue::from_bytes(transaction.0.clone());
826+
let tx_payload = dynamic_tx("Revive", "eth_transact", vec![payload_value]);
827+
828+
let ext = self.api.tx().create_unsigned(&tx_payload).map_err(|e| {
829+
Error::InternalError(format!("Failed to create unsigned extrinsic: {e}"))
830+
})?;
831+
832+
// Submit the extrinsic to the transaction pool
833+
self.rpc
834+
.author_submit_extrinsic(ext.encoded())
835+
.await
836+
.map_err(|e| Error::InternalError(format!("Failed to submit transaction: {e}")))?;
837+
802838
Ok(hash)
803839
}
804840

crates/anvil-polkadot/src/substrate_node/lazy_loading/backend/blockchain.rs

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ impl<Block: BlockT + DeserializeOwned> Blockchain<Block> {
156156
storage.genesis_hash = hash;
157157
}
158158

159-
// Update leaves for non-genesis blocks
160-
if storage.blocks.len() > 1 {
161-
storage.leaves.import(hash, number, *header.parent_hash());
162-
}
159+
// Update leaves for all blocks including genesis.
160+
// For genesis when forking, the parent_hash points to the previous block on the remote chain.
161+
// That parent won't be in our leaf set, so this effectively adds genesis as a new leaf.
162+
storage.leaves.import(hash, number, *header.parent_hash());
163163

164164
// Finalize block only if explicitly requested via new_state
165165
if let NewBlockState::Final = new_state {
@@ -266,25 +266,43 @@ impl<Block: BlockT + DeserializeOwned> HeaderBackend<Block> for Blockchain<Block
266266

267267
// If not found in local storage, fetch from RPC client
268268
let header = if let Some(rpc) = self.rpc() {
269-
rpc.block(Some(hash)).ok().flatten().map(|full| {
270-
let block = full.block.clone();
271-
self.storage
272-
.write()
273-
.blocks
274-
.insert(hash, StoredBlock::Full(block.clone(), full.justifications));
275-
block.header().clone()
276-
})
269+
match rpc.block(Some(hash)) {
270+
Ok(Some(full)) => {
271+
let block = full.block.clone();
272+
self.storage
273+
.write()
274+
.blocks
275+
.insert(hash, StoredBlock::Full(block.clone(), full.justifications));
276+
Some(block.header().clone())
277+
}
278+
Ok(None) => {
279+
// Block not found on remote chain - this is expected for locally-built blocks
280+
tracing::debug!(
281+
target: LAZY_LOADING_LOG_TARGET,
282+
"Block {:?} not found in local storage or remote RPC",
283+
hash
284+
);
285+
None
286+
}
287+
Err(e) => {
288+
tracing::warn!(
289+
target: LAZY_LOADING_LOG_TARGET,
290+
"Failed to fetch block {:?} from RPC: {}",
291+
hash,
292+
e
293+
);
294+
None
295+
}
296+
}
277297
} else {
278-
None
279-
};
280-
281-
if header.is_none() {
282-
tracing::warn!(
298+
// No RPC configured - block simply doesn't exist locally
299+
tracing::debug!(
283300
target: LAZY_LOADING_LOG_TARGET,
284-
"Expected block {:x?} to exist.",
285-
&hash
301+
"Block {:?} not found in local storage (no RPC configured)",
302+
hash
286303
);
287-
}
304+
None
305+
};
288306

289307
Ok(header)
290308
}
@@ -418,19 +436,34 @@ impl<Block: BlockT + DeserializeOwned> sp_blockchain::Backend<Block> for Blockch
418436
Ok(leaves)
419437
}
420438

421-
fn children(&self, _parent_hash: Block::Hash) -> sp_blockchain::Result<Vec<Block::Hash>> {
422-
unimplemented!("Not supported by the `lazy-loading` backend.")
439+
fn children(&self, parent_hash: Block::Hash) -> sp_blockchain::Result<Vec<Block::Hash>> {
440+
// Find all blocks whose parent_hash matches the given hash
441+
let storage = self.storage.read();
442+
let children: Vec<Block::Hash> = storage
443+
.blocks
444+
.iter()
445+
.filter_map(|(hash, block)| {
446+
if *block.header().parent_hash() == parent_hash {
447+
Some(*hash)
448+
} else {
449+
None
450+
}
451+
})
452+
.collect();
453+
Ok(children)
423454
}
424455

425456
fn indexed_transaction(&self, _hash: Block::Hash) -> sp_blockchain::Result<Option<Vec<u8>>> {
426-
unimplemented!("Not supported by the `lazy-loading` backend.")
457+
// Indexed transactions are not supported in the lazy-loading backend
458+
Ok(None)
427459
}
428460

429461
fn block_indexed_body(
430462
&self,
431463
_hash: Block::Hash,
432464
) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> {
433-
unimplemented!("Not supported by the `lazy-loading` backend.")
465+
// Indexed block bodies are not supported in the lazy-loading backend
466+
Ok(None)
434467
}
435468
}
436469

crates/anvil-polkadot/src/substrate_node/lazy_loading/backend/forked_lazy_backend.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use polkadot_sdk::{
99
traits::{Block as BlockT, HashingFor},
1010
},
1111
sp_state_machine::{
12-
self, BackendTransaction, InMemoryBackend, IterArgs, StorageCollection, StorageValue,
13-
TrieBackend, backend::AsTrieBackend,
12+
self, Backend as StateMachineBackend, BackendTransaction, InMemoryBackend, IterArgs,
13+
StorageCollection, StorageValue, TrieBackend, backend::AsTrieBackend,
1414
},
1515
sp_storage::ChildInfo,
1616
sp_trie::{self, PrefixedMemoryDB},
@@ -99,6 +99,63 @@ impl<Block: BlockT + DeserializeOwned> ForkedLazyBackend<Block> {
9999
pub(crate) fn rpc(&self) -> Option<&dyn RPCClient<Block>> {
100100
self.rpc_client.as_deref()
101101
}
102+
103+
/// Prefetch multiple storage keys in a single RPC batch call.
104+
/// This significantly reduces latency when we know which keys will be needed.
105+
/// Keys that are already cached or marked as removed will be skipped.
106+
/// Returns the number of keys actually fetched from remote.
107+
pub fn prefetch_keys(&self, keys: &[Vec<u8>]) -> usize {
108+
if keys.is_empty() {
109+
return 0;
110+
}
111+
112+
let rpc = match self.rpc() {
113+
Some(rpc) => rpc,
114+
None => return 0,
115+
};
116+
117+
// Filter out keys that are already cached or removed
118+
let db = self.db.read();
119+
let removed = self.removed_keys.read();
120+
121+
let keys_to_fetch: Vec<polkadot_sdk::sp_storage::StorageKey> = keys
122+
.iter()
123+
.filter(|key| {
124+
// Skip if already in cache
125+
if StateMachineBackend::storage(&*db, key).ok().flatten().is_some() {
126+
return false;
127+
}
128+
// Skip if marked as removed
129+
if removed.contains(*key) {
130+
return false;
131+
}
132+
true
133+
})
134+
.map(|key| polkadot_sdk::sp_storage::StorageKey(key.clone()))
135+
.collect();
136+
137+
drop(db);
138+
drop(removed);
139+
140+
if keys_to_fetch.is_empty() {
141+
return 0;
142+
}
143+
144+
let fetch_count = keys_to_fetch.len();
145+
146+
// Use the batch RPC call
147+
let block_to_query = if self.before_fork { self.block_hash } else { self.fork_block };
148+
149+
match rpc.storage_batch(keys_to_fetch, block_to_query) {
150+
Ok(results) => {
151+
for (key, value) in results {
152+
self.update_storage(&key.0, &value.map(|v| v.0));
153+
}
154+
fetch_count
155+
}
156+
Err(_) => 0,
157+
}
158+
}
102159
}
103160

104161
impl<Block: BlockT + DeserializeOwned> sp_state_machine::Backend<HashingFor<Block>>

crates/anvil-polkadot/src/substrate_node/lazy_loading/backend/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ mod tests;
1212
use parking_lot::RwLock;
1313
use polkadot_sdk::{
1414
sc_client_api::{
15-
TrieCacheContext, UsageInfo,
16-
backend::{self, AuxStore},
15+
HeaderBackend, TrieCacheContext, UsageInfo,
16+
backend::{self, AuxStore, Backend as ClientBackend},
1717
},
1818
sp_blockchain,
1919
sp_core::{H256, offchain::storage::InMemOffchainStorage},
@@ -59,6 +59,22 @@ impl<Block: BlockT + DeserializeOwned> Backend<Block> {
5959
fn fork_checkpoint(&self) -> Option<&Block::Header> {
6060
self.fork_config.as_ref().map(|(_, checkpoint)| checkpoint)
6161
}
62+
63+
/// Prefetch multiple storage keys in a single batch RPC call.
64+
/// This significantly reduces latency when we know which keys will be needed
65+
/// (e.g., before transaction validation).
66+
/// Returns the number of keys actually fetched from remote.
67+
pub fn prefetch_storage_keys(&self, keys: &[Vec<u8>]) -> usize {
68+
// Get the best block hash to find the current state
69+
let best_hash = HeaderBackend::info(&self.blockchain).best_hash;
70+
71+
// Try to get the state for the best block
72+
if let Ok(state) = ClientBackend::state_at(self, best_hash, TrieCacheContext::Trusted) {
73+
state.prefetch_keys(keys)
74+
} else {
75+
0
76+
}
77+
}
6278
}
6379

6480
impl<Block: BlockT + DeserializeOwned> AuxStore for Backend<Block> {

crates/anvil-polkadot/src/substrate_node/lazy_loading/backend/tests.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::*;
22
use mock_rpc::{Rpc, TestBlock, TestHeader};
33
use parking_lot::RwLock;
44
use polkadot_sdk::{
5-
sc_client_api::{Backend as BackendT, HeaderBackend, StateBackend},
5+
sc_client_api::{Backend as BackendT, StateBackend},
66
sp_runtime::{
77
OpaqueExtrinsic,
88
traits::{BlakeTwo256, Header as HeaderT},
@@ -276,6 +276,22 @@ mod mock_rpc {
276276
let take = min(filtered.len(), count as usize);
277277
Ok(filtered.into_iter().take(take).map(|k| k.0).collect())
278278
}
279+
280+
fn storage_batch(
281+
&self,
282+
keys: Vec<StorageKey>,
283+
at: Option<Block::Hash>,
284+
) -> Result<Vec<(StorageKey, Option<StorageData>)>, jsonrpsee::core::ClientError> {
285+
// Simple implementation: just call storage for each key
286+
let results = keys
287+
.into_iter()
288+
.map(|key| {
289+
let value = self.storage(key.clone(), at).ok().flatten();
290+
(key, value)
291+
})
292+
.collect();
293+
Ok(results)
294+
}
279295
}
280296
}
281297

0 commit comments

Comments
 (0)