diff --git a/Cargo.lock b/Cargo.lock index b351a18ece36..2fecb3a5ebba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,8 +1257,8 @@ dependencies = [ [[package]] name = "antelope-client" -version = "0.3.0" -source = "git+https://github.com/telosnetwork/antelope-rs?branch=master#fea2203b2edb5cfcedd5365031e5286b47dc5c66" +version = "0.3.1" +source = "git+https://github.com/telosnetwork/antelope-rs?rev=c485ff3e0a3e247c85c204b41144765accefbbc3#c485ff3e0a3e247c85c204b41144765accefbbc3" dependencies = [ "antelope-client-macros", "async-trait", @@ -9425,11 +9425,12 @@ dependencies = [ "alloy-signer-local", "alloy-sol-types", "alloy-transport-http 0.4.2", - "antelope-client 0.3.0", + "antelope-client 0.3.1", "clap", "derive_more 1.0.0", "env_logger 0.11.5", "eyre", + "lazy_static", "reqwest 0.12.8", "reth", "reth-auto-seal-consensus", @@ -10610,7 +10611,7 @@ dependencies = [ "alloy-network 0.4.2", "alloy-primitives", "alloy-rpc-types 0.4.2", - "antelope-client 0.3.0", + "antelope-client 0.3.1", "async-trait", "derive_more 1.0.0", "jsonrpsee-types", diff --git a/Cargo.toml b/Cargo.toml index 62025e2ea230..684cf781368c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -601,7 +601,8 @@ reth-node-telos = { path = "crates/telos/node" } reth-telos-rpc = { path = "crates/telos/rpc" } reth-telos-primitives-traits = { path = "crates/telos/primitives-traits" } reth-telos-rpc-engine-api = { path = "crates/telos/rpc-engine-api" } -antelope-client = { git = "https://github.com/telosnetwork/antelope-rs", branch = "master" } +# antelope-client = { git = "https://github.com/telosnetwork/antelope-rs", branch = "master" } +antelope-client = { git = "https://github.com/telosnetwork/antelope-rs", rev = "c485ff3e0a3e247c85c204b41144765accefbbc3" } [patch.crates-io] #alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "8c499409"} diff --git a/crates/telos/node/Cargo.toml b/crates/telos/node/Cargo.toml index a530dd01d848..cf5383cd6229 100644 --- a/crates/telos/node/Cargo.toml +++ b/crates/telos/node/Cargo.toml @@ -44,6 +44,7 @@ serde_json = "1.0.122" tokio = "1.39.2" tracing = "0.1.40" tracing-subscriber = "0.3.18" +lazy_static = "1.5.0" [features] telos = [ diff --git a/crates/telos/node/src/two_way_storage_compare.rs b/crates/telos/node/src/two_way_storage_compare.rs index e39c03d63f6d..ceff51e25894 100644 --- a/crates/telos/node/src/two_way_storage_compare.rs +++ b/crates/telos/node/src/two_way_storage_compare.rs @@ -16,20 +16,20 @@ use reth::providers::StateProviderBox; use reth_db::{PlainAccountState, PlainStorageState}; #[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] -struct AccountRow { - index: u64, - address: Checksum160, - account: Name, - nonce: u64, - code: Vec, - balance: Checksum256, +pub struct AccountRow { + pub index: u64, + pub address: Checksum160, + pub account: Name, + pub nonce: u64, + pub code: Vec, + pub balance: Checksum256, } #[derive(Debug, Clone, Default, Serialize, Deserialize, StructPacker)] -struct AccountStateRow { - index: u64, - key: Checksum256, - value: Checksum256, +pub struct AccountStateRow { + pub index: u64, + pub key: Checksum256, + pub value: Checksum256, } /// This struct holds matching statistics diff --git a/crates/telos/node/tests/integration.rs b/crates/telos/node/tests/integration.rs index e6f09a9d715d..6cd29620b6fa 100644 --- a/crates/telos/node/tests/integration.rs +++ b/crates/telos/node/tests/integration.rs @@ -1,145 +1,28 @@ use alloy_provider::{Provider, ProviderBuilder}; -use antelope::api::client::{APIClient, DefaultProvider}; use reqwest::Url; use reth::{ - args::RpcServerArgs, - builder::{NodeBuilder, NodeConfig}, + builder::NodeBuilder, tasks::TaskManager, }; -use reth_chainspec::{ChainSpec, ChainSpecBuilder, TEVMTESTNET}; use reth_e2e_test_utils::node::NodeTestContext; use reth_node_telos::{TelosArgs, TelosNode}; use reth_telos_rpc::TelosClient; -use std::{fs, path::PathBuf, sync::Arc, time::Duration}; -use telos_consensus_client::{ - client::ConsensusClient, - config::{AppConfig, CliArgs}, - main_utils::build_consensus_client, -}; -use telos_translator_rs::{ - block::TelosEVMBlock, translator::Translator, types::translator_types::ChainId, -}; -use testcontainers::{ - core::ContainerPort::Tcp, runners::AsyncRunner, ContainerAsync, GenericImage, -}; +use std::time::Duration; +use telos_translator_rs::block::TelosEVMBlock; +use testcontainers::runners::AsyncRunner; use tokio::sync::mpsc; +use crate::utils::runners::{build_consensus_and_translator, CONTAINER_LAST_EVM_BLOCK, CONTAINER_NAME, CONTAINER_TAG, init_reth, start_ship, TelosRethNodeHandle}; pub mod live_test_runner; +pub mod utils; -struct TelosRethNodeHandle { - execution_port: u16, - jwt_secret: String, -} - -const CONTAINER_TAG: &str = - "v0.1.11@sha256:d138f2e08db108d5d420b4db99a57fb9d45a3ee3e0f0faa7d4c4a065f7f018ce"; - -// This is the last block in the container, after this block the node is done syncing and is running live -const CONTAINER_LAST_EVM_BLOCK: u64 = 1010; - -async fn start_ship() -> ContainerAsync { - // Change this container to a local image if using new ship data, - // then make sure to update the ship data in the testcontainer-nodeos-evm repo and build a new version - - // The tag for this image needs to come from the Github packages UI, under the "OS/Arch" tab - // and should be the tag for linux/amd64 - let container: ContainerAsync = - GenericImage::new("ghcr.io/telosnetwork/testcontainer-nodeos-evm", CONTAINER_TAG) - .with_exposed_port(Tcp(8888)) - .with_exposed_port(Tcp(18999)) - .start() - .await - .unwrap(); - - let port_8888 = container.get_host_port_ipv4(8888).await.unwrap(); - - let api_base_url = format!("http://localhost:{port_8888}"); - let api_client = APIClient::::default_provider(api_base_url, Some(1)).unwrap(); - - let mut last_block = 0; - - loop { - let Ok(info) = api_client.v1_chain.get_info().await else { - println!("Waiting for telos node to produce blocks..."); - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - continue; - }; - if last_block != 0 && info.head_block_num > last_block { - break; - } - last_block = info.head_block_num; - } - - container -} - -fn init_reth() -> eyre::Result<(NodeConfig, String)> { - let chain_spec = Arc::new( - ChainSpecBuilder::default() - .chain(TEVMTESTNET.chain) - .genesis(TEVMTESTNET.genesis.clone()) - .frontier_activated() - .homestead_activated() - .tangerine_whistle_activated() - .spurious_dragon_activated() - .byzantium_activated() - .constantinople_activated() - .petersburg_activated() - .istanbul_activated() - .berlin_activated() - .build(), - ); - - let mut rpc_config = RpcServerArgs::default().with_unused_ports().with_http(); - rpc_config.auth_jwtsecret = Some(PathBuf::from("tests/assets/jwt.hex")); - - // Node setup - let node_config = NodeConfig::test().with_chain(chain_spec).with_rpc(rpc_config.clone()); - - let jwt = fs::read_to_string(node_config.rpc.auth_jwtsecret.clone().unwrap())?; - Ok((node_config, jwt)) -} - -async fn build_consensus_and_translator( - reth_handle: TelosRethNodeHandle, - ship_port: u16, - chain_port: u16, -) -> (ConsensusClient, Translator) { - let config = AppConfig { - log_level: "debug".to_string(), - chain_id: ChainId(41), - execution_endpoint: format!("http://localhost:{}", reth_handle.execution_port), - jwt_secret: reth_handle.jwt_secret, - ship_endpoint: format!("ws://localhost:{ship_port}"), - chain_endpoint: format!("http://localhost:{chain_port}"), - batch_size: 1, - prev_hash: "b25034033c9ca7a40e879ddcc29cf69071a22df06688b5fe8cc2d68b4e0528f9".to_string(), - validate_hash: None, - evm_start_block: 1, - // TODO: Determine a good stop block and test it here - evm_stop_block: None, - data_path: "temp/db".to_string(), - block_checkpoint_interval: 1000, - maximum_sync_range: 100000, - latest_blocks_in_db_num: 100, - max_retry: None, - retry_interval: None, - }; - - let cli_args = CliArgs { config: "".to_string(), clean: true }; - - let c = build_consensus_client(&cli_args, config).await.unwrap(); - let translator = Translator::new((&c.config).into()); - - (c, translator) -} #[tokio::test] async fn testing_chain_sync() { tracing_subscriber::fmt::init(); println!("Starting test node"); - let container = start_ship().await; + let container = start_ship(CONTAINER_NAME, CONTAINER_TAG).await; let chain_port = container.get_host_port_ipv4(8888).await.unwrap(); let ship_port = container.get_host_port_ipv4(18999).await.unwrap(); diff --git a/crates/telos/node/tests/integration_lite.rs b/crates/telos/node/tests/integration_lite.rs new file mode 100644 index 000000000000..2c017e74ac2f --- /dev/null +++ b/crates/telos/node/tests/integration_lite.rs @@ -0,0 +1,389 @@ +use std::str::FromStr; +use antelope::api::client::{APIClient, DefaultProvider}; +use reth::{ + builder::NodeBuilder, + tasks::TaskManager, +}; +use reth_e2e_test_utils::node::NodeTestContext; +use reth_node_telos::{TelosArgs, TelosNode}; +use reth_telos_rpc::TelosClient; +use std::time::Duration; +use alloy_network::{Ethereum, EthereumWallet, ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{Address, B256, Bytes, keccak256, U256}; +use alloy_primitives::hex::FromHex; +use alloy_primitives::TxKind::Create; +use telos_translator_rs::block::TelosEVMBlock; +use tokio::sync::mpsc; +use tracing::{info, warn}; +use reth::primitives::BlockId; +use alloy_rpc_types::{AccessList, AccessListItem, BlockTransactionsKind}; +use alloy_transport_http::Http; +use antelope::chain::action::PermissionLevel; +use antelope::chain::private_key::PrivateKey; +use antelope::name; +use antelope::chain::name::Name; + +pub mod utils; +use crate::utils::cleos_evm::{EOSIO_ADDR, EOSIO_PKEY, EOSIO_WALLET, get_nonce, multi_raw_eth_tx, sign_native_tx}; +use crate::utils::runners::{build_consensus_and_translator, CONTAINER_LAST_EVM_BLOCK_LITE, CONTAINER_NAME_LITE, CONTAINER_TAG_LITE, init_reth, start_ship, TelosRethNodeHandle}; + +use alloy_provider::{Identity, Provider, ProviderBuilder, ReqwestProvider}; +use alloy_provider::fillers::{FillProvider, JoinFill, WalletFiller}; +use alloy_rpc_types::BlockNumberOrTag::Latest; +use alloy_sol_types::{sol, SolEvent}; +use reqwest::{Client, Url}; +use reth::rpc::types::{TransactionInput, TransactionRequest}; + +pub type TestProvider = FillProvider>, ReqwestProvider, Http, Ethereum>; + +#[tokio::test] +async fn testing_lite_chain() { + env_logger::builder().is_test(true).try_init().unwrap(); + + info!("Starting test node"); + let container = start_ship(CONTAINER_NAME_LITE, CONTAINER_TAG_LITE).await; + let chain_port = container.get_host_port_ipv4(8888).await.unwrap(); + let ship_port = container.get_host_port_ipv4(18999).await.unwrap(); + + let telos_url = format!("http://localhost:{}", chain_port); + let telos_client = APIClient::::default_provider(telos_url, Some(1)).unwrap(); + + let (node_config, jwt_secret) = init_reth().unwrap(); + + let exec = TaskManager::current(); + let exec = exec.executor(); + + reth_tracing::init_test_tracing(); + + let telos_args = TelosArgs { + telos_endpoint: Some(format!("http://localhost:{chain_port}")), + signer_account: Some("rpc.evm".to_string()), + signer_permission: Some("active".to_string()), + signer_key: Some("5Jr65kdYmn33C3UabzhmWDm2PuqbRfPuDStts3ZFNSBLM7TqaiL".to_string()), + gas_cache_seconds: None, + experimental: false, + persistence_threshold: 0, + memory_block_buffer_target: 1, + max_execute_block_batch_size: 100, + two_way_storage_compare: false, + block_delta: None, + }; + + let node_handle = NodeBuilder::new(node_config.clone()) + .testing_node(exec) + .node(TelosNode::new(telos_args.clone())) + .extend_rpc_modules(move |ctx| { + if telos_args.telos_endpoint.is_some() { + ctx.registry.eth_api().set_telos_client(TelosClient::new(telos_args.into())); + } + + Ok(()) + }) + .launch() + .await + .unwrap(); + + let execution_port = node_handle.node.auth_server_handle().local_addr().port(); + let rpc_port = node_handle.node.rpc_server_handles.rpc.http_local_addr().unwrap().port(); + let reth_handle = TelosRethNodeHandle { execution_port, jwt_secret }; + info!("Starting Reth on RPC port {}!", rpc_port); + let _ = NodeTestContext::new(node_handle.node.clone()).await.unwrap(); + info!("Starting consensus on RPC port {}!", rpc_port); + let (client, translator) = + build_consensus_and_translator(reth_handle, ship_port, chain_port).await; + + let consensus_shutdown = client.shutdown_handle(); + let translator_shutdown = translator.shutdown_handle(); + + let (block_sender, block_receiver) = mpsc::channel::(1000); + + info!("Telos consensus client starting, awaiting result..."); + let client_handle = tokio::spawn(client.run(block_receiver)); + + info!("Telos translator client is starting..."); + let translator_handle = tokio::spawn(translator.launch(Some(block_sender))); + + let rpc_url = Url::from(format!("http://localhost:{}", rpc_port).parse().unwrap()); + let reth_provider = ProviderBuilder::new() + .wallet(EOSIO_WALLET.clone()) + .on_http(rpc_url.clone()); + + loop { + tokio::time::sleep(Duration::from_secs(1)).await; + let latest_block = reth_provider.get_block_number().await.unwrap(); + info!("Latest block: {latest_block}"); + if client_handle.is_finished() { + _ = translator_shutdown.shutdown().await.unwrap(); + break; + } + if latest_block > CONTAINER_LAST_EVM_BLOCK_LITE { + break; + } + } + + run_tests(&telos_client, &reth_provider).await; + + _ = translator_shutdown.shutdown().await.unwrap(); + _ = consensus_shutdown.shutdown().await.unwrap(); + + info!("Client shutdown done."); + + _ = tokio::join!(client_handle, translator_handle); + info!("Translator shutdown done."); +} + +pub async fn run_tests( + telos_client: &APIClient, + reth_provider: &TestProvider +) { + let balance = reth_provider.get_balance(EOSIO_ADDR.clone()).await.unwrap(); + info!("Running live tests using address: {:?} with balance: {:?}", EOSIO_ADDR.to_string(), balance); + + let block = reth_provider.get_block(BlockId::latest(), BlockTransactionsKind::Full).await; + info!("Latest block:\n {:?}", block); + + test_1k_txs( + telos_client, + reth_provider, + Address::from_hex("0000000000000000deadbeef0000000000000000").unwrap() + ).await; + + test_blocknum_onchain(reth_provider).await; +} + +pub async fn test_blocknum_onchain(reth_provider: &TestProvider) { + sol! { + #[sol(rpc, bytecode="6080604052348015600e575f80fd5b5060ef8061001b5f395ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c80637f6c6f101460345780638fb82b0214604e575b5f80fd5b603a6056565b6040516045919060a2565b60405180910390f35b6054605d565b005b5f43905090565b437fc04eeb4cfe0799838abac8fa75bca975bff679179886c80c84a7b93229a1a61860405160405180910390a2565b5f819050919050565b609c81608c565b82525050565b5f60208201905060b35f8301846095565b9291505056fea264697066735822122003482ecf0ea4d820deb6b5ebd2755b67c3c8d4fb9ed50a8b4e0bce59613552df64736f6c634300081a0033")] + contract BlockNumChecker { + + event BlockNumber(uint256 indexed number); + + function getBlockNum() public view returns (uint) { + return block.number; + } + + function logBlockNum() public { + emit BlockNumber(block.number); + } + } + } + + info!("Deploying contract using address {}", EOSIO_ADDR.to_string()); + + let nonce = reth_provider.get_transaction_count(EOSIO_ADDR.clone()).await.unwrap(); + let chain_id = reth_provider.get_chain_id().await.unwrap(); + let gas_price = reth_provider.get_gas_price().await.unwrap(); + + let legacy_tx = alloy_consensus::TxLegacy { + chain_id: Some(chain_id), + nonce, + gas_price: gas_price.into(), + gas_limit: 20_000_000, + to: Create, + value: U256::ZERO, + input: BlockNumChecker::BYTECODE.to_vec().into(), + }; + + let legacy_tx_request = TransactionRequest { + from: Some(EOSIO_ADDR.clone()), + to: Some(legacy_tx.to), + gas: Some(legacy_tx.gas_limit), + gas_price: Some(legacy_tx.gas_price), + value: Some(legacy_tx.value), + input: TransactionInput::from(legacy_tx.input), + nonce: Some(legacy_tx.nonce), + chain_id: legacy_tx.chain_id, + ..Default::default() + }; + + let deploy_result = reth_provider.send_transaction(legacy_tx_request.clone()).await.unwrap(); + + let deploy_tx_hash = deploy_result.tx_hash(); + info!("Deployed contract with tx hash: {deploy_tx_hash}"); + let receipt = deploy_result.get_receipt().await.unwrap(); + info!("Receipt: {:?}", receipt); + + let contract_address = receipt.contract_address().unwrap(); + let block_num_checker = BlockNumChecker::new(contract_address, reth_provider.clone()); + + let legacy_tx_request = TransactionRequest::default() + .with_from(EOSIO_ADDR.clone()) + .with_to(contract_address) + .with_gas_limit(20_000_000) + .with_gas_price(gas_price) + .with_input(block_num_checker.logBlockNum().calldata().clone()) + .with_nonce(reth_provider.get_transaction_count(EOSIO_ADDR.clone()).await.unwrap()) + .with_chain_id(chain_id); + + let log_block_num_tx_result = reth_provider.send_transaction(legacy_tx_request).await.unwrap(); + + let log_block_num_tx_hash = log_block_num_tx_result.tx_hash(); + info!("Called contract with tx hash: {log_block_num_tx_hash}"); + let receipt = log_block_num_tx_result.get_receipt().await.unwrap(); + info!("log block number receipt: {:?}", receipt); + let rpc_block_num = receipt.block_number().unwrap(); + let receipt = receipt.inner; + let logs = receipt.logs(); + let first_log = logs[0].clone().inner; + let block_num_event = BlockNumChecker::BlockNumber::decode_log(&first_log, true).unwrap(); + assert_eq!(U256::from(rpc_block_num), block_num_event.number); + info!("Block numbers match inside transaction event"); + + // test eip1559 transaction which is not supported + test_1559_tx(reth_provider, EOSIO_ADDR.clone()).await; + // test eip2930 transaction which is not supported + test_2930_tx(reth_provider, EOSIO_ADDR.clone()).await; + // test double approve erc20 call + test_double_approve_erc20(reth_provider, EOSIO_ADDR.clone()).await; + // The below needs to be done using LegacyTransaction style call... with the current code it's including base_fee_per_gas and being rejected by reth + // let block_num_latest = block_num_checker.getBlockNum().call().await.unwrap(); + // assert!(block_num_latest._0 > U256::from(rpc_block_num), "Latest block number via call to getBlockNum is not greater than the block number in the previous log event"); + // + // let block_num_five_back = block_num_checker.getBlockNum().call().block(BlockId::number(rpc_block_num - 5)).await.unwrap(); + // assert!(block_num_five_back._0 == U256::from(rpc_block_num - 5), "Block number 5 blocks back via historical eth_call is not correct"); + +} + +// test_1559_tx tests sending eip1559 transaction that has max_priority_fee_per_gas and max_fee_per_gas set +pub async fn test_1559_tx(provider: &TestProvider, sender_address: Address) { + let nonce = provider.get_transaction_count(sender_address).await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let to_address: Address = Address::from_str("0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4").unwrap(); + + let tx = TransactionRequest::default() + .with_to(to_address) + .with_nonce(nonce) + .with_chain_id(chain_id) + .with_value(U256::from(100)) + .with_gas_limit(21_000) + .with_max_priority_fee_per_gas(1_000_000_000) + .with_max_fee_per_gas(20_000_000_000); + + let tx_result = provider.send_transaction(tx).await; + assert!(tx_result.is_err()); +} + +// test_2930_tx tests sending eip2930 transaction which has access_list provided +pub async fn test_2930_tx(provider: &TestProvider, sender_address: Address) { + let nonce = provider.get_transaction_count(sender_address).await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let to_address: Address = Address::from_str("0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4").unwrap(); + let tx = TransactionRequest::default() + .to(to_address) + .nonce(nonce) + .value(U256::from(1e17)) + .with_chain_id(chain_id) + .with_gas_price(gas_price) + .with_gas_limit(20_000_000) + .max_priority_fee_per_gas(1e11 as u128) + .with_access_list(AccessList::from(vec![AccessListItem { address: to_address, storage_keys: vec![B256::ZERO] }])) + .max_fee_per_gas(2e9 as u128); + let tx_result = provider.send_transaction(tx).await; + assert!(tx_result.is_err()); +} + + +// test_double_approve_erc20 sends 2 transactions for approve on the ERC20 token and asserts that only once it is success +pub async fn test_double_approve_erc20( + provider: &TestProvider, + sender_address: Address, +) { + let nonce = provider.get_transaction_count(sender_address).await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let erc20_contract_address: Address = + "0x49f54c5e2301eb9256438123e80762470c2c7ec2".parse().unwrap(); + let spender: Address = "0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4".parse().unwrap(); + let function_signature = "approve(address,uint256)"; + let amount: U256 = U256::from(0); + let selector = &keccak256(function_signature.as_bytes())[..4]; + let amount_bytes: [u8; 32] = amount.to_be_bytes(); + let mut encoded_data = Vec::new(); + encoded_data.extend_from_slice(selector); + encoded_data.extend_from_slice(spender.as_ref()); + encoded_data.extend_from_slice(&amount_bytes); + let input_data = Bytes::from(encoded_data); + + // Build approve transaction + let mut tx = TransactionRequest::default() + .to(erc20_contract_address) + .with_input(input_data) + .nonce(nonce) + .value(U256::from(10)) + .with_chain_id(chain_id) + .with_gas_price(gas_price) + .with_gas_limit(20_000_000); + + // call approve + let tx_result = provider.send_transaction(tx.clone()).await; + assert!(tx_result.is_ok()); + let receipt1 = tx_result.unwrap().get_receipt().await; + assert!(receipt1.is_ok()); + + let nonce = provider.get_transaction_count(sender_address).await.unwrap(); + tx.nonce = Some(nonce); + + // repeat approve + let tx_result = provider.send_transaction(tx.clone()).await; + assert!(tx_result.is_ok()); + + let receipt2 = tx_result.unwrap().get_receipt().await; + assert!(receipt2.is_ok()); + + let block_number = receipt2.unwrap().block_number.unwrap(); + + // make sure the block is included + while let Some(block) = provider.get_block_by_number(Latest, false).await.unwrap() { + if block.header.number == block_number { + break; + } + } +} + +pub async fn test_1k_txs( + telos_client: &APIClient, + reth_provider: &TestProvider, + target_addr: Address +) { + let chain_id = reth_provider.get_chain_id().await.unwrap(); + let gas_price = reth_provider.get_gas_price().await.unwrap(); + + let eosio_key = PrivateKey::from_str(EOSIO_PKEY, false).unwrap(); + + let start_nonce = get_nonce(&telos_client, &EOSIO_ADDR).await; + + for _i in 0..2 { + let info = telos_client.v1_chain.get_info().await.unwrap(); + let nonce = get_nonce(&telos_client, &EOSIO_ADDR).await; + let tx = multi_raw_eth_tx( + 500, + &info, + name!("eosio"), + PermissionLevel::new(name!("eosio"), name!("active")), + false, + None, + &EOSIO_WALLET, + chain_id, + nonce, + EOSIO_ADDR.clone(), + target_addr, + gas_price, + 20_000_000, + U256::from(10) + ).await; + + let signed_tx = sign_native_tx(&tx, &info, &eosio_key); + + let result = telos_client.v1_chain.send_transaction(signed_tx).await.unwrap(); + + warn!("500 txs in block {}", result.processed.block_num); + tokio::time::sleep(Duration::from_millis(750)).await; + } + + tokio::time::sleep(Duration::from_millis(500)).await; + let last_nonce = get_nonce(&telos_client, &EOSIO_ADDR).await; + assert_eq!(last_nonce - start_nonce, 1000); +} \ No newline at end of file diff --git a/crates/telos/node/tests/live_test_runner.rs b/crates/telos/node/tests/live_test_runner.rs deleted file mode 100644 index 4ba71a7f2c3a..000000000000 --- a/crates/telos/node/tests/live_test_runner.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::fmt::Debug; -use std::str::FromStr; -use alloy_contract::private::Transport; -use alloy_network::{Ethereum, ReceiptResponse, TransactionBuilder}; -use alloy_primitives::{keccak256, Address, B256, U256}; -use alloy_provider::network::EthereumWallet; -use alloy_provider::{Provider, ProviderBuilder}; -use alloy_rpc_types::{TransactionRequest}; -use alloy_signer_local::PrivateKeySigner; -use alloy_sol_types::private::primitives::TxKind::{Create}; -use alloy_sol_types::{sol, SolEvent}; -use reth::rpc::types::{BlockTransactionsKind, TransactionInput}; -use reqwest::{Url}; -use tracing::info; -use reth::primitives::BlockId; -use reth::primitives::BlockNumberOrTag::Latest; -use reth::primitives::revm_primitives::bytes::Bytes; -use reth::revm::primitives::{AccessList, AccessListItem}; - -#[tokio::test] -pub async fn run_local() { - env_logger::builder().is_test(true).try_init().unwrap(); - let url = "http://localhost:8545"; - let private_key = "26e86e45f6fc45ec6e2ecd128cec80fa1d1505e5507dcd2ae58c3130a7a97b48"; - run_tests(url, private_key).await; -} - -pub async fn run_tests(url: &str, private_key: &str) { - let signer = PrivateKeySigner::from_str(private_key).unwrap(); - let wallet = EthereumWallet::from(signer.clone()); - - let provider = ProviderBuilder::new() - //.network::() - .wallet(wallet.clone()) - .on_http(Url::from_str(url).unwrap()); - - let signer_address = signer.address(); - let balance = provider.get_balance(signer_address).await.unwrap(); - - info!("Running live tests using address: {:?} with balance: {:?}", signer_address, balance); - - let block = provider.get_block(BlockId::latest(), BlockTransactionsKind::Full).await; - info!("Latest block:\n {:?}", block); - - test_blocknum_onchain(url, private_key).await; -} - -pub async fn test_blocknum_onchain(url: &str, private_key: &str) { - sol! { - #[sol(rpc, bytecode="6080604052348015600e575f80fd5b5060ef8061001b5f395ff3fe6080604052348015600e575f80fd5b50600436106030575f3560e01c80637f6c6f101460345780638fb82b0214604e575b5f80fd5b603a6056565b6040516045919060a2565b60405180910390f35b6054605d565b005b5f43905090565b437fc04eeb4cfe0799838abac8fa75bca975bff679179886c80c84a7b93229a1a61860405160405180910390a2565b5f819050919050565b609c81608c565b82525050565b5f60208201905060b35f8301846095565b9291505056fea264697066735822122003482ecf0ea4d820deb6b5ebd2755b67c3c8d4fb9ed50a8b4e0bce59613552df64736f6c634300081a0033")] - contract BlockNumChecker { - - event BlockNumber(uint256 indexed number); - - function getBlockNum() public view returns (uint) { - return block.number; - } - - function logBlockNum() public { - emit BlockNumber(block.number); - } - } - } - - let signer = PrivateKeySigner::from_str(private_key).unwrap(); - let address = signer.address(); - let wallet = EthereumWallet::from(signer); - - let provider = - ProviderBuilder::new().wallet(wallet.clone()).on_http(Url::from_str(url).unwrap()); - - info!("Deploying contract using address {address}"); - - let nonce = provider.get_transaction_count(address).await.unwrap(); - let chain_id = provider.get_chain_id().await.unwrap(); - let gas_price = provider.get_gas_price().await.unwrap(); - - let legacy_tx = alloy_consensus::TxLegacy { - chain_id: Some(chain_id), - nonce, - gas_price: gas_price.into(), - gas_limit: 20_000_000, - to: Create, - value: U256::ZERO, - input: BlockNumChecker::BYTECODE.to_vec().into(), - }; - - let legacy_tx_request = TransactionRequest { - from: Some(address), - to: Some(legacy_tx.to), - gas: Some(legacy_tx.gas_limit), - gas_price: Some(legacy_tx.gas_price), - value: Some(legacy_tx.value), - input: TransactionInput::from(legacy_tx.input), - nonce: Some(legacy_tx.nonce), - chain_id: legacy_tx.chain_id, - ..Default::default() - }; - - let deploy_result = provider.send_transaction(legacy_tx_request.clone()).await.unwrap(); - - let deploy_tx_hash = deploy_result.tx_hash(); - info!("Deployed contract with tx hash: {deploy_tx_hash}"); - let receipt = deploy_result.get_receipt().await.unwrap(); - info!("Receipt: {:?}", receipt); - - let contract_address = receipt.contract_address().unwrap(); - let block_num_checker = BlockNumChecker::new(contract_address, provider.clone()); - - let legacy_tx_request = TransactionRequest::default() - .with_from(address) - .with_to(contract_address) - .with_gas_limit(20_000_000) - .with_gas_price(gas_price) - .with_input(block_num_checker.logBlockNum().calldata().clone()) - .with_nonce(provider.get_transaction_count(address).await.unwrap()) - .with_chain_id(chain_id); - - let log_block_num_tx_result = provider.send_transaction(legacy_tx_request).await.unwrap(); - - let log_block_num_tx_hash = log_block_num_tx_result.tx_hash(); - info!("Called contract with tx hash: {log_block_num_tx_hash}"); - let receipt = log_block_num_tx_result.get_receipt().await.unwrap(); - info!("log block number receipt: {:?}", receipt); - let rpc_block_num = receipt.block_number().unwrap(); - let receipt = receipt.inner; - let logs = receipt.logs(); - let first_log = logs[0].clone().inner; - let block_num_event = BlockNumChecker::BlockNumber::decode_log(&first_log, true).unwrap(); - assert_eq!(U256::from(rpc_block_num), block_num_event.number); - info!("Block numbers match inside transaction event"); - - // test eip1559 transaction which is not supported - test_1559_tx(provider.clone(), address).await; - // test eip2930 transaction which is not supported - test_2930_tx(provider.clone(), address).await; - // test double approve erc20 call - test_double_approve_erc20(provider.clone(), address).await; - // The below needs to be done using LegacyTransaction style call... with the current code it's including base_fee_per_gas and being rejected by reth - // let block_num_latest = block_num_checker.getBlockNum().call().await.unwrap(); - // assert!(block_num_latest._0 > U256::from(rpc_block_num), "Latest block number via call to getBlockNum is not greater than the block number in the previous log event"); - // - // let block_num_five_back = block_num_checker.getBlockNum().call().block(BlockId::number(rpc_block_num - 5)).await.unwrap(); - // assert!(block_num_five_back._0 == U256::from(rpc_block_num - 5), "Block number 5 blocks back via historical eth_call is not correct"); - -} - -// test_1559_tx tests sending eip1559 transaction that has max_priority_fee_per_gas and max_fee_per_gas set -pub async fn test_1559_tx(provider: impl Provider + Send + Sync, sender_address: Address) -where - T: Transport + Clone + Debug, -{ - let nonce = provider.get_transaction_count(sender_address).await.unwrap(); - let chain_id = provider.get_chain_id().await.unwrap(); - let to_address: Address = Address::from_str("0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4").unwrap(); - - let tx = TransactionRequest::default() - .with_to(to_address) - .with_nonce(nonce) - .with_chain_id(chain_id) - .with_value(U256::from(100)) - .with_gas_limit(21_000) - .with_max_priority_fee_per_gas(1_000_000_000) - .with_max_fee_per_gas(20_000_000_000); - - let tx_result = provider.send_transaction(tx).await; - assert!(tx_result.is_err()); -} - -// test_2930_tx tests sending eip2930 transaction which has access_list provided -pub async fn test_2930_tx(provider: impl Provider + Send + Sync, sender_address: Address) -where - T: Transport + Clone + Debug, - -{ - let nonce = provider.get_transaction_count(sender_address).await.unwrap(); - let chain_id = provider.get_chain_id().await.unwrap(); - let gas_price = provider.get_gas_price().await.unwrap(); - - let to_address: Address = Address::from_str("0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4").unwrap(); - let tx = TransactionRequest::default() - .to(to_address) - .nonce(nonce) - .value(U256::from(1e17)) - .with_chain_id(chain_id) - .with_gas_price(gas_price) - .with_gas_limit(20_000_000) - .max_priority_fee_per_gas(1e11 as u128) - .with_access_list(AccessList::from(vec![AccessListItem { address: to_address, storage_keys: vec![B256::ZERO] }])) - .max_fee_per_gas(2e9 as u128); - let tx_result = provider.send_transaction(tx).await; - assert!(tx_result.is_err()); -} - - -// test_double_approve_erc20 sends 2 transactions for approve on the ERC20 token and asserts that only once it is success -pub async fn test_double_approve_erc20( - provider: impl Provider + Send + Sync, - sender_address: Address, -) where - T: Transport + Clone + Debug, -{ - let nonce = provider.get_transaction_count(sender_address).await.unwrap(); - let chain_id = provider.get_chain_id().await.unwrap(); - let gas_price = provider.get_gas_price().await.unwrap(); - - let erc20_contract_address: Address = - "0x49f54c5e2301eb9256438123e80762470c2c7ec2".parse().unwrap(); - let spender: Address = "0x23CB6AE34A13a0977F4d7101eBc24B87Bb23F0d4".parse().unwrap(); - let function_signature = "approve(address,uint256)"; - let amount: U256 = U256::from(0); - let selector = &keccak256(function_signature.as_bytes())[..4]; - let amount_bytes: [u8; 32] = amount.to_be_bytes(); - let mut encoded_data = Vec::new(); - encoded_data.extend_from_slice(selector); - encoded_data.extend_from_slice(spender.as_ref()); - encoded_data.extend_from_slice(&amount_bytes); - let input_data = Bytes::from(encoded_data); - - // Build approve transaction - let mut tx = TransactionRequest::default() - .to(erc20_contract_address) - .with_input(input_data) - .nonce(nonce) - .value(U256::from(10)) - .with_chain_id(chain_id) - .with_gas_price(gas_price) - .with_gas_limit(20_000_000); - - // call approve - let tx_result = provider.send_transaction(tx.clone()).await; - assert!(tx_result.is_ok()); - let receipt1 = tx_result.unwrap().get_receipt().await; - assert!(receipt1.is_ok()); - - let nonce = provider.get_transaction_count(sender_address).await.unwrap(); - tx.nonce = Some(nonce); - - // repeat approve - let tx_result = provider.send_transaction(tx.clone()).await; - assert!(tx_result.is_ok()); - - let receipt2 = tx_result.unwrap().get_receipt().await; - assert!(receipt2.is_ok()); - - let block_number = receipt2.unwrap().block_number.unwrap(); - - // make sure the block is included - while let Some(block) = provider.get_block_by_number(Latest, false).await.unwrap() { - if block.header.number == block_number { - break; - } - } -} - diff --git a/crates/telos/node/tests/utils/cleos_evm.rs b/crates/telos/node/tests/utils/cleos_evm.rs new file mode 100644 index 000000000000..ddfaf0891c7b --- /dev/null +++ b/crates/telos/node/tests/utils/cleos_evm.rs @@ -0,0 +1,219 @@ +use std::str::FromStr; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_primitives::bytes::BytesMut; +use alloy_primitives::private::alloy_rlp::Encodable; +use antelope::api::v1::structs::{GetInfoResponse, GetTableRowsParams, IndexPosition, TableIndexType}; +use antelope::chain::action::{Action, PermissionLevel}; +use antelope::chain::checksum::{Checksum160, Checksum256}; +use antelope::chain::name::Name; +use antelope::chain::transaction::{SignedTransaction, Transaction}; +use antelope::chain::Packer; +use antelope::{name, StructPacker}; +use antelope::serializer::{Decoder, Encoder}; +use antelope::chain::private_key::PrivateKey; +use alloy_rpc_types::TransactionRequest; +use alloy_signer_local::PrivateKeySigner; +use antelope::api::client::{APIClient, DefaultProvider}; +use lazy_static::lazy_static; +use reth_node_telos::two_way_storage_compare::AccountRow; + +pub const EOSIO_PKEY: &str = "5Jr65kdYmn33C3UabzhmWDm2PuqbRfPuDStts3ZFNSBLM7TqaiL"; +pub const EOSIO_EVM_PRIV_KEY: &str = "87ef69a835f8cd0c44ab99b7609a20b2ca7f1c8470af4f0e5b44db927d542084"; +pub const EOSIO_EVM_PUB_KEY: &str = "c51fe232a0153f1f44572369cefe7b90f2ba08a5"; + +lazy_static! { + pub static ref EOSIO_SIGNER: PrivateKeySigner = PrivateKeySigner::from_str(EOSIO_EVM_PRIV_KEY).unwrap(); + pub static ref EOSIO_ADDR: Address = Address::from_str(EOSIO_EVM_PUB_KEY).unwrap(); + pub static ref EOSIO_WALLET: EthereumWallet = EthereumWallet::from(EOSIO_SIGNER.clone()); +} + +pub fn pad_address(address: &Address) -> Checksum256 { + let mut padded = vec![0; 12]; + padded.extend_from_slice(address.as_slice()); + Checksum256::from_bytes(padded.as_slice()).unwrap() +} + +pub async fn get_account_by_addr(client: &APIClient, address: &Address) -> AccountRow { + let cs_addr = pad_address(address); + let query_params_account = GetTableRowsParams { + code: name!("eosio.evm"), + table: name!("account"), + scope: None, + lower_bound: Some(TableIndexType::CHECKSUM256(cs_addr)), + upper_bound: Some(TableIndexType::CHECKSUM256(cs_addr)), + limit: Some(1), + reverse: None, + index_position: Some(IndexPosition::SECONDARY), + show_payer: None, + }; + let account_rows = client.v1_chain.get_table_rows::(query_params_account) + .await + .expect(&format!("Network error trying find an account row for {}", cs_addr)); + + account_rows.rows + .first() + .expect(&format!("Couldn\'t find an account row for {}", cs_addr)) + .clone() +} + +pub async fn get_nonce(client: &APIClient, address: &Address) -> u64 { + let account = get_account_by_addr(client, address).await; + account.nonce +} + +#[allow(dead_code)] +pub async fn raw_eth_tx( + info: &GetInfoResponse, + ram_payer: Name, + perms: PermissionLevel, + estimate_gas: bool, + sender: Option, + wallet: &EthereumWallet, + chain_id: u64, + nonce: u64, + from: Address, + to: Address, + gas_price: u128, + gas_limit: u64, + value: U256 +) -> Transaction { + multi_raw_eth_tx(1, info, ram_payer, perms, estimate_gas, sender, wallet, chain_id, nonce, from, to, gas_price, gas_limit, value).await +} + +#[allow(dead_code)] +pub async fn multi_raw_eth_tx( + amount: usize, + info: &GetInfoResponse, + ram_payer: Name, + perms: PermissionLevel, + estimate_gas: bool, + sender: Option, + wallet: &EthereumWallet, + chain_id: u64, + nonce: u64, + from: Address, + to: Address, + gas_price: u128, + gas_limit: u64, + value: U256 +) -> Transaction { + let mut trx_header = info.get_transaction_header(90); + trx_header.max_cpu_usage_ms = 200; + let mut actions = vec![]; + for i in 0..amount { + // generate raw evm tx + let eth_tx_typed = TransactionRequest::default() + .with_chain_id(chain_id) + .with_nonce(nonce + i as u64) + .with_from(from) + .with_to(to) + .with_gas_price(gas_price.into()) + .with_gas_limit(gas_limit) + .with_value(value); + + let eth_tx_envelope = eth_tx_typed.build(&wallet).await.unwrap(); + let mut eth_tx_raw_buf = BytesMut::new(); + eth_tx_envelope.encode(&mut eth_tx_raw_buf); + + let eth_tx_raw: Vec = eth_tx_raw_buf.to_vec(); + + // generate native transaction + #[derive(Clone, Eq, PartialEq, Default, StructPacker)] + struct Raw { + ram_payer: Name, + tx: Vec, + estimate_gas: bool, + sender: Option, + } + + let raw_data = Raw { + ram_payer, + tx: eth_tx_raw, + estimate_gas, + sender, + }; + actions.push(Action::new_ex( + name!("eosio.evm"), + name!("raw"), + vec![perms], + raw_data, + )); + } + + Transaction { + header: trx_header, + context_free_actions: vec![], + actions, + extension: vec![], + } +} + + +// #[allow(dead_code)] +// pub async fn doresources_sandwich( +// info: &GetInfoResponse, +// ram_payer: Name, +// perms: PermissionLevel, +// chain_id: u64 +// ) -> Transaction { +// let mut trx_header = info.get_transaction_header(90); +// trx_header.max_cpu_usage_ms = 200; +// let mut actions = vec![]; +// for i in 0..amount { +// // generate raw evm tx +// let eth_tx_typed = TransactionRequest::default() +// .with_chain_id(chain_id) +// .with_nonce(nonce + i as u64) +// .with_from(from) +// .with_to(to) +// .with_gas_price(gas_price.into()) +// .with_gas_limit(gas_limit) +// .with_value(value); +// +// let eth_tx_envelope = eth_tx_typed.build(&wallet).await.unwrap(); +// let mut eth_tx_raw_buf = BytesMut::new(); +// eth_tx_envelope.encode(&mut eth_tx_raw_buf); +// +// let eth_tx_raw: Vec = eth_tx_raw_buf.to_vec(); +// +// // generate native transaction +// #[derive(Clone, Eq, PartialEq, Default, StructPacker)] +// struct Raw { +// ram_payer: Name, +// tx: Vec, +// estimate_gas: bool, +// sender: Option, +// } +// +// let raw_data = Raw { +// ram_payer, +// tx: eth_tx_raw, +// estimate_gas, +// sender, +// }; +// actions.push(Action::new_ex( +// name!("eosio.evm"), +// name!("raw"), +// vec![perms], +// raw_data, +// )); +// } +// +// Transaction { +// header: trx_header, +// context_free_actions: vec![], +// actions, +// extension: vec![], +// } +// } + +#[allow(dead_code)] +pub fn sign_native_tx(trx: &Transaction, info: &GetInfoResponse, private_key: &PrivateKey) -> SignedTransaction { + let sign_data = trx.signing_data(info.chain_id.data.as_ref()); + SignedTransaction { + transaction: trx.clone(), + signatures: vec![private_key.sign_message(&sign_data)], + context_free_data: vec![], + } +} \ No newline at end of file diff --git a/crates/telos/node/tests/utils/mod.rs b/crates/telos/node/tests/utils/mod.rs new file mode 100644 index 000000000000..405fe76c6826 --- /dev/null +++ b/crates/telos/node/tests/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod cleos_evm; +pub mod runners; \ No newline at end of file diff --git a/crates/telos/node/tests/utils/runners.rs b/crates/telos/node/tests/utils/runners.rs new file mode 100644 index 000000000000..34dd33af4d4d --- /dev/null +++ b/crates/telos/node/tests/utils/runners.rs @@ -0,0 +1,131 @@ +use std::fs; +use std::path::PathBuf; +use std::sync::Arc; +use antelope::api::client::{APIClient, DefaultProvider}; +use telos_consensus_client::client::ConsensusClient; +use telos_consensus_client::config::{AppConfig, CliArgs}; +use telos_consensus_client::main_utils::build_consensus_client; +use telos_translator_rs::translator::Translator; +use telos_translator_rs::types::translator_types::ChainId; +use testcontainers::{ContainerAsync, GenericImage}; +use testcontainers::core::ContainerPort::Tcp; +use testcontainers::runners::AsyncRunner; +use reth::args::RpcServerArgs; +use reth::builder::NodeConfig; +use reth_chainspec::{ChainSpec, ChainSpecBuilder, TEVMTESTNET}; + +#[derive(Debug)] +pub struct TelosRethNodeHandle { + pub execution_port: u16, + pub jwt_secret: String, +} + + +pub const CONTAINER_NAME: &str = "ghcr.io/telosnetwork/testcontainer-nodeos-evm"; +pub const CONTAINER_NAME_LITE: &str = "guilledk/testcontainer-nodeos-evm-lite"; + +pub const CONTAINER_TAG: &str = + "v0.1.11@sha256:d138f2e08db108d5d420b4db99a57fb9d45a3ee3e0f0faa7d4c4a065f7f018ce"; +pub const CONTAINER_TAG_LITE: &str = + "latest@sha256:e91304655bca4b190af5c7d1bbdd86ba26ae927c43fe82bdc36edfad24e022cb"; + +// This is the last block in the container, after this block the node is done syncing and is running live +pub const CONTAINER_LAST_EVM_BLOCK: u64 = 1010; +pub const CONTAINER_LAST_EVM_BLOCK_LITE: u64 = 37; + +pub async fn start_ship(name: &str, tag: &str) -> ContainerAsync { + // Change this container to a local image if using new ship data, + // then make sure to update the ship data in the testcontainer-nodeos-evm repo and build a new version + + // The tag for this image needs to come from the Github packages UI, under the "OS/Arch" tab + // and should be the tag for linux/amd64 + let container: ContainerAsync = + GenericImage::new(name, tag) + .with_exposed_port(Tcp(8888)) + .with_exposed_port(Tcp(18999)) + .start() + .await + .unwrap(); + + let port_8888 = container.get_host_port_ipv4(8888).await.unwrap(); + + let api_base_url = format!("http://localhost:{port_8888}"); + let api_client = APIClient::::default_provider(api_base_url, Some(1)).unwrap(); + + let mut last_block = 0; + + loop { + let Ok(info) = api_client.v1_chain.get_info().await else { + println!("Waiting for telos node to produce blocks..."); + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + continue; + }; + if last_block != 0 && info.head_block_num > last_block { + break; + } + last_block = info.head_block_num; + } + + container +} + +pub fn init_reth() -> eyre::Result<(NodeConfig, String)> { + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(TEVMTESTNET.chain) + .genesis(TEVMTESTNET.genesis.clone()) + .frontier_activated() + .homestead_activated() + .tangerine_whistle_activated() + .spurious_dragon_activated() + .byzantium_activated() + .constantinople_activated() + .petersburg_activated() + .istanbul_activated() + .berlin_activated() + .build(), + ); + + let mut rpc_config = RpcServerArgs::default().with_unused_ports().with_http(); + rpc_config.auth_jwtsecret = Some(PathBuf::from("tests/assets/jwt.hex")); + + // Node setup + let node_config = NodeConfig::test().with_chain(chain_spec).with_rpc(rpc_config.clone()); + + let jwt = fs::read_to_string(node_config.rpc.auth_jwtsecret.clone().unwrap())?; + Ok((node_config, jwt)) +} + +pub async fn build_consensus_and_translator( + reth_handle: crate::TelosRethNodeHandle, + ship_port: u16, + chain_port: u16, +) -> (ConsensusClient, Translator) { + let config = AppConfig { + log_level: "debug".to_string(), + chain_id: ChainId(41), + execution_endpoint: format!("http://localhost:{}", reth_handle.execution_port), + jwt_secret: reth_handle.jwt_secret, + ship_endpoint: format!("ws://localhost:{ship_port}"), + chain_endpoint: format!("http://localhost:{chain_port}"), + batch_size: 1, + prev_hash: "b25034033c9ca7a40e879ddcc29cf69071a22df06688b5fe8cc2d68b4e0528f9".to_string(), + validate_hash: None, + evm_start_block: 1, + // TODO: Determine a good stop block and test it here + evm_stop_block: None, + data_path: "temp/db".to_string(), + block_checkpoint_interval: 1000, + maximum_sync_range: 100000, + latest_blocks_in_db_num: 100, + max_retry: None, + retry_interval: None, + }; + + let cli_args = CliArgs { config: "".to_string(), clean: true }; + + let c = build_consensus_client(&cli_args, config).await.unwrap(); + let translator = Translator::new((&c.config).into()); + + (c, translator) +}