Ethereum processor docker tests, barring send

We need the TX publication relay thingy for send to work (though that is the
point the test fails at).
This commit is contained in:
Luke Parker 2024-05-21 00:29:33 -04:00
parent ae8a27b876
commit 11ec9e3535
No known key found for this signature in database
20 changed files with 305 additions and 91 deletions

2
Cargo.lock generated
View file

@ -3706,6 +3706,7 @@ dependencies = [
"elliptic-curve",
"once_cell",
"sha2",
"signature",
]
[[package]]
@ -8070,6 +8071,7 @@ dependencies = [
"dockertest",
"ethereum-serai",
"hex",
"k256",
"monero-serai",
"parity-scale-codec",
"rand_core",

View file

@ -46,4 +46,4 @@ tokio = { version = "1", features = ["macros"] }
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", rev = "b79db21734cffddc11753fe62ba571565c896f42", default-features = false }
[features]
tests = ["alloy-node-bindings"]
tests = ["alloy-node-bindings", "frost/tests"]

View file

@ -17,6 +17,7 @@ pub fn coordinator(
let longer_reattempts = if network == Network::Dev { "longer-reattempts" } else { "" };
let setup = mimalloc(Os::Debian).to_string() +
&build_serai_service(
"",
network.release(),
&format!("{db} {longer_reattempts}"),
"serai-coordinator",

View file

@ -137,7 +137,7 @@ WORKDIR /home/{user}
}
}
fn build_serai_service(release: bool, features: &str, package: &str) -> String {
fn build_serai_service(prelude: &str, release: bool, features: &str, package: &str) -> String {
let profile = if release { "release" } else { "debug" };
let profile_flag = if release { "--release" } else { "" };
@ -159,6 +159,8 @@ RUN apt install -y make protobuf-compiler
# Add the wasm toolchain
RUN rustup target add wasm32-unknown-unknown
{prelude}
# Add files for build
ADD patches /serai/patches
ADD common /serai/common

View file

@ -13,7 +13,7 @@ pub fn message_queue(
monero_key: <Ristretto as Ciphersuite>::G,
) {
let setup = mimalloc(Os::Debian).to_string() +
&build_serai_service(network.release(), network.db(), "serai-message-queue");
&build_serai_service("", network.release(), network.db(), "serai-message-queue");
let env_vars = [
("COORDINATOR_KEY", hex::encode(coordinator_key.to_bytes())),

View file

@ -17,6 +17,15 @@ pub fn processor(
) {
let setup = mimalloc(Os::Debian).to_string() +
&build_serai_service(
if coin == "ethereum" {
r#"
RUN cargo install svm-rs
RUN svm install 0.8.25
RUN svm use 0.8.25
"#
} else {
""
},
network.release(),
&format!("binaries {} {coin}", network.db()),
"serai-processor",
@ -34,7 +43,7 @@ RUN apt install -y ca-certificates
let hostname = format!("serai-{}-{coin}", network.label());
let port = match coin {
"bitcoin" => 8332,
"ethereum" => return, // TODO
"ethereum" => 8545,
"monero" => 18081,
_ => panic!("unrecognized external network"),
};

View file

@ -11,9 +11,9 @@ pub fn serai(
serai_key: &Zeroizing<<Ristretto as Ciphersuite>::F>,
) {
// Always builds in release for performance reasons
let setup = mimalloc(Os::Debian).to_string() + &build_serai_service(true, "", "serai-node");
let setup = mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "", "serai-node");
let setup_fast_epoch =
mimalloc(Os::Debian).to_string() + &build_serai_service(true, "fast-epoch", "serai-node");
mimalloc(Os::Debian).to_string() + &build_serai_service("", true, "fast-epoch", "serai-node");
let env_vars = [("KEY", hex::encode(serai_key.to_repr()))];
let mut env_vars_str = String::new();

View file

@ -512,6 +512,7 @@ impl<N: Network, D: Db> KeyGen<N, D> {
ProcessorMessage::GeneratedKeyPair {
id,
substrate_key: generated_substrate_key.unwrap().to_bytes(),
// TODO: This can be made more efficient since tweaked keys may be a subset of keys
network_key: generated_network_key.unwrap().to_bytes().as_ref().to_vec(),
}
}

View file

@ -63,9 +63,22 @@ fn instruction_from_output<N: Network>(
return (presumed_origin, None);
}
let Ok(shorthand) = Shorthand::decode(&mut data) else { return (presumed_origin, None) };
let Ok(instruction) = RefundableInInstruction::try_from(shorthand) else {
return (presumed_origin, None);
let shorthand = match Shorthand::decode(&mut data) {
Ok(shorthand) => shorthand,
Err(e) => {
info!("data in output {} wasn't valid shorthand: {e:?}", hex::encode(output.id()));
return (presumed_origin, None);
}
};
let instruction = match RefundableInInstruction::try_from(shorthand) {
Ok(instruction) => instruction,
Err(e) => {
info!(
"shorthand in output {} wasn't convertible to a RefundableInInstruction: {e:?}",
hex::encode(output.id())
);
return (presumed_origin, None);
}
};
let mut balance = output.balance();

View file

@ -279,6 +279,8 @@ impl<N: Network, D: Db> ScannerHandle<N, D> {
activation_number: usize,
key: <N::Curve as Ciphersuite>::G,
) {
info!("Registering key {} in scanner at {activation_number}", hex::encode(key.to_bytes()));
let mut scanner_lock = self.scanner.write().await;
let scanner = scanner_lock.as_mut().unwrap();
assert!(
@ -286,8 +288,6 @@ impl<N: Network, D: Db> ScannerHandle<N, D> {
"activation block of new keys was already scanned",
);
info!("Registering key {} in scanner at {activation_number}", hex::encode(key.to_bytes()));
if scanner.keys.is_empty() {
assert!(scanner.ram_scanned.is_none());
scanner.ram_scanned = Some(activation_number);

View file

@ -116,7 +116,7 @@ impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
assert!(self.coins.contains(&utxo.balance().coin));
}
let mut nonce = LastNonce::get(txn).map_or(1, |nonce| nonce + 1);
let mut nonce = LastNonce::get(txn).unwrap_or(1);
let mut plans = vec![];
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
// Once we rotate, all further payments should be scheduled via the new multisig

View file

@ -432,7 +432,7 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
}
// If there's a UTXO to restore, restore it
// This is down now as if there is a to_restore output, and it was inserted into self.utxos
// This is done now as if there is a to_restore output, and it was inserted into self.utxos
// earlier, self.utxos.len() may become `N::MAX_INPUTS + 1`
// The prior block requires the len to be `<= N::MAX_INPUTS`
if let Some(to_restore) = to_restore {
@ -442,9 +442,10 @@ impl<N: UtxoNetwork<Scheduler = Self>> Scheduler<N> {
txn.put(scheduler_key::<D, _>(&self.key), self.serialize());
log::info!(
"created {} plans containing {} payments to sign",
"created {} plans containing {} payments to sign, with {} payments pending scheduling",
plans.len(),
payments_at_start - self.payments.len(),
self.payments.len(),
);
plans
}
@ -589,7 +590,8 @@ impl<N: UtxoNetwork<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
output: N::Output,
refund_to: N::Address,
) -> Plan<N> {
Plan {
let output_id = output.id().as_ref().to_vec();
let res = Plan {
key: output.key(),
// Uses a payment as this will still be successfully sent due to fee amortization,
// and because change is currently always a Serai key
@ -597,7 +599,9 @@ impl<N: UtxoNetwork<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
inputs: vec![output],
change: None,
scheduler_addendum: (),
}
};
log::info!("refund plan for {} has ID {}", hex::encode(output_id), hex::encode(res.id()));
res
}
fn shim_forward_plan(output: N::Output, to: <N::Curve as Ciphersuite>::G) -> Option<Plan<N>> {

View file

@ -426,7 +426,7 @@ impl<D: Db> Network for Ethereum<D> {
.get_block(BlockNumberOrTag::Finalized.into(), false)
.await
.map_err(|_| NetworkError::ConnectionError)?
.expect("no blocks were finalized")
.ok_or(NetworkError::ConnectionError)?
.header
.number
.unwrap();

View file

@ -23,11 +23,14 @@ zeroize = { version = "1", default-features = false }
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
curve25519-dalek = "4"
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["ristretto"] }
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["secp256k1", "ristretto"] }
dkg = { path = "../../crypto/dkg", default-features = false, features = ["tests"] }
bitcoin-serai = { path = "../../coins/bitcoin" }
k256 = "0.13"
ethereum-serai = { path = "../../coins/ethereum" }
monero-serai = { path = "../../coins/monero" }
messages = { package = "serai-processor-messages", path = "../../processor/messages" }
@ -43,7 +46,7 @@ serde_json = { version = "1", default-features = false }
tokio = { version = "1", features = ["time"] }
processor = { package = "serai-processor", path = "../../processor", features = ["bitcoin", "monero"] }
processor = { package = "serai-processor", path = "../../processor", features = ["bitcoin", "ethereum", "monero"] }
dockertest = "0.4"
serai-docker-tests = { path = "../docker" }

View file

@ -61,6 +61,7 @@ pub fn processor_instance(
pub type Handles = (String, String, String);
pub fn processor_stack(
network: NetworkId,
network_hostname_override: Option<String>,
) -> (Handles, <Ristretto as Ciphersuite>::F, Vec<TestBodySpecification>) {
let (network_composition, network_rpc_port) = network_instance(network);
@ -113,7 +114,10 @@ pub fn processor_stack(
}
let processor_composition = compositions.last_mut().unwrap();
processor_composition.inject_container_name(handles[0].clone(), "NETWORK_RPC_HOSTNAME");
processor_composition.inject_container_name(
network_hostname_override.unwrap_or_else(|| handles[0].clone()),
"NETWORK_RPC_HOSTNAME",
);
processor_composition.inject_container_name(handles[1].clone(), "MESSAGE_QUEUE_RPC");
((handles[0].clone(), handles[1].clone(), handles[2].clone()), coord_key, compositions)
@ -182,25 +186,52 @@ impl Coordinator {
}
}
NetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_client::ClientBuilder,
provider::{Provider, RootProvider},
network::Ethereum,
use std::sync::Arc;
use ethereum_serai::{
alloy::{
simple_request_transport::SimpleRequest,
rpc_client::ClientBuilder,
provider::{Provider, RootProvider},
network::Ethereum,
},
deployer::Deployer,
};
let provider = RootProvider::<_, Ethereum>::new(
let provider = Arc::new(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)));
if handle
.block_on(provider.raw_request::<_, ()>("evm_setAutomine".into(), [false]))
.is_ok()
{
handle.block_on(async {
// Deploy the deployer
let tx = Deployer::deployment_tx();
let signer = tx.recover_signer().unwrap();
let (tx, sig, _) = tx.into_parts();
provider
.raw_request::<_, ()>(
"anvil_setBalance".into(),
[signer.to_string(), (tx.gas_limit * tx.gas_price).to_string()],
)
.await
.unwrap();
let mut bytes = vec![];
tx.encode_with_signature_fields(&sig, &mut bytes);
let _ = provider.send_raw_transaction(&bytes).await.unwrap();
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
let _ = Deployer::new(provider.clone()).await.unwrap().unwrap();
// Sleep until the actual time is ahead of whatever time is in the epoch we just
// mined
tokio::time::sleep(core::time::Duration::from_secs(30)).await;
});
break;
}
}
NetworkId::Monero => {
@ -371,7 +402,10 @@ 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.get_latest_block_number().await.unwrap() + 1;
let other_rpc = Rpc::new(network_rpc(self.network, ops, &coordinator.network_handle))
.await
.expect("couldn't connect to the Bitcoin RPC");
let from = other_rpc.get_latest_block_number().await.unwrap() + 1;
for b in from ..= to {
let mut buf = vec![];
@ -382,12 +416,10 @@ impl Coordinator {
.consensus_encode(&mut buf)
.unwrap();
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<String> =
rpc.rpc_call("submitblock", serde_json::json!([hex::encode(buf)])).await.unwrap();
let res: Option<String> = other_rpc
.rpc_call("submitblock", serde_json::json!([hex::encode(buf)]))
.await
.unwrap();
if let Some(err) = res {
panic!("submitblock failed: {err}");
}
@ -397,22 +429,52 @@ impl Coordinator {
NetworkId::Ethereum => {
use ethereum_serai::alloy::{
simple_request_transport::SimpleRequest,
rpc_types::BlockNumberOrTag,
rpc_client::ClientBuilder,
provider::{Provider, RootProvider},
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();
let (expected_number, state) = {
let provider = RootProvider::<_, Ethereum>::new(
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
);
let expected_number = provider
.get_block(BlockNumberOrTag::Latest.into(), false)
.await
.unwrap()
.unwrap()
.header
.number;
(
expected_number,
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();
assert!(provider
.raw_request::<_, bool>("anvil_loadState".into(), &[&state])
.await
.unwrap());
let new_number = provider
.get_block(BlockNumberOrTag::Latest.into(), false)
.await
.unwrap()
.unwrap()
.header
.number;
// TODO: https://github.com/foundry-rs/foundry/issues/7955
let _ = expected_number;
let _ = new_number;
//assert_eq!(expected_number, new_number);
}
}
NetworkId::Monero => {
@ -421,21 +483,17 @@ impl Coordinator {
let rpc = HttpRpc::new(rpc_url).await.expect("couldn't connect to the Monero RPC");
let to = rpc.get_height().await.unwrap();
for coordinator in others {
let from = HttpRpc::new(network_rpc(self.network, ops, &coordinator.network_handle))
.await
.expect("couldn't connect to the Monero RPC")
.get_height()
.await
.unwrap();
let other_rpc =
HttpRpc::new(network_rpc(coordinator.network, ops, &coordinator.network_handle))
.await
.expect("couldn't connect to the Monero RPC");
let from = other_rpc.get_height().await.unwrap();
for b in from .. to {
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
let res: serde_json::Value = other_rpc
.json_rpc_call("submit_block", Some(serde_json::json!([hex::encode(block)])))
.await
.unwrap();

View file

@ -96,6 +96,7 @@ pub enum Wallet {
input_tx: bitcoin_serai::bitcoin::Transaction,
},
Ethereum {
rpc_url: String,
key: <ciphersuite::Secp256k1 as Ciphersuite>::F,
nonce: u64,
},
@ -155,7 +156,7 @@ impl Wallet {
}
NetworkId::Ethereum => {
use ciphersuite::{group::ff::Field, Ciphersuite, Secp256k1};
use ciphersuite::{group::ff::Field, Secp256k1};
use ethereum_serai::alloy::{
primitives::{U256, Address},
simple_request_transport::SimpleRequest,
@ -183,7 +184,7 @@ impl Wallet {
.await
.unwrap();
Wallet::Ethereum { key, nonce: 0 }
Wallet::Ethereum { rpc_url: rpc_url.clone(), key, nonce: 0 }
}
NetworkId::Monero => {
@ -328,22 +329,107 @@ impl Wallet {
(buf, Balance { coin: Coin::Bitcoin, amount: Amount(AMOUNT) })
}
Wallet::Ethereum { key, ref mut nonce } => {
/*
use ethereum_serai::alloy::primitives::U256;
Wallet::Ethereum { rpc_url, key, ref mut nonce } => {
use std::sync::Arc;
use ethereum_serai::{
alloy::{
primitives::{U256, TxKind},
sol_types::SolCall,
simple_request_transport::SimpleRequest,
consensus::{TxLegacy, SignableTransaction},
rpc_client::ClientBuilder,
provider::{Provider, RootProvider},
network::Ethereum,
},
crypto::PublicKey,
deployer::Deployer,
};
let eight_decimals = U256::from(100_000_000u64);
let nine_decimals = eight_decimals * U256::from(10u64);
let eighteen_decimals = nine_decimals * nine_decimals;
let one_eth = eighteen_decimals;
let tx = todo!("send to router");
let provider = Arc::new(RootProvider::<_, Ethereum>::new(
ClientBuilder::default().transport(SimpleRequest::new(rpc_url.clone()), true),
));
let to_as_key = PublicKey::new(
<ciphersuite::Secp256k1 as Ciphersuite>::read_G(&mut to.as_slice()).unwrap(),
)
.unwrap();
let router_addr = {
// Find the deployer
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
// Find the router, deploying if non-existent
let router = if let Some(router) =
deployer.find_router(provider.clone(), &to_as_key).await.unwrap()
{
router
} else {
let mut tx = deployer.deploy_router(&to_as_key);
tx.gas_price = 1_000_000_000u64.into();
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
let signer = tx.recover_signer().unwrap();
let (tx, sig, _) = tx.into_parts();
provider
.raw_request::<_, ()>(
"anvil_setBalance".into(),
[signer.to_string(), (tx.gas_limit * tx.gas_price).to_string()],
)
.await
.unwrap();
let mut bytes = vec![];
tx.encode_with_signature_fields(&sig, &mut bytes);
let _ = provider.send_raw_transaction(&bytes).await.unwrap();
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
deployer.find_router(provider.clone(), &to_as_key).await.unwrap().unwrap()
};
router.address()
};
let tx = TxLegacy {
chain_id: None,
nonce: *nonce,
gas_price: 1_000_000_000u128,
gas_limit: 200_000u128,
to: TxKind::Call(router_addr.into()),
// 1 ETH
value: one_eth,
input: ethereum_serai::router::abi::inInstructionCall::new((
[0; 20].into(),
one_eth,
if let Some(instruction) = instruction {
Shorthand::Raw(RefundableInInstruction { origin: None, instruction }).encode().into()
} else {
vec![].into()
},
))
.abi_encode()
.into(),
};
*nonce += 1;
(tx, Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) })
*/
let _ = key;
let _ = nonce;
todo!()
let sig =
k256::ecdsa::SigningKey::from(k256::elliptic_curve::NonZeroScalar::new(*key).unwrap())
.sign_prehash_recoverable(tx.signature_hash().as_ref())
.unwrap();
let mut bytes = vec![];
tx.encode_with_signature_fields(&sig.into(), &mut bytes);
// We drop the bottom 10 decimals
(
bytes,
Balance { coin: Coin::Ether, amount: Amount(u64::try_from(eight_decimals).unwrap()) },
)
}
Wallet::Monero { handle, ref spend_key, ref view_pair, ref mut inputs } => {
@ -438,13 +524,10 @@ impl Wallet {
)
.unwrap()
}
Wallet::Ethereum { key, .. } => {
use ciphersuite::{Ciphersuite, Secp256k1};
ExternalAddress::new(
ethereum_serai::crypto::address(&(Secp256k1::generator() * key)).into(),
)
.unwrap()
}
Wallet::Ethereum { key, .. } => ExternalAddress::new(
ethereum_serai::crypto::address(&(ciphersuite::Secp256k1::generator() * key)).into(),
)
.unwrap(),
Wallet::Monero { view_pair, .. } => {
use monero_serai::wallet::address::{Network, AddressSpec};
ExternalAddress::new(

View file

@ -17,7 +17,8 @@ use serai_client::{
validator_sets::primitives::Session,
};
use processor::networks::{Network, Bitcoin, Monero};
use serai_db::MemDb;
use processor::networks::{Network, Bitcoin, Ethereum, Monero};
use crate::{*, tests::*};
@ -188,7 +189,7 @@ pub(crate) async fn substrate_block(
#[test]
fn batch_test() {
for network in [NetworkId::Bitcoin, NetworkId::Monero] {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@ -245,6 +246,8 @@ fn batch_test() {
// The scanner works on a 5s interval, so this leaves a few s for any processing/latency
tokio::time::sleep(Duration::from_secs(10)).await;
println!("sent in transaction. with in instruction: {}", instruction.is_some());
let expected_batch = Batch {
network,
id: i,
@ -256,10 +259,11 @@ fn batch_test() {
coin: balance_sent.coin,
amount: Amount(
balance_sent.amount.0 -
(2 * if network == NetworkId::Bitcoin {
Bitcoin::COST_TO_AGGREGATE
} else {
Monero::COST_TO_AGGREGATE
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
}),
),
},
@ -272,6 +276,8 @@ fn batch_test() {
},
};
println!("receiving batch preprocesses...");
// Make sure the processors picked it up by checking they're trying to sign a batch for it
let (mut id, mut preprocesses) =
recv_batch_preprocesses(&mut coordinators, Session(0), &expected_batch, 0).await;
@ -291,6 +297,8 @@ fn batch_test() {
recv_batch_preprocesses(&mut coordinators, Session(0), &expected_batch, attempt).await;
}
println!("signing batch...");
// Continue with signing the batch
let batch = sign_batch(&mut coordinators, key_pair.0 .0, id, preprocesses).await;

View file

@ -144,7 +144,7 @@ pub(crate) async fn key_gen(coordinators: &mut [Coordinator]) -> KeyPair {
#[test]
fn key_gen_test() {
for network in [NetworkId::Bitcoin, NetworkId::Monero] {
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {

View file

@ -20,8 +20,14 @@ pub(crate) const THRESHOLD: usize = ((COORDINATORS * 2) / 3) + 1;
fn new_test(network: NetworkId) -> (Vec<(Handles, <Ristretto as Ciphersuite>::F)>, DockerTest) {
let mut coordinators = vec![];
let mut test = DockerTest::new().with_network(dockertest::Network::Isolated);
let mut eth_handle = None;
for _ in 0 .. COORDINATORS {
let (handles, coord_key, compositions) = processor_stack(network);
let (handles, coord_key, compositions) = processor_stack(network, eth_handle.clone());
// TODO: Remove this once https://github.com/foundry-rs/foundry/issues/7955
// This has all processors share an Ethereum node until we can sync controlled nodes
if network == NetworkId::Ethereum {
eth_handle = eth_handle.or_else(|| Some(handles.0.clone()));
}
coordinators.push((handles, coord_key));
for composition in compositions {
test.provide_container(composition);

View file

@ -8,12 +8,15 @@ use dkg::{Participant, tests::clone_without};
use messages::{sign::SignId, SubstrateContext};
use serai_client::{
primitives::{BlockHash, NetworkId},
primitives::{BlockHash, NetworkId, Amount, Balance, SeraiAddress},
coins::primitives::{OutInstruction, OutInstructionWithBalance},
in_instructions::primitives::Batch,
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
validator_sets::primitives::Session,
};
use serai_db::MemDb;
use processor::networks::{Network, Bitcoin, Ethereum, Monero};
use crate::{*, tests::*};
#[allow(unused)]
@ -144,7 +147,7 @@ pub(crate) async fn sign_tx(
#[test]
fn send_test() {
for network in [NetworkId::Bitcoin, NetworkId::Monero] {
for network in [NetworkId::Bitcoin, /* TODO NetworkId::Ethereum, */ NetworkId::Monero] {
let (coordinators, test) = new_test(network);
test.run(|ops| async move {
@ -173,7 +176,11 @@ fn send_test() {
coordinators[0].sync(&ops, &coordinators[1 ..]).await;
// Send into the processor's wallet
let (tx, balance_sent) = wallet.send_to_address(&ops, &key_pair.1, None).await;
let mut serai_address = [0; 32];
OsRng.fill_bytes(&mut serai_address);
let instruction = InInstruction::Transfer(SeraiAddress(serai_address));
let (tx, balance_sent) =
wallet.send_to_address(&ops, &key_pair.1, Some(instruction.clone())).await;
for coordinator in &mut coordinators {
coordinator.publish_transacton(&ops, &tx).await;
}
@ -192,8 +199,25 @@ fn send_test() {
// The scanner works on a 5s interval, so this leaves a few s for any processing/latency
tokio::time::sleep(Duration::from_secs(10)).await;
let expected_batch =
Batch { network, id: 0, block: BlockHash(block_with_tx.unwrap()), instructions: vec![] };
let amount_minted = Amount(
balance_sent.amount.0 -
(2 * match network {
NetworkId::Bitcoin => Bitcoin::COST_TO_AGGREGATE,
NetworkId::Ethereum => Ethereum::<MemDb>::COST_TO_AGGREGATE,
NetworkId::Monero => Monero::COST_TO_AGGREGATE,
NetworkId::Serai => panic!("minted for Serai?"),
}),
);
let expected_batch = Batch {
network,
id: 0,
block: BlockHash(block_with_tx.unwrap()),
instructions: vec![InInstructionWithBalance {
instruction,
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
}],
};
// Make sure the proceessors picked it up by checking they're trying to sign a batch for it
let (id, preprocesses) =
@ -221,7 +245,7 @@ fn send_test() {
block: substrate_block_num,
burns: vec![OutInstructionWithBalance {
instruction: OutInstruction { address: wallet.address(), data: None },
balance: balance_sent,
balance: Balance { coin: balance_sent.coin, amount: amount_minted },
}],
batches: vec![batch.batch.id],
},