788 lines
25 KiB
Rust
788 lines
25 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//! Test the eth-rpc cli with the kitchensink node.
|
|
//! This only includes basic transaction tests, most of the other tests are in the
|
|
//! [evm-test-suite](https://github.com/paritytech/evm-test-suite) repository.
|
|
|
|
use crate::{
|
|
cli::{self, CliCommand},
|
|
client,
|
|
example::TransactionBuilder,
|
|
subxt_client::{
|
|
self, src_chain::runtime_types::pezpallet_revive::primitives::Code, SrcChainConfig,
|
|
},
|
|
EthRpcClient,
|
|
};
|
|
use anyhow::anyhow;
|
|
use clap::Parser;
|
|
use jsonrpsee::ws_client::{WsClient, WsClientBuilder};
|
|
use pezpallet_revive::{
|
|
create1,
|
|
evm::{
|
|
Account, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, BlockTag,
|
|
HashesOrTransactionInfos, TransactionInfo, H256, U256,
|
|
},
|
|
};
|
|
use std::{sync::Arc, thread};
|
|
use subxt::{
|
|
backend::rpc::RpcClient,
|
|
ext::subxt_rpcs::rpc_params,
|
|
tx::{SubmittableTransaction, TxStatus},
|
|
OnlineClient,
|
|
};
|
|
|
|
const LOG_TARGET: &str = "eth-rpc-tests";
|
|
|
|
/// Create a websocket client with a 120s timeout.
|
|
async fn ws_client_with_retry(url: &str) -> WsClient {
|
|
let timeout = tokio::time::Duration::from_secs(120);
|
|
tokio::time::timeout(timeout, async {
|
|
loop {
|
|
if let Ok(client) = WsClientBuilder::default().build(url).await {
|
|
return client;
|
|
} else {
|
|
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
|
}
|
|
}
|
|
})
|
|
.await
|
|
.expect("Hit timeout")
|
|
}
|
|
|
|
struct SharedResources {
|
|
_node_handle: std::thread::JoinHandle<()>,
|
|
_rpc_handle: std::thread::JoinHandle<()>,
|
|
}
|
|
|
|
impl SharedResources {
|
|
fn start() -> Self {
|
|
// Start pez-revive-dev-node
|
|
let _node_handle = thread::spawn(move || {
|
|
if let Err(e) = pez_revive_dev_node::command::run_with_args(vec![
|
|
"--dev".to_string(),
|
|
"--rpc-port=45789".to_string(),
|
|
"-lerror,pezsc_rpc_server=info,runtime::revive=debug".to_string(),
|
|
]) {
|
|
panic!("Node exited with error: {e:?}");
|
|
}
|
|
});
|
|
|
|
// Start the rpc server.
|
|
let args = CliCommand::parse_from([
|
|
"--dev",
|
|
"--rpc-port=45788",
|
|
"--pez-node-rpc-url=ws://localhost:45789",
|
|
"--no-prometheus",
|
|
"-linfo,eth-rpc=debug",
|
|
]);
|
|
|
|
let _rpc_handle = thread::spawn(move || {
|
|
if let Err(e) = cli::run(args) {
|
|
panic!("eth-rpc exited with error: {e:?}");
|
|
}
|
|
});
|
|
|
|
Self { _node_handle, _rpc_handle }
|
|
}
|
|
|
|
async fn client() -> WsClient {
|
|
ws_client_with_retry("ws://localhost:45788").await
|
|
}
|
|
|
|
fn pez_node_rpc_url() -> &'static str {
|
|
"ws://localhost:45789"
|
|
}
|
|
}
|
|
|
|
macro_rules! unwrap_call_err(
|
|
($err:expr) => {
|
|
match $err.downcast_ref::<jsonrpsee::core::client::Error>().unwrap() {
|
|
jsonrpsee::core::client::Error::Call(call) => call,
|
|
_ => panic!("Expected Call error"),
|
|
}
|
|
}
|
|
);
|
|
|
|
// Helper functions
|
|
/// Prepare multiple EVM transfer transactions with nonce in descending order
|
|
async fn prepare_evm_transactions<Client: EthRpcClient + Sync + Send>(
|
|
client: &Arc<Client>,
|
|
signer: Account,
|
|
recipient: pezpallet_revive::evm::Address,
|
|
amount: U256,
|
|
count: usize,
|
|
) -> anyhow::Result<Vec<TransactionBuilder<Client>>> {
|
|
let start_nonce =
|
|
client.get_transaction_count(signer.address(), BlockTag::Latest.into()).await?;
|
|
|
|
let mut transactions = Vec::new();
|
|
for i in (0..count).rev() {
|
|
let nonce = start_nonce.saturating_add(U256::from(i as u64));
|
|
let tx_builder = TransactionBuilder::new(client)
|
|
.signer(signer.clone())
|
|
.nonce(nonce)
|
|
.value(amount)
|
|
.to(recipient);
|
|
|
|
transactions.push(tx_builder);
|
|
log::trace!(target: LOG_TARGET, "Prepared EVM transaction {}/{count} with nonce: {nonce:?}", i + 1);
|
|
}
|
|
|
|
Ok(transactions)
|
|
}
|
|
|
|
/// Prepare multiple Bizinikiwi transfer transactions with sequential nonces
|
|
async fn prepare_bizinikiwi_transactions(
|
|
node_client: &OnlineClient<SrcChainConfig>,
|
|
signer: &subxt_signer::sr25519::Keypair,
|
|
count: usize,
|
|
) -> anyhow::Result<Vec<SubmittableTransaction<SrcChainConfig, OnlineClient<SrcChainConfig>>>> {
|
|
let mut nonce = node_client.tx().account_nonce(&signer.public_key().into()).await?;
|
|
let mut bizinikiwi_txs = Vec::new();
|
|
for i in 0..count {
|
|
let remark_data = format!("Hello from test {}", i);
|
|
let call = subxt::dynamic::tx(
|
|
"System",
|
|
"remark",
|
|
vec![subxt::dynamic::Value::from_bytes(remark_data.as_bytes())],
|
|
);
|
|
|
|
// Note: Using polkadot config from subxt (external crate)
|
|
let params = subxt::config::polkadot::PolkadotExtrinsicParamsBuilder::new()
|
|
.nonce(nonce)
|
|
.build();
|
|
|
|
let tx = node_client.tx().create_signed(&call, signer, params).await?;
|
|
bizinikiwi_txs.push(tx);
|
|
log::trace!(target: LOG_TARGET, "Prepared bizinikiwi transaction {i}/{count} with nonce: {nonce}");
|
|
nonce += 1 as u64;
|
|
}
|
|
Ok(bizinikiwi_txs)
|
|
}
|
|
|
|
/// Submit multiple transactions and return them without waiting for receipts
|
|
async fn submit_evm_transactions<Client: EthRpcClient + Sync + Send>(
|
|
transactions: Vec<TransactionBuilder<Client>>,
|
|
) -> anyhow::Result<
|
|
Vec<(
|
|
H256,
|
|
pezpallet_revive::evm::GenericTransaction,
|
|
crate::example::SubmittedTransaction<Client>,
|
|
)>,
|
|
> {
|
|
let mut submitted_txs = Vec::new();
|
|
|
|
for tx_builder in transactions {
|
|
let tx = tx_builder.send().await?;
|
|
let hash = tx.hash();
|
|
let generic_tx = tx.generic_transaction();
|
|
submitted_txs.push((hash, generic_tx, tx));
|
|
}
|
|
|
|
Ok(submitted_txs)
|
|
}
|
|
|
|
/// Submit bizinikiwi transactions and return futures for waiting
|
|
async fn submit_bizinikiwi_transactions(
|
|
bizinikiwi_txs: Vec<SubmittableTransaction<SrcChainConfig, OnlineClient<SrcChainConfig>>>,
|
|
) -> Vec<impl std::future::Future<Output = Result<(), anyhow::Error>>> {
|
|
let mut futures = Vec::new();
|
|
|
|
for (i, tx) in bizinikiwi_txs.into_iter().enumerate() {
|
|
let fut = async move {
|
|
match tx.submit_and_watch().await {
|
|
Ok(mut progress) => {
|
|
log::trace!(target: LOG_TARGET, "Bizinikiwi tx {i} submitted");
|
|
while let Some(status) = progress.next().await {
|
|
match status {
|
|
Ok(TxStatus::InFinalizedBlock(block)) |
|
|
Ok(TxStatus::InBestBlock(block)) => {
|
|
log::trace!(target: LOG_TARGET,
|
|
"Bizinikiwi tx {i} included in block {:?}",
|
|
block.block_hash()
|
|
);
|
|
return Ok(());
|
|
},
|
|
Err(e) => return Err(anyhow::anyhow!("Bizinikiwi tx {i} error: {e}")),
|
|
Ok(status) => {
|
|
log::trace!(target: LOG_TARGET, "Bizinikiwi tx {i} status {:?}", status);
|
|
},
|
|
}
|
|
}
|
|
Err(anyhow::anyhow!(
|
|
"Failed to get status of submitted bizinikiwi tx {i}, assuming error"
|
|
))
|
|
},
|
|
Err(e) => Err(anyhow::anyhow!("Failed to submit bizinikiwi tx {i}: {e}")),
|
|
}
|
|
};
|
|
futures.push(fut);
|
|
}
|
|
|
|
futures
|
|
}
|
|
|
|
/// Verify all given transaction hashes are in the specified block and accessible via RPC
|
|
async fn verify_transactions_in_single_block(
|
|
client: &Arc<WsClient>,
|
|
block_number: U256,
|
|
expected_tx_hashes: &[H256],
|
|
) -> anyhow::Result<()> {
|
|
// Fetch the block
|
|
let block = client
|
|
.get_block_by_number(BlockNumberOrTag::U256(block_number), false)
|
|
.await?
|
|
.ok_or_else(|| anyhow!("Block {block_number} should exist"))?;
|
|
|
|
let block_tx_hashes = match &block.transactions {
|
|
HashesOrTransactionInfos::Hashes(hashes) => hashes.clone(),
|
|
HashesOrTransactionInfos::TransactionInfos(infos) =>
|
|
infos.iter().map(|info| info.hash).collect(),
|
|
};
|
|
|
|
if let Some(missing_hash) =
|
|
expected_tx_hashes.iter().find(|hash| !block_tx_hashes.contains(hash))
|
|
{
|
|
return Err(anyhow!("Transaction {missing_hash:?} not found in block {block_number}"));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn run_all_eth_rpc_tests() -> anyhow::Result<()> {
|
|
// start node and rpc server
|
|
let _shared = SharedResources::start();
|
|
let client = Arc::new(SharedResources::client().await);
|
|
|
|
macro_rules! run_tests {
|
|
($($test:ident),+ $(,)?) => {
|
|
$(
|
|
{
|
|
let test_name = stringify!($test);
|
|
log::debug!(target: LOG_TARGET, "Running test: {}", test_name);
|
|
match $test(client.clone()).await {
|
|
Ok(()) => log::debug!(target: LOG_TARGET, "Test passed: {}", test_name),
|
|
Err(err) => panic!("Test {} failed: {err:?}", test_name),
|
|
}
|
|
}
|
|
)+
|
|
};
|
|
}
|
|
|
|
run_tests!(
|
|
test_transfer,
|
|
test_deploy_and_call,
|
|
test_runtime_api_dry_run_addr_works,
|
|
test_invalid_transaction,
|
|
test_evm_blocks_should_match,
|
|
test_evm_blocks_hydrated_should_match,
|
|
test_block_hash_for_tag_with_proper_ethereum_block_hash_works,
|
|
test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails,
|
|
test_block_hash_for_tag_with_block_number_works,
|
|
test_block_hash_for_tag_with_block_tags_works,
|
|
test_multiple_transactions_in_block,
|
|
test_mixed_evm_bizinikiwi_transactions,
|
|
test_runtime_pallets_address_upload_code,
|
|
);
|
|
|
|
log::debug!(target: LOG_TARGET, "All tests completed successfully!");
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_transfer(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let ethan = Account::from(subxt_signer::eth::dev::ethan());
|
|
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
|
|
|
|
let value = 1_000_000_000_000_000_000_000u128.into();
|
|
let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?;
|
|
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
assert_eq!(
|
|
Some(ethan.address()),
|
|
receipt.to,
|
|
"Receipt should have the correct contract address."
|
|
);
|
|
|
|
let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
|
|
assert_eq!(
|
|
Some(value),
|
|
balance.checked_sub(initial_balance),
|
|
"Ethan {:?} {balance:?} should have increased by {value:?} from {initial_balance}.",
|
|
ethan.address()
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_deploy_and_call(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let account = Account::default();
|
|
|
|
// Balance transfer
|
|
let ethan = Account::from(subxt_signer::eth::dev::ethan());
|
|
let initial_balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
|
|
let value = 1_000_000_000_000_000_000_000u128.into();
|
|
let tx = TransactionBuilder::new(&client).value(value).to(ethan.address()).send().await?;
|
|
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
assert_eq!(
|
|
Some(ethan.address()),
|
|
receipt.to,
|
|
"Receipt should have the correct contract address."
|
|
);
|
|
|
|
let balance = client.get_balance(ethan.address(), BlockTag::Latest.into()).await?;
|
|
assert_eq!(
|
|
Some(value),
|
|
balance.checked_sub(initial_balance),
|
|
"Ethan {:?} {balance:?} should have increased by {value:?} from {initial_balance}.",
|
|
ethan.address()
|
|
);
|
|
|
|
// Deploy contract
|
|
let data = b"hello world".to_vec();
|
|
let value = U256::from(5_000_000_000_000u128);
|
|
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
let input = bytes.into_iter().chain(data.clone()).collect::<Vec<u8>>();
|
|
let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?;
|
|
let tx = TransactionBuilder::new(&client).value(value).input(input).send().await?;
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
let contract_address = create1(&account.address(), nonce.try_into().unwrap());
|
|
assert_eq!(
|
|
Some(contract_address),
|
|
receipt.contract_address,
|
|
"Contract should be deployed at {contract_address:?}."
|
|
);
|
|
|
|
let nonce_after_deploy =
|
|
client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?;
|
|
|
|
assert_eq!(nonce_after_deploy - nonce, U256::from(1), "Nonce should have increased by 1");
|
|
|
|
let initial_balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
|
|
assert_eq!(
|
|
value, initial_balance,
|
|
"Contract {contract_address:?} balance should be the same as the value sent ({value})."
|
|
);
|
|
|
|
// Call contract
|
|
let tx = TransactionBuilder::new(&client)
|
|
.value(value)
|
|
.to(contract_address)
|
|
.send()
|
|
.await?;
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
|
|
assert_eq!(
|
|
Some(contract_address),
|
|
receipt.to,
|
|
"Receipt should have the correct contract address {contract_address:?}."
|
|
);
|
|
|
|
let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
|
|
assert_eq!(Some(value), balance.checked_sub(initial_balance), "Contract {contract_address:?} Balance {balance} should have increased from {initial_balance} by {value}.");
|
|
|
|
// Balance transfer to contract
|
|
let initial_balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
|
|
let tx = TransactionBuilder::new(&client)
|
|
.value(value)
|
|
.to(contract_address)
|
|
.send()
|
|
.await?;
|
|
|
|
tx.wait_for_receipt().await?;
|
|
|
|
let balance = client.get_balance(contract_address, BlockTag::Latest.into()).await?;
|
|
|
|
assert_eq!(
|
|
Some(value),
|
|
balance.checked_sub(initial_balance),
|
|
"Balance {balance} should have increased from {initial_balance} by {value}."
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_runtime_api_dry_run_addr_works(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let account = Account::default();
|
|
let origin: [u8; 32] = account.bizinikiwi_account().into();
|
|
let data = b"hello world".to_vec();
|
|
let value = 5_000_000_000_000u128;
|
|
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
|
|
let payload = subxt_client::apis().revive_api().instantiate(
|
|
subxt::utils::AccountId32(origin),
|
|
value,
|
|
None,
|
|
None,
|
|
Code::Upload(bytes),
|
|
data,
|
|
None,
|
|
);
|
|
|
|
// runtime_api.at_latest() uses the latest finalized block, query nonce accordingly
|
|
let nonce = client
|
|
.get_transaction_count(account.address(), BlockTag::Finalized.into())
|
|
.await?;
|
|
let contract_address = create1(&account.address(), nonce.try_into().unwrap());
|
|
|
|
let c = OnlineClient::<SrcChainConfig>::from_url("ws://localhost:45789").await?;
|
|
let res = c.runtime_api().at_latest().await?.call(payload).await?.result.unwrap();
|
|
|
|
assert_eq!(res.addr, contract_address);
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_invalid_transaction(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let ethan = Account::from(subxt_signer::eth::dev::ethan());
|
|
|
|
let err = TransactionBuilder::new(&client)
|
|
.value(U256::from(1_000_000_000_000u128))
|
|
.to(ethan.address())
|
|
.mutate(|tx| tx.chain_id = Some(42u32.into()))
|
|
.send()
|
|
.await
|
|
.unwrap_err();
|
|
|
|
let call_err = unwrap_call_err!(err.source().unwrap());
|
|
assert_eq!(call_err.message(), "Invalid Transaction");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_evm_block_from_storage(
|
|
node_client: &OnlineClient<SrcChainConfig>,
|
|
pez_node_rpc_client: &RpcClient,
|
|
block_number: U256,
|
|
) -> anyhow::Result<Block> {
|
|
let block_hash: H256 = pez_node_rpc_client
|
|
.request("chain_getBlockHash", rpc_params![block_number])
|
|
.await
|
|
.unwrap();
|
|
|
|
let query = subxt_client::storage().revive().ethereum_block();
|
|
let Some(block) = node_client.storage().at(block_hash).fetch(&query).await.unwrap() else {
|
|
return Err(anyhow!("EVM block {block_hash:?} not found"));
|
|
};
|
|
Ok(block.0)
|
|
}
|
|
|
|
async fn test_evm_blocks_should_match(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let (node_client, pez_node_rpc_client, _) =
|
|
client::connect(SharedResources::pez_node_rpc_url()).await.unwrap();
|
|
|
|
// Deploy a contract to have some interesting blocks
|
|
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
let value = U256::from(5_000_000_000_000u128);
|
|
let tx = TransactionBuilder::new(&client)
|
|
.value(value)
|
|
.input(bytes.to_vec())
|
|
.send()
|
|
.await?;
|
|
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
let block_number = receipt.block_number;
|
|
let block_hash = receipt.block_hash;
|
|
log::trace!(target: LOG_TARGET, "block_number = {block_number:?}");
|
|
log::trace!(target: LOG_TARGET, "tx hash = {:?}", tx.hash());
|
|
|
|
let evm_block_from_storage =
|
|
get_evm_block_from_storage(&node_client, &pez_node_rpc_client, block_number).await?;
|
|
|
|
// Fetch the block immediately (should come from storage EthereumBlock)
|
|
let evm_block_from_rpc_by_number = client
|
|
.get_block_by_number(BlockNumberOrTag::U256(block_number.into()), false)
|
|
.await?
|
|
.expect("Block should exist");
|
|
let evm_block_from_rpc_by_hash =
|
|
client.get_block_by_hash(block_hash, false).await?.expect("Block should exist");
|
|
|
|
assert!(
|
|
matches!(
|
|
evm_block_from_rpc_by_number.transactions,
|
|
pezpallet_revive::evm::HashesOrTransactionInfos::Hashes(_)
|
|
),
|
|
"Block should not have hydrated transactions"
|
|
);
|
|
|
|
// All EVM blocks must match
|
|
assert_eq!(evm_block_from_storage, evm_block_from_rpc_by_number, "EVM blocks should match");
|
|
assert_eq!(evm_block_from_storage, evm_block_from_rpc_by_hash, "EVM blocks should match");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_evm_blocks_hydrated_should_match(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
// Deploy a contract to have some transactions in the block
|
|
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
let value = U256::from(5_000_000_000_000u128);
|
|
let signer = Account::default();
|
|
let signer_copy = Account::default();
|
|
let tx = TransactionBuilder::new(&client)
|
|
.value(value)
|
|
.signer(signer)
|
|
.input(bytes.to_vec())
|
|
.send()
|
|
.await?;
|
|
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
let block_number = receipt.block_number;
|
|
let block_hash = receipt.block_hash;
|
|
log::trace!(target: LOG_TARGET, "block_number = {block_number:?}");
|
|
log::trace!(target: LOG_TARGET, "tx hash = {:?}", tx.hash());
|
|
|
|
// Fetch the block with hydrated transactions via RPC (by number and by hash)
|
|
let evm_block_from_rpc_by_number = client
|
|
.get_block_by_number(BlockNumberOrTag::U256(block_number.into()), true)
|
|
.await?
|
|
.expect("Block should exist");
|
|
let evm_block_from_rpc_by_hash =
|
|
client.get_block_by_hash(block_hash, true).await?.expect("Block should exist");
|
|
|
|
// Both blocks should be identical
|
|
assert_eq!(
|
|
evm_block_from_rpc_by_number, evm_block_from_rpc_by_hash,
|
|
"Hydrated EVM blocks should match"
|
|
);
|
|
|
|
// Verify transaction info
|
|
let unsigned_tx = tx
|
|
.generic_transaction()
|
|
.try_into_unsigned()
|
|
.expect("Transaction shall be converted");
|
|
let signed_tx = signer_copy.sign_transaction(unsigned_tx);
|
|
let expected_tx_info = TransactionInfo::new(&receipt, signed_tx);
|
|
|
|
let tx_info = if let HashesOrTransactionInfos::TransactionInfos(tx_infos) =
|
|
evm_block_from_rpc_by_number.transactions
|
|
{
|
|
tx_infos[0].clone()
|
|
} else {
|
|
panic!("Expected hydrated transactions");
|
|
};
|
|
assert_eq!(expected_tx_info, tx_info, "TransationInfos should match");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_block_hash_for_tag_with_proper_ethereum_block_hash_works(
|
|
client: Arc<WsClient>,
|
|
) -> anyhow::Result<()> {
|
|
// Deploy a transaction to create a block with transactions
|
|
let (bytes, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
let value = U256::from(5_000_000_000_000u128);
|
|
let tx = TransactionBuilder::new(&client)
|
|
.value(value)
|
|
.input(bytes.to_vec())
|
|
.send()
|
|
.await?;
|
|
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
let ethereum_block_hash = receipt.block_hash;
|
|
|
|
log::trace!(target: LOG_TARGET, "Testing with Ethereum block hash: {ethereum_block_hash:?}");
|
|
|
|
let block_by_hash = client
|
|
.get_block_by_hash(ethereum_block_hash, false)
|
|
.await?
|
|
.expect("Block should exist");
|
|
|
|
let account = Account::default();
|
|
let balance = client.get_balance(account.address(), ethereum_block_hash.into()).await?;
|
|
|
|
assert!(balance >= U256::zero(), "Balance should be retrievable with Ethereum hash");
|
|
assert_eq!(block_by_hash.hash, ethereum_block_hash, "Block hash should match");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_block_hash_for_tag_with_invalid_ethereum_block_hash_fails(
|
|
client: Arc<WsClient>,
|
|
) -> anyhow::Result<()> {
|
|
let fake_eth_hash = H256::from([0x42u8; 32]);
|
|
|
|
log::trace!(target: LOG_TARGET, "Testing with fake Ethereum hash: {fake_eth_hash:?}");
|
|
|
|
let account = Account::default();
|
|
let result = client.get_balance(account.address(), fake_eth_hash.into()).await;
|
|
|
|
assert!(result.is_err(), "Should fail with non-existent Ethereum hash");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_block_hash_for_tag_with_block_number_works(
|
|
client: Arc<WsClient>,
|
|
) -> anyhow::Result<()> {
|
|
let block_number = client.block_number().await?;
|
|
|
|
log::trace!(target: LOG_TARGET, "Testing with block number: {block_number}");
|
|
|
|
let account = Account::default();
|
|
let balance = client
|
|
.get_balance(account.address(), BlockNumberOrTagOrHash::BlockNumber(block_number))
|
|
.await?;
|
|
|
|
assert!(balance >= U256::zero(), "Balance should be retrievable with block number");
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_block_hash_for_tag_with_block_tags_works(
|
|
client: Arc<WsClient>,
|
|
) -> anyhow::Result<()> {
|
|
let account = Account::default();
|
|
|
|
let tags = vec![
|
|
BlockTag::Latest,
|
|
BlockTag::Finalized,
|
|
BlockTag::Safe,
|
|
BlockTag::Earliest,
|
|
BlockTag::Pending,
|
|
];
|
|
|
|
for tag in tags {
|
|
let balance = client.get_balance(account.address(), tag.clone().into()).await?;
|
|
|
|
assert!(balance >= U256::zero(), "Balance should be retrievable with tag {tag:?}");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_multiple_transactions_in_block(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let num_transactions = 20;
|
|
let alith = Account::default();
|
|
let ethan = Account::from(subxt_signer::eth::dev::ethan());
|
|
let amount = U256::from(1_000_000_000_000_000_000u128);
|
|
|
|
// Prepare EVM transfer transactions
|
|
let transactions =
|
|
prepare_evm_transactions(&client, alith, ethan.address(), amount, num_transactions).await?;
|
|
|
|
// Submit all transactions
|
|
let submitted_txs = submit_evm_transactions(transactions).await?;
|
|
let tx_hashes: Vec<H256> = submitted_txs.iter().map(|(hash, _, _)| *hash).collect();
|
|
log::trace!(target: LOG_TARGET, "Submitted {} transactions", submitted_txs.len());
|
|
|
|
// All transactions should be included in the same block since nonces are in descending order
|
|
let first_receipt = submitted_txs[0].2.wait_for_receipt().await?;
|
|
|
|
// Fetch and verify block contains all transactions
|
|
verify_transactions_in_single_block(&client, first_receipt.block_number, &tx_hashes).await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_mixed_evm_bizinikiwi_transactions(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let num_evm_txs = 10;
|
|
let num_bizinikiwi_txs = 7;
|
|
|
|
let alith = Account::default();
|
|
let ethan = Account::from(subxt_signer::eth::dev::ethan());
|
|
let amount = U256::from(500_000_000_000_000_000u128);
|
|
|
|
// Prepare EVM transactions
|
|
log::trace!(target: LOG_TARGET, "Creating {num_evm_txs} EVM transfer transactions");
|
|
let evm_transactions =
|
|
prepare_evm_transactions(&client, alith, ethan.address(), amount, num_evm_txs).await?;
|
|
|
|
// Prepare bizinikiwi transactions (simple remarks)
|
|
log::trace!(target: LOG_TARGET, "Creating {num_bizinikiwi_txs} bizinikiwi remark transactions");
|
|
let alice_signer = subxt_signer::sr25519::dev::alice();
|
|
let (node_client, _, _) = client::connect(SharedResources::pez_node_rpc_url()).await.unwrap();
|
|
|
|
let bizinikiwi_txs =
|
|
prepare_bizinikiwi_transactions(&node_client, &alice_signer, num_bizinikiwi_txs).await?;
|
|
|
|
log::trace!(target: LOG_TARGET, "Submitting {num_evm_txs} EVM and {num_bizinikiwi_txs} bizinikiwi transactions");
|
|
|
|
// Submit EVM transactions
|
|
let evm_submitted = submit_evm_transactions(evm_transactions).await?;
|
|
let evm_tx_hashes: Vec<H256> = evm_submitted.iter().map(|(hash, _, _)| *hash).collect();
|
|
|
|
// Submit bizinikiwi transactions
|
|
let bizinikiwi_futures = submit_bizinikiwi_transactions(bizinikiwi_txs).await;
|
|
|
|
// Wait for first EVM receipt and all bizinikiwi transactions in parallel
|
|
let (evm_first_receipt_result, _bizinikiwi_results) = tokio::join!(
|
|
async { evm_submitted[0].2.wait_for_receipt().await },
|
|
futures::future::join_all(bizinikiwi_futures)
|
|
);
|
|
// Handle the EVM receipt result
|
|
let evm_first_receipt = evm_first_receipt_result?;
|
|
|
|
// Fetch and verify block contains all transactions
|
|
verify_transactions_in_single_block(&client, evm_first_receipt.block_number, &evm_tx_hashes)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn test_runtime_pallets_address_upload_code(client: Arc<WsClient>) -> anyhow::Result<()> {
|
|
let (node_client, pez_node_rpc_client, _) =
|
|
client::connect(SharedResources::pez_node_rpc_url()).await?;
|
|
|
|
let (bytecode, _) = pezpallet_revive_fixtures::compile_module("dummy")?;
|
|
let signer = Account::default();
|
|
|
|
// Helper function to get bizinikiwi block hash from EVM block number
|
|
let get_bizinikiwi_block_hash = |block_number: U256| {
|
|
let rpc_client = pez_node_rpc_client.clone();
|
|
async move {
|
|
rpc_client
|
|
.request::<pezsp_core::H256>("chain_getBlockHash", rpc_params![block_number])
|
|
.await
|
|
}
|
|
};
|
|
|
|
// Step 1: Encode the Bizinikiwi upload_code call
|
|
let upload_call = subxt::dynamic::tx(
|
|
"Revive",
|
|
"upload_code",
|
|
vec![
|
|
subxt::dynamic::Value::from_bytes(&bytecode),
|
|
subxt::dynamic::Value::u128(u128::max_value()), // storage_deposit_limit
|
|
],
|
|
);
|
|
let encoded_call = node_client.tx().call_data(&upload_call)?;
|
|
|
|
// Step 2: Send the encoded call to RUNTIME_PALLETS_ADDR
|
|
let tx = TransactionBuilder::new(&client)
|
|
.signer(signer.clone())
|
|
.to(pezpallet_revive::RUNTIME_PALLETS_ADDR)
|
|
.input(encoded_call.clone())
|
|
.send()
|
|
.await?;
|
|
|
|
// Step 3: Wait for receipt
|
|
let receipt = tx.wait_for_receipt().await?;
|
|
|
|
// Step 4: Verify transaction was successful
|
|
assert_eq!(
|
|
receipt.status.unwrap_or(U256::zero()),
|
|
U256::one(),
|
|
"Transaction should be successful"
|
|
);
|
|
|
|
// Step 5: Verify the code was actually uploaded
|
|
let code_hash = H256(pezsp_io::hashing::keccak_256(&bytecode));
|
|
let query = subxt_client::storage().revive().pristine_code(code_hash);
|
|
let block_hash: pezsp_core::H256 = get_bizinikiwi_block_hash(receipt.block_number).await?;
|
|
let stored_code = node_client.storage().at(block_hash).fetch(&query).await?;
|
|
assert!(stored_code.is_some(), "Code with hash {code_hash:?} should exist in storage");
|
|
assert_eq!(stored_code.unwrap(), bytecode, "Stored code should match the uploaded bytecode");
|
|
|
|
Ok(())
|
|
}
|