Skip to content

Commit be01f15

Browse files
authored
anvil-polkadot: add chain reversion RPCs (#336)
* anvil-polkadot: add snapshot manager & evm snapshot RPC Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * cargo: format toml & lock Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * Revert "cargo: format toml & lock" This reverts commit 22d3d36. * evm_revert: wip debug test failure Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: seal best blocks only Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: test snapshot and revert Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * tomls: update formatting and polkadot-sdk dep Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: revert uneeded changes Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * Update crates/revive-env/Cargo.toml * Update crates/revive-strategy/Cargo.toml * anvil-polkadot: update block provider best block after revert Also set the finalized manual seal param to true for experiments. Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: set db pruning to archive Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: impl rollback & add tests Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): add todos for block provider testing Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): finalize existing tests Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: update evm_revert & anvil-rollback return type Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): add revert and txs in mempool test Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: code polish Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): fix timestmap with evm revert test Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): final polish Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: remove leftovers Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: fix merge issues Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: simplify time updating after revert Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: remove extra timestamp setting in storage Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: address leftovers after merge Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * Apply suggestion from @iulianbarbu * Apply suggestion from @iulianbarbu * anvil-polkadot: revert new backend error variants Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * Update crates/anvil-polkadot/src/substrate_node/service/backend.rs * anvil-polkadot: address feedback Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * Update crates/anvil-polkadot/src/substrate_node/snapshot.rs * Update crates/anvil-polkadot/src/substrate_node/snapshot.rs * anvil-polkadot(tests): address feedback Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): fix merge issue Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot: address feedback Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * anvil-polkadot(tests): simplify evm_snapshot result assert Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> * doc: fix clippy Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> --------- Signed-off-by: Iulian Barbu <iulian.barbu@parity.io>
1 parent 9dd622a commit be01f15

File tree

18 files changed

+1238
-429
lines changed

18 files changed

+1238
-429
lines changed

Cargo.lock

Lines changed: 311 additions & 297 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/anvil-polkadot/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ path = "bin/main.rs"
2121
# foundry internal
2222
codec = { version = "3.7.5", default-features = true, package = "parity-scale-codec" }
2323
substrate-runtime = { path = "substrate-runtime" }
24+
pallet-revive-eth-rpc = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master" }
2425
secp256k1 = { version = "0.28.0", default-features = false }
2526
libsecp256k1 = { version = "0.7.0", default-features = false }
2627
sp-runtime-interface = { git = "https://github.com/paritytech/polkadot-sdk.git", branch = "master", default-features = false }
@@ -64,7 +65,6 @@ polkadot-sdk = { git = "https://github.com/paritytech/polkadot-sdk.git", branch
6465
"substrate-frame-rpc-system",
6566
"substrate-rpc-client",
6667
"substrate-wasm-builder",
67-
"pallet-revive-eth-rpc"
6868
] }
6969
anvil.workspace = true
7070
anvil-core.workspace = true

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
use crate::substrate_node::{mining_engine::MiningError, service::BackendError};
22
use anvil_rpc::{error::RpcError, response::ResponseResult};
3-
use polkadot_sdk::{
4-
pallet_revive_eth_rpc::{EthRpcError, client::ClientError},
5-
sp_api,
6-
};
3+
use pallet_revive_eth_rpc::{EthRpcError, client::ClientError};
4+
use polkadot_sdk::sp_api;
75
use serde::Serialize;
86

97
#[derive(Debug, thiserror::Error)]

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::{
22
AnvilNodeConfig,
33
logging::LoggingManager,
4-
substrate_node::{impersonation::ImpersonationManager, service::Service},
4+
substrate_node::{
5+
impersonation::ImpersonationManager, service::Service, snapshot::SnapshotManager,
6+
},
57
};
68
use anvil_core::eth::EthRequest;
79
use anvil_rpc::response::ResponseResult;
@@ -20,19 +22,26 @@ pub struct ApiRequest {
2022
}
2123

2224
pub fn spawn(
25+
config: &AnvilNodeConfig,
2326
substrate_service: &Service,
2427
logging_manager: LoggingManager,
25-
config: &AnvilNodeConfig,
28+
snapshot_manager: SnapshotManager,
2629
) -> ApiHandle {
2730
let (api_handle, receiver) = mpsc::channel(100);
2831

2932
let service = substrate_service.clone();
3033
let mut impersonation_manager = ImpersonationManager::default();
3134
impersonation_manager.set_auto_impersonate_account(config.enable_auto_impersonate);
3235
substrate_service.spawn_handle.spawn("anvil-api-server", "anvil", async move {
33-
let api_server = ApiServer::new(service, receiver, logging_manager, impersonation_manager)
34-
.await
35-
.unwrap_or_else(|err| panic!("Failed to spawn the API server: {err}"));
36+
let api_server = ApiServer::new(
37+
service,
38+
receiver,
39+
logging_manager,
40+
snapshot_manager,
41+
impersonation_manager,
42+
)
43+
.await
44+
.unwrap_or_else(|err| panic!("Failed to spawn the API server: {err}"));
3645
api_server.run().await;
3746
});
3847

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

Lines changed: 137 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ use crate::{
1515
in_mem_rpc::InMemoryRpcClient,
1616
mining_engine::MiningEngine,
1717
service::{
18-
BackendWithOverlay, Client, Service,
18+
BackendError, BackendWithOverlay, Client, Service,
1919
storage::{
2020
AccountType, ByteCodeType, CodeInfo, ContractInfo, ReviveAccountInfo,
2121
SystemAccountInfo,
2222
},
2323
},
24+
snapshot::{RevertInfo, SnapshotManager},
2425
},
2526
};
2627
use alloy_eips::{BlockId, BlockNumberOrTag};
@@ -31,6 +32,11 @@ use anvil_core::eth::{EthRequest, Params as MineParams};
3132
use anvil_rpc::response::ResponseResult;
3233
use codec::{Decode, Encode};
3334
use futures::{StreamExt, channel::mpsc};
35+
use pallet_revive_eth_rpc::{
36+
BlockInfoProvider, EthRpcError, ReceiptExtractor, ReceiptProvider, SubxtBlockInfoProvider,
37+
client::{Client as EthRpcClient, ClientError, SubscriptionType},
38+
subxt_client::{self, SrcChainConfig},
39+
};
3440
use polkadot_sdk::{
3541
pallet_revive::{
3642
ReviveApi,
@@ -39,15 +45,13 @@ use polkadot_sdk::{
3945
TransactionSigned,
4046
},
4147
},
42-
pallet_revive_eth_rpc::{
43-
EthRpcError, ReceiptExtractor, ReceiptProvider, SubxtBlockInfoProvider,
44-
client::{Client as EthRpcClient, ClientError, SubscriptionType},
45-
subxt_client::{self, SrcChainConfig},
46-
},
4748
parachains_common::{AccountId, Hash, Nonce},
49+
polkadot_sdk_frame::runtime::types_common::OpaqueBlock,
4850
sc_client_api::HeaderBackend,
51+
sc_service::SpawnTaskHandle,
4952
sp_api::{Metadata, ProvideRuntimeApi},
5053
sp_arithmetic::Permill,
54+
sp_blockchain::Info,
5155
sp_core::{self, Hasher, keccak_256},
5256
sp_runtime::traits::BlakeTwo256,
5357
};
@@ -67,13 +71,15 @@ pub struct Wallet {
6771
}
6872

6973
pub struct ApiServer {
74+
eth_rpc_client: EthRpcClient,
7075
req_receiver: mpsc::Receiver<ApiRequest>,
7176
backend: BackendWithOverlay,
7277
logging_manager: LoggingManager,
7378
client: Arc<Client>,
7479
mining_engine: Arc<MiningEngine>,
75-
eth_rpc_client: EthRpcClient,
80+
block_provider: SubxtBlockInfoProvider,
7681
wallet: Wallet,
82+
snapshot_manager: SnapshotManager,
7783
impersonation_manager: ImpersonationManager,
7884
}
7985

@@ -82,11 +88,24 @@ impl ApiServer {
8288
substrate_service: Service,
8389
req_receiver: mpsc::Receiver<ApiRequest>,
8490
logging_manager: LoggingManager,
91+
snapshot_manager: SnapshotManager,
8592
impersonation_manager: ImpersonationManager,
8693
) -> Result<Self> {
87-
let eth_rpc_client = create_revive_rpc_client(&substrate_service).await?;
94+
let rpc_client = RpcClient::new(InMemoryRpcClient(substrate_service.rpc_handlers.clone()));
95+
let api = create_online_client(&substrate_service, rpc_client.clone()).await?;
96+
let rpc = LegacyRpcMethods::<SrcChainConfig>::new(rpc_client.clone());
97+
let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?;
98+
let eth_rpc_client = create_revive_rpc_client(
99+
api.clone(),
100+
rpc_client.clone(),
101+
rpc,
102+
block_provider.clone(),
103+
substrate_service.spawn_handle.clone(),
104+
)
105+
.await?;
88106

89107
Ok(Self {
108+
block_provider,
90109
req_receiver,
91110
logging_manager,
92111
backend: BackendWithOverlay::new(
@@ -96,6 +115,7 @@ impl ApiServer {
96115
client: substrate_service.client.clone(),
97116
mining_engine: substrate_service.mining_engine.clone(),
98117
eth_rpc_client,
118+
snapshot_manager,
99119
impersonation_manager,
100120
wallet: Wallet {
101121
accounts: vec![
@@ -129,6 +149,7 @@ impl ApiServer {
129149
EthRequest::SetAutomine(enabled) => self.set_auto_mine(enabled).to_rpc_result(),
130150
EthRequest::EvmMine(mine) => self.evm_mine(mine).await.to_rpc_result(),
131151
EthRequest::EvmMineDetailed(mine) => self.evm_mine_detailed(mine).await.to_rpc_result(),
152+
132153
//------- TimeMachine---------
133154
EthRequest::EvmSetBlockTimeStampInterval(time) => {
134155
self.set_block_timestamp_interval(time).to_rpc_result()
@@ -141,6 +162,7 @@ impl ApiServer {
141162
}
142163
EthRequest::EvmIncreaseTime(time) => self.increase_time(time).to_rpc_result(),
143164
EthRequest::EvmSetTime(timestamp) => self.set_time(timestamp).to_rpc_result(),
165+
144166
// -- Impersonation --
145167
EthRequest::ImpersonateAccount(addr) => {
146168
self.impersonate_account(H160::from_slice(addr.0.as_ref())).to_rpc_result()
@@ -151,6 +173,10 @@ impl ApiServer {
151173
EthRequest::AutoImpersonateAccount(enable) => {
152174
self.auto_impersonate_account(enable).to_rpc_result()
153175
}
176+
EthRequest::EthSendUnsignedTransaction(request) => {
177+
node_info!("eth_sendUnsignedTransaction");
178+
self.send_transaction(*request.clone(), true).await.to_rpc_result()
179+
}
154180

155181
//------- Eth RPCs---------
156182
EthRequest::EthChainId(_) => self.eth_chain_id().to_rpc_result(),
@@ -190,6 +216,11 @@ impl ApiServer {
190216
.map(|val| AlloyU256::from(val).inner())
191217
.to_rpc_result(),
192218

219+
// --- Snapshot ---
220+
EthRequest::EvmSnapshot(()) => self.snapshot().await.to_rpc_result(),
221+
EthRequest::Rollback(depth) => self.rollback(depth).await.to_rpc_result(),
222+
EthRequest::EvmRevert(id) => self.revert(id).await.to_rpc_result(),
223+
193224
// ------- State injector ---------
194225
EthRequest::SetBalance(address, value) => {
195226
self.set_balance(address, value).to_rpc_result()
@@ -252,10 +283,6 @@ impl ApiServer {
252283
node_info!("eth_getLogs");
253284
self.get_logs(filter).await.to_rpc_result()
254285
}
255-
EthRequest::EthSendUnsignedTransaction(request) => {
256-
node_info!("eth_sendUnsignedTransaction");
257-
self.send_transaction(*request.clone(), true).await.to_rpc_result()
258-
}
259286
_ => Err::<(), _>(Error::RpcUnimplemented).to_rpc_result(),
260287
};
261288

@@ -614,6 +641,36 @@ impl ApiServer {
614641
Ok(Some(block))
615642
}
616643

644+
pub(crate) async fn snapshot(&mut self) -> Result<U256> {
645+
node_info!("evm_snapshot");
646+
Ok(self.snapshot_manager.snapshot())
647+
}
648+
649+
pub(crate) async fn revert(&mut self, id: U256) -> Result<bool> {
650+
node_info!("evm_revert");
651+
let res = self
652+
.snapshot_manager
653+
.revert(id)
654+
.map_err(|err| Error::Backend(BackendError::Client(err)))?;
655+
let Some(res) = res else { return Ok(false) };
656+
657+
self.on_revert_update(res).await?;
658+
659+
Ok(true)
660+
}
661+
662+
pub(crate) async fn rollback(&mut self, depth: Option<u64>) -> Result<()> {
663+
node_info!("anvil_rollback");
664+
let res = self
665+
.snapshot_manager
666+
.rollback(depth)
667+
.map_err(|err| Error::Backend(BackendError::Client(err)))?;
668+
669+
self.on_revert_update(res).await?;
670+
671+
Ok(())
672+
}
673+
617674
async fn get_block_transaction_count_by_hash(&self, block_hash: B256) -> Result<Option<U256>> {
618675
let block_hash = H256::from_slice(block_hash.as_slice());
619676
Ok(self.eth_rpc_client.receipts_count_per_block(&block_hash).await.map(U256::from))
@@ -729,13 +786,6 @@ impl ApiServer {
729786
Ok(FilterResults::Logs(logs))
730787
}
731788

732-
// Helpers
733-
async fn get_block_hash_for_tag(&self, block_id: Option<BlockId>) -> Result<H256> {
734-
self.eth_rpc_client
735-
.block_hash_for_tag(ReviveBlockId::from(block_id).inner())
736-
.await
737-
.map_err(Error::from)
738-
}
739789
// State injector RPCs
740790
fn set_chain_id(&self, chain_id: u64) -> Result<()> {
741791
node_info!("anvil_setChainId");
@@ -894,7 +944,52 @@ impl ApiServer {
894944

895945
Ok(())
896946
}
947+
897948
// ----- Helpers
949+
async fn update_block_provider_on_revert(&self, info: &Info<OpaqueBlock>) -> Result<()> {
950+
let new_best_block = self.block_provider.block_by_number(info.best_number).await?;
951+
let new_finalized_block =
952+
self.block_provider.block_by_number(info.finalized_number).await?;
953+
954+
if let Some(block) = new_best_block.and_then(Arc::into_inner) {
955+
self.block_provider.update_latest(block, SubscriptionType::BestBlocks).await;
956+
}
957+
958+
if let Some(block) = new_finalized_block.and_then(Arc::into_inner) {
959+
self.block_provider.update_latest(block, SubscriptionType::FinalizedBlocks).await;
960+
}
961+
962+
Ok(())
963+
}
964+
965+
async fn update_time_on_revert(&self, best_hash: Hash) -> Result<()> {
966+
let timestamp = self.backend.read_timestamp(best_hash)?;
967+
self.mining_engine.set_time(Duration::from_millis(timestamp));
968+
Ok(())
969+
}
970+
971+
async fn on_revert_update(&self, revert_info: RevertInfo) -> Result<()> {
972+
if revert_info.reverted > 0 {
973+
self.update_block_provider_on_revert(&revert_info.info).await?;
974+
}
975+
976+
let hash = self
977+
.get_block_hash_for_tag(Some(BlockId::Number(BlockNumberOrTag::Number(
978+
revert_info.info.best_number.into(),
979+
))))
980+
.await?;
981+
self.update_time_on_revert(hash).await?;
982+
983+
Ok(())
984+
}
985+
986+
async fn get_block_hash_for_tag(&self, block_id: Option<BlockId>) -> Result<H256> {
987+
self.eth_rpc_client
988+
.block_hash_for_tag(ReviveBlockId::from(block_id).inner())
989+
.await
990+
.map_err(Error::from)
991+
}
992+
898993
fn get_account_id(&self, block: Hash, address: Address) -> Result<AccountId> {
899994
Ok(self.client.runtime_api().account_id(block, ReviveAddress::from(address).inner())?)
900995
}
@@ -961,9 +1056,10 @@ fn new_contract_info(address: &Address, code_hash: H256, nonce: Nonce) -> Contra
9611056
}
9621057
}
9631058

964-
async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcClient> {
965-
let rpc_client = RpcClient::new(InMemoryRpcClient(substrate_service.rpc_handlers.clone()));
966-
1059+
async fn create_online_client(
1060+
substrate_service: &Service,
1061+
rpc_client: RpcClient,
1062+
) -> Result<OnlineClient<SrcChainConfig>> {
9671063
let genesis_block_number = substrate_service.genesis_block_number.try_into().map_err(|_| {
9681064
Error::InternalError(format!(
9691065
"Genesis block number {} is too large for u32 (max: {})",
@@ -996,9 +1092,11 @@ async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcC
9961092
else {
9971093
return Err(Error::InternalError("Unable to fetch metadata versions".to_string()));
9981094
};
1095+
9991096
let Some(latest_metadata_version) = supported_metadata_versions.into_iter().max() else {
10001097
return Err(Error::InternalError("No stable metadata versions supported".to_string()));
10011098
};
1099+
10021100
let opaque_metadata = substrate_service
10031101
.client
10041102
.runtime_api()
@@ -1014,16 +1112,24 @@ async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcC
10141112
let subxt_metadata = SubxtMetadata::decode(&mut (*opaque_metadata).as_slice())
10151113
.map_err(|_| Error::InternalError("Unable to decode metadata".to_string()))?;
10161114

1017-
let api = OnlineClient::<SrcChainConfig>::from_rpc_client_with(
1115+
OnlineClient::<SrcChainConfig>::from_rpc_client_with(
10181116
genesis_hash,
10191117
subxt_runtime_version,
10201118
subxt_metadata,
1021-
rpc_client.clone(),
1022-
)?;
1023-
let rpc = LegacyRpcMethods::<SrcChainConfig>::new(rpc_client.clone());
1024-
1025-
let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?;
1119+
rpc_client,
1120+
)
1121+
.map_err(|err| {
1122+
Error::InternalError(format!("Failed to initialize the subxt online client: {err}"))
1123+
})
1124+
}
10261125

1126+
async fn create_revive_rpc_client(
1127+
api: OnlineClient<SrcChainConfig>,
1128+
rpc_client: RpcClient,
1129+
rpc: LegacyRpcMethods<SrcChainConfig>,
1130+
block_provider: SubxtBlockInfoProvider,
1131+
task_spawn_handle: SpawnTaskHandle,
1132+
) -> Result<EthRpcClient> {
10271133
let (pool, keep_latest_n_blocks) = {
10281134
// see sqlite in-memory issue: https://github.com/launchbadge/sqlx/issues/2510
10291135
let pool = SqlitePoolOptions::new()
@@ -1069,16 +1175,17 @@ async fn create_revive_rpc_client(substrate_service: &Service) -> Result<EthRpcC
10691175
.await
10701176
.map_err(Error::from)?;
10711177
let eth_rpc_client_clone = eth_rpc_client.clone();
1072-
substrate_service.spawn_handle.spawn("block-subscription", "None", async move {
1178+
task_spawn_handle.spawn("block-subscription", "None", async move {
10731179
let eth_rpc_client = eth_rpc_client_clone;
10741180
let best_future =
10751181
eth_rpc_client.subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks);
10761182
let finalized_future =
10771183
eth_rpc_client.subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks);
10781184
let res = tokio::try_join!(best_future, finalized_future).map(|_| ());
10791185
if let Err(err) = res {
1080-
panic!("Block subscription task failed: {err:?}",)
1186+
panic!("Block subscription task failed: {err:?}")
10811187
}
10821188
});
1189+
10831190
Ok(eth_rpc_client)
10841191
}

0 commit comments

Comments
 (0)