Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
606 changes: 303 additions & 303 deletions Cargo.lock

Large diffs are not rendered by default.

89 changes: 82 additions & 7 deletions crates/anvil-polkadot/src/api_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ use alloy_serde::WithOtherFields;
use alloy_trie::{EMPTY_ROOT_HASH, KECCAK_EMPTY, TrieAccount};
use anvil_core::eth::{EthRequest, Params as MineParams};
use anvil_rpc::response::ResponseResult;
use chrono::{DateTime, Datelike, Utc};
use codec::{Decode, Encode};
use futures::{StreamExt, channel::mpsc};
use indexmap::IndexMap;
Expand Down Expand Up @@ -390,10 +391,17 @@ impl ApiServer {
"The interval between blocks is too large".to_string(),
));
}
self.mining_engine

// Subscribe to new best blocks.
let receiver = self.eth_rpc_client.block_notifier().map(|sender| sender.subscribe());

let awaited_hash = self
.mining_engine
.mine(blocks.map(|b| b.to()), interval.map(|i| Duration::from_secs(i.to())))
.await
.map_err(Error::Mining)
.map_err(Error::Mining)?;
self.wait_for_hash(receiver, awaited_hash).await?;
Ok(())
}

fn set_interval_mining(&self, interval: u64) -> Result<()> {
Expand Down Expand Up @@ -425,7 +433,10 @@ impl ApiServer {
async fn evm_mine(&self, mine: Option<MineParams<Option<MineOptions>>>) -> Result<String> {
node_info!("evm_mine");

self.mining_engine.evm_mine(mine.and_then(|p| p.params)).await?;
// Subscribe to new best blocks.
let receiver = self.eth_rpc_client.block_notifier().map(|sender| sender.subscribe());
let awaited_hash = self.mining_engine.evm_mine(mine.and_then(|p| p.params)).await?;
self.wait_for_hash(receiver, awaited_hash).await?;
Ok("0x0".to_string())
}

Expand All @@ -434,7 +445,15 @@ impl ApiServer {
mine: Option<MineParams<Option<MineOptions>>>,
) -> Result<Vec<Block>> {
node_info!("evm_mine_detailed");
let mined_blocks = self.mining_engine.do_evm_mine(mine.and_then(|p| p.params)).await?;

// Subscribe to new best blocks.
let receiver = self.eth_rpc_client.block_notifier().map(|sender| sender.subscribe());

let (mined_blocks, awaited_hash) =
self.mining_engine.do_evm_mine(mine.and_then(|p| p.params)).await?;

self.wait_for_hash(receiver, awaited_hash).await?;

let mut blocks = Vec::with_capacity(mined_blocks as usize);
let last_block = self.client.info().best_number as u64;
let starting = last_block - mined_blocks + 1;
Expand Down Expand Up @@ -1455,6 +1474,59 @@ impl ApiServer {

Ok(())
}

async fn wait_for_hash(
&self,
receiver: Option<tokio::sync::broadcast::Receiver<H256>>,
awaited_hash: H256,
) -> Result<()> {
if let Some(mut receiver) = receiver {
tokio::time::timeout(Duration::from_secs(3), async {
loop {
if let Ok(block_hash) = receiver.recv().await {
if let Err(e) = self.log_mined_block(block_hash).await {
node_info!("Failed to log mined block {block_hash:?}: {e:?}");
}
if block_hash == awaited_hash {
break;
}
}
}
})
.await
.map_err(|e| {
Error::InternalError(format!(
"Was not notified about the new best block in time {e:?}."
))
})?;
}
Ok(())
}

async fn log_mined_block(&self, block_hash: H256) -> Result<()> {
let block_timestamp = self.backend.read_timestamp(block_hash)?;
let block_number = self.backend.read_block_number(block_hash)?;
let timestamp = utc_from_millis(block_timestamp)?;
node_info!(" Block Number: {}", block_number);
node_info!(" Block Hash: {:?}", block_hash);
if timestamp.year() > 9999 {
// rf2822 panics with more than 4 digits
node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339());
} else {
node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822());
}
Ok(())
}
}

/// Returns the `Utc` datetime for the given seconds since unix epoch
fn utc_from_millis(millis: u64) -> Result<DateTime<Utc>> {
DateTime::from_timestamp_millis(
millis.try_into().map_err(|err| {
Error::InvalidParams(format!("Could not convert the timestamp: {err:?}"))
})?,
)
.ok_or(Error::InvalidParams("Could not get the utc datetime 😭".to_string()))
}

fn new_contract_info(address: &Address, code_hash: H256, nonce: Nonce) -> ContractInfo {
Expand Down Expand Up @@ -1581,9 +1653,12 @@ async fn create_revive_rpc_client(
.await
.map_err(|err| Error::ReviveRpc(EthRpcError::ClientError(ClientError::SqlxError(err))))?;

let eth_rpc_client = EthRpcClient::new(api, rpc_client, rpc, block_provider, receipt_provider)
.await
.map_err(Error::from)?;
let mut eth_rpc_client =
EthRpcClient::new(api, rpc_client, rpc, block_provider, receipt_provider)
.await
.map_err(Error::from)?;

eth_rpc_client.create_block_notifier();
let eth_rpc_client_clone = eth_rpc_client.clone();
task_spawn_handle.spawn("block-subscription", "None", async move {
let eth_rpc_client = eth_rpc_client_clone;
Expand Down
24 changes: 13 additions & 11 deletions crates/anvil-polkadot/src/substrate_node/mining_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use parking_lot::RwLock;
use polkadot_sdk::{
sc_consensus_manual_seal::{CreatedBlock, EngineCommand, Error as BlockProducingError},
sc_service::TransactionPool,
sp_core,
sp_core::{self, H256},
};
use std::{pin::Pin, sync::Arc};
use substrate_runtime::Hash;
Expand Down Expand Up @@ -134,21 +134,22 @@ impl MiningEngine {
/// * `interval` - Optional time to advance between blocks (in seconds)
///
/// # Returns
/// * `Ok(())` - All blocks were mined successfully
/// * `Ok(H256)` - The hash of the last block mined successfully.
/// * `Err(MiningError)` - Block production failed
pub async fn mine(
&self,
num_blocks: Option<u64>,
interval: Option<Duration>,
) -> Result<(), MiningError> {
) -> Result<H256, MiningError> {
let blocks = num_blocks.unwrap_or(1);
let mut last_hash = H256::zero();
for _ in 0..blocks {
if let Some(interval) = interval {
self.time_manager.increase_time(interval.as_secs());
}
seal_now(&self.seal_command_sender).await?;
last_hash = seal_now(&self.seal_command_sender).await?.hash;
}
Ok(())
Ok(last_hash)
}

/// Ethereum-compatible block mining RPC method.
Expand All @@ -161,10 +162,10 @@ impl MiningEngine {
/// * `opts` - Optional mining parameters including timestamp and block count
///
/// # Returns
/// * `Ok(())` - Success response
/// * `Ok(H256)` - The hash of the last block mined successfully.
/// * `Err(MiningError)` - Mining operation failed
pub async fn evm_mine(&self, opts: Option<MineOptions>) -> Result<(), MiningError> {
self.do_evm_mine(opts).await.map(|_| ())
pub async fn evm_mine(&self, opts: Option<MineOptions>) -> Result<H256, MiningError> {
self.do_evm_mine(opts).await.map(|res| res.1)
}

/// Configure interval-based mining mode.
Expand Down Expand Up @@ -311,8 +312,9 @@ impl MiningEngine {
self.waker.wake();
}

pub async fn do_evm_mine(&self, opts: Option<MineOptions>) -> Result<u64, MiningError> {
pub async fn do_evm_mine(&self, opts: Option<MineOptions>) -> Result<(u64, H256), MiningError> {
let mut blocks_to_mine = 1u64;
let mut last_hash = H256::zero();

if let Some(opts) = opts {
let timestamp = match opts {
Expand All @@ -333,10 +335,10 @@ impl MiningEngine {
}

for _ in 0..blocks_to_mine {
seal_now(&self.seal_command_sender).await?;
last_hash = seal_now(&self.seal_command_sender).await?.hash;
}

Ok(blocks_to_mine)
Ok((blocks_to_mine, last_hash))
}
}

Expand Down
11 changes: 11 additions & 0 deletions crates/anvil-polkadot/src/substrate_node/service/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub enum BackendError {
MissingAuraAuthorities,
#[error("Could not find timestamp in the state")]
MissingTimestamp,
#[error("Could not find block number in the state")]
MissingBlockNumber,
#[error("Unable to decode total issuance {0}")]
DecodeTotalIssuance(codec::Error),
#[error("Unable to decode chain id {0}")]
Expand All @@ -44,6 +46,8 @@ pub enum BackendError {
DecodeCodeInfo(codec::Error),
#[error("Unable to decode timestamp: {0}")]
DecodeTimestamp(codec::Error),
#[error("Unable to decode blockNumber: {0}")]
DecodeBlockNumber(codec::Error),
#[error("Unable to decode aura authorities: {0}")]
DecodeAuraAuthorities(codec::Error),
}
Expand Down Expand Up @@ -72,6 +76,13 @@ impl BackendWithOverlay {
u64::decode(&mut &value[..]).map_err(BackendError::DecodeTimestamp)
}

pub fn read_block_number(&self, hash: Hash) -> Result<u32> {
let key = well_known_keys::BLOCK_NUMBER_KEY;
let value =
self.read_top_state(hash, key.to_vec())?.ok_or(BackendError::MissingBlockNumber)?;
u32::decode(&mut &value[..]).map_err(BackendError::DecodeBlockNumber)
}

pub fn read_chain_id(&self, hash: Hash) -> Result<u64> {
let key = well_known_keys::CHAIN_ID;

Expand Down
8 changes: 3 additions & 5 deletions crates/anvil-polkadot/tests/it/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use anvil_polkadot::{
config::{AnvilNodeConfig, SubstrateNodeConfig},
};
use polkadot_sdk::pallet_revive::{self, evm::Account};
use std::{collections::BTreeMap, path::PathBuf, time::Duration};
use std::{collections::BTreeMap, path::PathBuf};
use subxt::utils::H160;

#[tokio::test(flavor = "multi_thread")]
Expand Down Expand Up @@ -54,7 +54,6 @@ async fn test_genesis_params() {

let latest_block_number = node.best_block_number().await;
assert_eq!(latest_block_number, genesis_block_number + 2);
tokio::time::sleep(Duration::from_millis(400)).await;
assert_eq!(node.eth_best_block().await.number.as_u32(), genesis_block_number + 2);

let hash2 = node.block_hash_by_number(genesis_block_number + 2).await.unwrap();
Expand Down Expand Up @@ -234,13 +233,12 @@ async fn test_coinbase_genesis() {
.with_genesis(Some(Genesis { coinbase: genesis_coinbase, ..Default::default() }));
let substrate_node_config = SubstrateNodeConfig::new(&anvil_node_config);
let mut node = TestNode::new(anvil_node_config.clone(), substrate_node_config).await.unwrap();
unwrap_response::<()>(node.eth_rpc(EthRequest::SetAutomine(true)).await.unwrap()).unwrap();

// Deploy multicall contract
let alith = Account::from(subxt_signer::eth::dev::alith());
let contract_code = get_contract_code("Multicall");
let tx_hash = node.deploy_contract(&contract_code.init, alith.address(), Some(1)).await;
tokio::time::sleep(Duration::from_millis(400)).await;
let tx_hash = node.deploy_contract(&contract_code.init, alith.address(), None).await;
let _ = node.eth_rpc(EthRequest::Mine(None, None)).await.unwrap();

// Get contract address.
let receipt = node.get_transaction_receipt(tx_hash).await;
Expand Down
2 changes: 0 additions & 2 deletions crates/anvil-polkadot/tests/it/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use polkadot_sdk::{
pallet_revive::evm::{Account, TransactionSigned},
sp_core::{H256, U256},
};
use std::time::Duration;

#[tokio::test(flavor = "multi_thread")]
async fn can_sign_transaction() {
Expand Down Expand Up @@ -78,7 +77,6 @@ async fn can_sign_transaction() {
)
.unwrap();
unwrap_response::<()>(node.eth_rpc(EthRequest::Mine(None, None)).await.unwrap()).unwrap();
tokio::time::sleep(Duration::from_millis(400)).await;

let transaction_receipt = node.get_transaction_receipt(tx_hash).await;
assert_eq!(transaction_receipt.from, alith.address());
Expand Down
Loading
Loading