diff --git a/tests/docker/src/lib.rs b/tests/docker/src/lib.rs index ee68b9795..3493d5029 100644 --- a/tests/docker/src/lib.rs +++ b/tests/docker/src/lib.rs @@ -124,7 +124,7 @@ pub fn build(name: String) { // Check any additionally specified paths let meta = |path: PathBuf| (path.clone(), fs::metadata(path)); let mut metadatas = match name.as_str() { - "bitcoin" | "monero" => vec![], + "bitcoin" | "ethereum" | "monero" => vec![], "message-queue" => vec![ meta(repo_path.join("common")), meta(repo_path.join("crypto")), diff --git a/tests/processor/Cargo.toml b/tests/processor/Cargo.toml index 686dbcea7..be27e7de9 100644 --- a/tests/processor/Cargo.toml +++ b/tests/processor/Cargo.toml @@ -27,12 +27,14 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] } bitcoin-serai = { path = "../../coins/bitcoin" } +ethereum-serai = { path = "../../coins/ethereum" } monero-serai = { path = "../../coins/monero" } messages = { package = "serai-processor-messages", path = "../../processor/messages" } scale = { package = "parity-scale-codec", version = "3" } serai-client = { path = "../../substrate/client" } +serai-db = { path = "../../common/db", default-features = false } serai-message-queue = { path = "../../message-queue" } borsh = { version = "1", features = ["de_strict_order"] } diff --git a/tests/processor/src/lib.rs b/tests/processor/src/lib.rs index e400057a4..66aa28c46 100644 --- a/tests/processor/src/lib.rs +++ b/tests/processor/src/lib.rs @@ -181,7 +181,28 @@ impl Coordinator { break; } } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ethereum_serai::{ + alloy_simple_request_transport::SimpleRequest, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + + loop { + if handle + .block_on(provider.raw_request::<_, ()>("evm_setAutomine".into(), [false])) + .is_ok() + { + break; + } + handle.block_on(tokio::time::sleep(core::time::Duration::from_secs(1))); + } + } NetworkId::Monero => { use monero_serai::rpc::HttpRpc; @@ -271,7 +292,45 @@ impl Coordinator { block.consensus_encode(&mut block_buf).unwrap(); (hash, block_buf) } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ethereum_serai::{ + alloy_simple_request_transport::SimpleRequest, + alloy_rpc_types::BlockNumberOrTag, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + let start = provider + .get_block(BlockNumberOrTag::Latest.into(), false) + .await + .unwrap() + .unwrap() + .header + .number + .unwrap(); + // We mine 96 blocks to mine one epoch, then cause its finalization + provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap(); + let end_of_epoch = start + 31; + let hash = provider + .get_block(BlockNumberOrTag::Number(end_of_epoch).into(), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap(); + + let state = provider + .raw_request::<_, String>("anvil_dumpState".into(), ()) + .await + .unwrap() + .into_bytes(); + (hash.into(), state) + } NetworkId::Monero => { use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; use monero_serai::{ @@ -303,39 +362,6 @@ impl Coordinator { } } - pub async fn broadcast_block(&self, ops: &DockerOperations, block: &[u8]) { - let rpc_url = network_rpc(self.network, ops, &self.network_handle); - match self.network { - NetworkId::Bitcoin => { - use bitcoin_serai::rpc::Rpc; - - let rpc = - Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC"); - let res: Option = - rpc.rpc_call("submitblock", serde_json::json!([hex::encode(block)])).await.unwrap(); - if let Some(err) = res { - panic!("submitblock failed: {err}"); - } - } - NetworkId::Ethereum => todo!(), - NetworkId::Monero => { - use monero_serai::rpc::HttpRpc; - - let rpc = - HttpRpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Monero RPC"); - let res: serde_json::Value = rpc - .json_rpc_call("submit_block", Some(serde_json::json!([hex::encode(block)]))) - .await - .unwrap(); - let err = res.get("error"); - if err.is_some() && (err.unwrap() != &serde_json::Value::Null) { - panic!("failed to submit Monero block: {res}"); - } - } - NetworkId::Serai => panic!("processor tests broadcasting block to Serai"), - } - } - pub async fn sync(&self, ops: &DockerOperations, others: &[Coordinator]) { let rpc_url = network_rpc(self.network, ops, &self.network_handle); match self.network { @@ -345,13 +371,8 @@ impl Coordinator { let rpc = Rpc::new(rpc_url).await.expect("couldn't connect to the Bitcoin RPC"); let to = rpc.get_latest_block_number().await.unwrap(); for coordinator in others { - let from = Rpc::new(network_rpc(self.network, ops, &coordinator.network_handle)) - .await - .expect("couldn't connect to the Bitcoin RPC") - .get_latest_block_number() - .await - .unwrap() + - 1; + let from = rpc.get_latest_block_number().await.unwrap() + 1; + for b in from ..= to { let mut buf = vec![]; rpc @@ -360,11 +381,40 @@ impl Coordinator { .unwrap() .consensus_encode(&mut buf) .unwrap(); - coordinator.broadcast_block(ops, &buf).await; + + let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle); + let rpc = + Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC"); + + let res: Option = + rpc.rpc_call("submitblock", serde_json::json!([hex::encode(buf)])).await.unwrap(); + if let Some(err) = res { + panic!("submitblock failed: {err}"); + } } } } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ethereum_serai::{ + alloy_simple_request_transport::SimpleRequest, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + let state = provider.raw_request::<_, String>("anvil_dumpState".into(), ()).await.unwrap(); + + for coordinator in others { + let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle); + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + provider.raw_request::<_, ()>("anvil_loadState".into(), &state).await.unwrap(); + } + } NetworkId::Monero => { use monero_serai::rpc::HttpRpc; @@ -378,12 +428,21 @@ impl Coordinator { .await .unwrap(); for b in from .. to { - coordinator - .broadcast_block( - ops, - &rpc.get_block(rpc.get_block_hash(b).await.unwrap()).await.unwrap().serialize(), - ) - .await; + let block = + rpc.get_block(rpc.get_block_hash(b).await.unwrap()).await.unwrap().serialize(); + + let rpc_url = network_rpc(coordinator.network, ops, &coordinator.network_handle); + let rpc = HttpRpc::new(rpc_url) + .await + .expect("couldn't connect to the coordinator's Monero RPC"); + let res: serde_json::Value = rpc + .json_rpc_call("submit_block", Some(serde_json::json!([hex::encode(block)]))) + .await + .unwrap(); + let err = res.get("error"); + if err.is_some() && (err.unwrap() != &serde_json::Value::Null) { + panic!("failed to submit Monero block: {res}"); + } } } } @@ -404,7 +463,19 @@ impl Coordinator { Rpc::new(rpc_url).await.expect("couldn't connect to the coordinator's Bitcoin RPC"); rpc.send_raw_transaction(&Transaction::consensus_decode(&mut &*tx).unwrap()).await.unwrap(); } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ethereum_serai::{ + alloy_simple_request_transport::SimpleRequest, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + let _ = provider.send_raw_transaction(tx).await.unwrap(); + } NetworkId::Monero => { use monero_serai::{transaction::Transaction, rpc::HttpRpc}; @@ -445,7 +516,26 @@ impl Coordinator { None } } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ethereum_serai::{ + alloy_simple_request_transport::SimpleRequest, + alloy_consensus::{TxLegacy, Signed}, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + let mut hash = [0; 32]; + hash.copy_from_slice(tx); + let tx = provider.get_transaction_by_hash(hash.into()).await.unwrap()?; + let (tx, sig, _) = Signed::::try_from(tx).unwrap().into_parts(); + let mut bytes = vec![]; + tx.encode_with_signature_fields(&sig, &mut bytes); + Some(bytes) + } NetworkId::Monero => { use monero_serai::rpc::HttpRpc; diff --git a/tests/processor/src/networks.rs b/tests/processor/src/networks.rs index 882b9e895..7a81062aa 100644 --- a/tests/processor/src/networks.rs +++ b/tests/processor/src/networks.rs @@ -19,6 +19,7 @@ pub const RPC_USER: &str = "serai"; pub const RPC_PASS: &str = "seraidex"; pub const BTC_PORT: u32 = 8332; +pub const ETH_PORT: u32 = 8545; pub const XMR_PORT: u32 = 18081; pub fn bitcoin_instance() -> (TestBodySpecification, u32) { @@ -31,6 +32,17 @@ pub fn bitcoin_instance() -> (TestBodySpecification, u32) { (composition, BTC_PORT) } +pub fn ethereum_instance() -> (TestBodySpecification, u32) { + serai_docker_tests::build("ethereum".to_string()); + + let composition = TestBodySpecification::with_image( + Image::with_repository("serai-dev-ethereum").pull_policy(PullPolicy::Never), + ) + .set_start_policy(StartPolicy::Strict) + .set_publish_all_ports(true); + (composition, ETH_PORT) +} + pub fn monero_instance() -> (TestBodySpecification, u32) { serai_docker_tests::build("monero".to_string()); @@ -45,7 +57,7 @@ pub fn monero_instance() -> (TestBodySpecification, u32) { pub fn network_instance(network: NetworkId) -> (TestBodySpecification, u32) { match network { NetworkId::Bitcoin => bitcoin_instance(), - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => ethereum_instance(), NetworkId::Monero => monero_instance(), NetworkId::Serai => { panic!("Serai is not a valid network to spawn an instance of for a processor") @@ -58,7 +70,7 @@ pub fn network_rpc(network: NetworkId, ops: &DockerOperations, handle: &str) -> .handle(handle) .host_port(match network { NetworkId::Bitcoin => BTC_PORT, - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => ETH_PORT, NetworkId::Monero => XMR_PORT, NetworkId::Serai => panic!("getting port for external network yet it was Serai"), }) @@ -70,7 +82,7 @@ pub fn confirmations(network: NetworkId) -> usize { use processor::networks::*; match network { NetworkId::Bitcoin => Bitcoin::CONFIRMATIONS, - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => Ethereum::::CONFIRMATIONS, NetworkId::Monero => Monero::CONFIRMATIONS, NetworkId::Serai => panic!("getting confirmations required for Serai"), } @@ -83,6 +95,10 @@ pub enum Wallet { public_key: bitcoin_serai::bitcoin::PublicKey, input_tx: bitcoin_serai::bitcoin::Transaction, }, + Ethereum { + key: ::F, + nonce: u64, + }, Monero { handle: String, spend_key: Zeroizing, @@ -138,7 +154,37 @@ impl Wallet { Wallet::Bitcoin { private_key, public_key, input_tx: funds } } - NetworkId::Ethereum => todo!(), + NetworkId::Ethereum => { + use ciphersuite::{group::ff::Field, Ciphersuite, Secp256k1}; + use ethereum_serai::{ + alloy_core::primitives::{U256, Address}, + alloy_simple_request_transport::SimpleRequest, + alloy_rpc_client::ClientBuilder, + alloy_provider::{Provider, RootProvider}, + alloy_network::Ethereum, + }; + + let key = ::F::random(&mut OsRng); + let address = + ethereum_serai::crypto::address(&(::generator() * key)); + + let provider = RootProvider::<_, Ethereum>::new( + ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true), + ); + + provider + .raw_request::<_, ()>( + "anvil_setBalance".into(), + [Address(address.into()).to_string(), { + let nine_decimals = U256::from(1_000_000_000u64); + (U256::from(100u64) * nine_decimals * nine_decimals).to_string() + }], + ) + .await + .unwrap(); + + Wallet::Ethereum { key, nonce: 0 } + } NetworkId::Monero => { use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, scalar::Scalar}; @@ -282,6 +328,24 @@ impl Wallet { (buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) }) } + Wallet::Ethereum { key, ref mut nonce } => { + /* + use ethereum_serai::alloy_core::primitives::U256; + + let eight_decimals = U256::from(100_000_000u64); + let nine_decimals = eight_decimals * U256::from(10u64); + let eighteen_decimals = nine_decimals * nine_decimals; + + let tx = todo!("send to router"); + + *nonce += 1; + (tx, Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) }) + */ + let _ = key; + let _ = nonce; + todo!() + } + Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => { use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use monero_serai::{ @@ -374,6 +438,13 @@ impl Wallet { ) .unwrap() } + Wallet::Ethereum { key, .. } => { + use ciphersuite::{Ciphersuite, Secp256k1}; + ExternalAddress::new( + ethereum_serai::crypto::address(&(Secp256k1::generator() * key)).into(), + ) + .unwrap() + } Wallet::Monero { view_pair, .. } => { use monero_serai::wallet::address::{Network, AddressSpec}; ExternalAddress::new(