mirror of
https://github.com/serai-dex/serai.git
synced 2025-03-12 09:26:51 +00:00
Processor scanner tests for Ethereum
This commit is contained in:
parent
5501de1f3a
commit
0c9dd5048e
10 changed files with 329 additions and 107 deletions
|
@ -55,6 +55,8 @@ impl Client {
|
||||||
fn connector() -> Connector {
|
fn connector() -> Connector {
|
||||||
let mut res = HttpConnector::new();
|
let mut res = HttpConnector::new();
|
||||||
res.set_keepalive(Some(core::time::Duration::from_secs(60)));
|
res.set_keepalive(Some(core::time::Duration::from_secs(60)));
|
||||||
|
res.set_nodelay(true);
|
||||||
|
res.set_reuse_address(true);
|
||||||
#[cfg(feature = "tls")]
|
#[cfg(feature = "tls")]
|
||||||
let res = HttpsConnectorBuilder::new()
|
let res = HttpsConnectorBuilder::new()
|
||||||
.with_native_roots()
|
.with_native_roots()
|
||||||
|
@ -68,7 +70,9 @@ impl Client {
|
||||||
pub fn with_connection_pool() -> Client {
|
pub fn with_connection_pool() -> Client {
|
||||||
Client {
|
Client {
|
||||||
connection: Connection::ConnectionPool(
|
connection: Connection::ConnectionPool(
|
||||||
HyperClient::builder(TokioExecutor::new()).build(Self::connector()),
|
HyperClient::builder(TokioExecutor::new())
|
||||||
|
.pool_idle_timeout(core::time::Duration::from_secs(60))
|
||||||
|
.build(Self::connector()),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
~/.foundry/bin/anvil --no-mining --slots-in-an-epoch 32
|
~/.foundry/bin/anvil --host 0.0.0.0 --no-cors --no-mining --slots-in-an-epoch 32 --silent
|
||||||
|
|
|
@ -84,7 +84,7 @@ serai-docker-tests = { path = "../tests/docker" }
|
||||||
secp256k1 = ["k256", "frost/secp256k1"]
|
secp256k1 = ["k256", "frost/secp256k1"]
|
||||||
bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"]
|
bitcoin = ["dep:secp256k1", "secp256k1", "bitcoin-serai", "serai-client/bitcoin"]
|
||||||
|
|
||||||
ethereum = ["secp256k1", "ethereum-serai"]
|
ethereum = ["secp256k1", "ethereum-serai/tests"]
|
||||||
|
|
||||||
ed25519 = ["dalek-ff-group", "frost/ed25519"]
|
ed25519 = ["dalek-ff-group", "frost/ed25519"]
|
||||||
monero = ["ed25519", "monero-serai", "serai-client/monero"]
|
monero = ["ed25519", "monero-serai", "serai-client/monero"]
|
||||||
|
|
|
@ -124,7 +124,7 @@ impl SignableTransaction for RouterCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> TransactionTrait<Ethereum<D>> for Transaction {
|
impl<D: Db> TransactionTrait<Ethereum<D>> for Transaction {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
fn id(&self) -> Self::Id {
|
fn id(&self) -> Self::Id {
|
||||||
self.hash.0
|
self.hash.0
|
||||||
|
@ -157,7 +157,7 @@ impl Epoch {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> Block<Ethereum<D>> for Epoch {
|
impl<D: Db> Block<Ethereum<D>> for Epoch {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
fn id(&self) -> [u8; 32] {
|
fn id(&self) -> [u8; 32] {
|
||||||
self.end_hash
|
self.end_hash
|
||||||
|
@ -170,7 +170,7 @@ impl<D: fmt::Debug + Db> Block<Ethereum<D>> for Epoch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: fmt::Debug + Db> Output<Ethereum<D>> for EthereumInInstruction {
|
impl<D: Db> Output<Ethereum<D>> for EthereumInInstruction {
|
||||||
type Id = [u8; 32];
|
type Id = [u8; 32];
|
||||||
|
|
||||||
fn kind(&self) -> OutputType {
|
fn kind(&self) -> OutputType {
|
||||||
|
@ -282,8 +282,8 @@ impl EventualityTrait for Eventuality {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct Ethereum<D: fmt::Debug + Db> {
|
pub struct Ethereum<D: Db> {
|
||||||
// This DB is solely used to access the first key generated, as needed to determine the Router's
|
// This DB is solely used to access the first key generated, as needed to determine the Router's
|
||||||
// address. Accordingly, all methods present are consistent to a Serai chain with a finalized
|
// address. Accordingly, all methods present are consistent to a Serai chain with a finalized
|
||||||
// first key (regardless of local state), and this is safe.
|
// first key (regardless of local state), and this is safe.
|
||||||
|
@ -292,20 +292,26 @@ pub struct Ethereum<D: fmt::Debug + Db> {
|
||||||
deployer: Deployer,
|
deployer: Deployer,
|
||||||
router: Arc<RwLock<Option<Router>>>,
|
router: Arc<RwLock<Option<Router>>>,
|
||||||
}
|
}
|
||||||
impl<D: fmt::Debug + Db> PartialEq for Ethereum<D> {
|
impl<D: Db> PartialEq for Ethereum<D> {
|
||||||
fn eq(&self, _other: &Ethereum<D>) -> bool {
|
fn eq(&self, _other: &Ethereum<D>) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<D: fmt::Debug + Db> Ethereum<D> {
|
impl<D: Db> fmt::Debug for Ethereum<D> {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt
|
||||||
|
.debug_struct("Ethereum")
|
||||||
|
.field("deployer", &self.deployer)
|
||||||
|
.field("router", &self.router)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<D: Db> Ethereum<D> {
|
||||||
pub async fn new(db: D, url: String) -> Self {
|
pub async fn new(db: D, url: String) -> Self {
|
||||||
let provider = Arc::new(RootProvider::new(
|
let provider = Arc::new(RootProvider::new(
|
||||||
ClientBuilder::default().transport(SimpleRequest::new(url), true),
|
ClientBuilder::default().transport(SimpleRequest::new(url), true),
|
||||||
));
|
));
|
||||||
|
|
||||||
#[cfg(test)] // TODO: Move to test code
|
|
||||||
provider.raw_request::<_, ()>("evm_setAutomine".into(), false).await.unwrap();
|
|
||||||
|
|
||||||
let mut deployer = Deployer::new(provider.clone()).await;
|
let mut deployer = Deployer::new(provider.clone()).await;
|
||||||
while !matches!(deployer, Ok(Some(_))) {
|
while !matches!(deployer, Ok(Some(_))) {
|
||||||
log::error!("Deployer wasn't deployed yet or networking error");
|
log::error!("Deployer wasn't deployed yet or networking error");
|
||||||
|
@ -362,7 +368,7 @@ impl<D: fmt::Debug + Db> Ethereum<D> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
impl<D: Db> Network for Ethereum<D> {
|
||||||
type Curve = Secp256k1;
|
type Curve = Secp256k1;
|
||||||
|
|
||||||
type Transaction = Transaction;
|
type Transaction = Transaction;
|
||||||
|
@ -479,7 +485,8 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||||
// Grab the key at the end of the epoch
|
// Grab the key at the end of the epoch
|
||||||
let key_at_end_of_block = loop {
|
let key_at_end_of_block = loop {
|
||||||
match router.key_at_end_of_block(block.start + 31).await {
|
match router.key_at_end_of_block(block.start + 31).await {
|
||||||
Ok(key) => break key,
|
Ok(Some(key)) => break key,
|
||||||
|
Ok(None) => return vec![],
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("couldn't connect to router for the key at the end of the block: {e:?}");
|
log::error!("couldn't connect to router for the key at the end of the block: {e:?}");
|
||||||
sleep(Duration::from_secs(5)).await;
|
sleep(Duration::from_secs(5)).await;
|
||||||
|
@ -491,17 +498,7 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||||
let mut all_events = vec![];
|
let mut all_events = vec![];
|
||||||
let mut top_level_txids = HashSet::new();
|
let mut top_level_txids = HashSet::new();
|
||||||
for erc20_addr in [DAI] {
|
for erc20_addr in [DAI] {
|
||||||
let erc20 = loop {
|
let erc20 = Erc20::new(self.provider.clone(), erc20_addr);
|
||||||
let Ok(Some(erc20)) = Erc20::new(self.provider.clone(), erc20_addr).await else {
|
|
||||||
log::error!(
|
|
||||||
"couldn't connect to Ethereum node for an ERC20: {}",
|
|
||||||
hex::encode(erc20_addr)
|
|
||||||
);
|
|
||||||
sleep(Duration::from_secs(5)).await;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
break erc20;
|
|
||||||
};
|
|
||||||
|
|
||||||
for block in block.start .. (block.start + 32) {
|
for block in block.start .. (block.start + 32) {
|
||||||
let transfers = loop {
|
let transfers = loop {
|
||||||
|
@ -821,6 +818,7 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||||
.provider
|
.provider
|
||||||
.get_transaction_by_hash(log.clone().transaction_hash.unwrap())
|
.get_transaction_by_hash(log.clone().transaction_hash.unwrap())
|
||||||
.await
|
.await
|
||||||
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -830,20 +828,26 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||||
.to_block(((block + 1) * 32) - 1)
|
.to_block(((block + 1) * 32) - 1)
|
||||||
.topic1(nonce);
|
.topic1(nonce);
|
||||||
let logs = self.provider.get_logs(&filter).await.unwrap();
|
let logs = self.provider.get_logs(&filter).await.unwrap();
|
||||||
self.provider.get_transaction_by_hash(logs[0].transaction_hash.unwrap()).await.unwrap()
|
self
|
||||||
|
.provider
|
||||||
|
.get_transaction_by_hash(logs[0].transaction_hash.unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn mine_block(&self) {
|
async fn mine_block(&self) {
|
||||||
self.provider.raw_request::<_, ()>("anvil_mine".into(), [32]).await.unwrap();
|
self.provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
async fn test_send(&self, send_to: Self::Address) -> Self::Block {
|
async fn test_send(&self, send_to: Self::Address) -> Self::Block {
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
use ciphersuite::group::ff::Field;
|
use ciphersuite::group::ff::Field;
|
||||||
|
use ethereum_serai::alloy_sol_types::SolCall;
|
||||||
|
|
||||||
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
let key = <Secp256k1 as Ciphersuite>::F::random(&mut OsRng);
|
||||||
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
let address = ethereum_serai::crypto::address(&(Secp256k1::generator() * key));
|
||||||
|
@ -858,15 +862,22 @@ impl<D: fmt::Debug + Db> Network for Ethereum<D> {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let value = U256::from_str_radix("1000000000000000000", 10).unwrap();
|
||||||
let tx = ethereum_serai::alloy_consensus::TxLegacy {
|
let tx = ethereum_serai::alloy_consensus::TxLegacy {
|
||||||
chain_id: None,
|
chain_id: None,
|
||||||
nonce: 0,
|
nonce: 0,
|
||||||
gas_price: 100_000_000_000u128,
|
gas_price: 1_000_000_000u128,
|
||||||
gas_limit: 21_0000u128,
|
gas_limit: 200_000u128,
|
||||||
to: ethereum_serai::alloy_core::primitives::TxKind::Call(send_to.0.into()),
|
to: ethereum_serai::alloy_core::primitives::TxKind::Call(send_to.0.into()),
|
||||||
// 1 ETH
|
// 1 ETH
|
||||||
value: U256::from_str_radix("1000000000000000000", 10).unwrap(),
|
value,
|
||||||
input: vec![].into(),
|
input: ethereum_serai::router::abi::inInstructionCall::new((
|
||||||
|
[0; 20].into(),
|
||||||
|
value,
|
||||||
|
vec![].into(),
|
||||||
|
))
|
||||||
|
.abi_encode()
|
||||||
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
use ethereum_serai::alloy_consensus::SignableTransaction;
|
use ethereum_serai::alloy_consensus::SignableTransaction;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use core::time::Duration;
|
use core::{time::Duration, pin::Pin, future::Future};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
@ -82,8 +82,9 @@ async fn spend<N: UtxoNetwork, D: Db>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_addresses<N: UtxoNetwork>(network: N)
|
pub async fn test_addresses<N: UtxoNetwork>(
|
||||||
where
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) where
|
||||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||||
{
|
{
|
||||||
let mut keys = frost::tests::key_gen::<_, N::Curve>(&mut OsRng);
|
let mut keys = frost::tests::key_gen::<_, N::Curve>(&mut OsRng);
|
||||||
|
@ -92,12 +93,14 @@ where
|
||||||
}
|
}
|
||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
let mut txn = db.txn();
|
let mut txn = db.txn();
|
||||||
|
|
|
@ -3,6 +3,8 @@ use dockertest::{
|
||||||
TestBodySpecification, DockerOperations, DockerTest,
|
TestBodySpecification, DockerOperations, DockerTest,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serai_db::MemDb;
|
||||||
|
|
||||||
#[cfg(feature = "bitcoin")]
|
#[cfg(feature = "bitcoin")]
|
||||||
mod bitcoin {
|
mod bitcoin {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -33,8 +35,6 @@ mod bitcoin {
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serai_db::MemDb;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
networks::{Network, Bitcoin, Output, OutputType, Block},
|
networks::{Network, Bitcoin, Output, OutputType, Block},
|
||||||
|
@ -57,7 +57,7 @@ mod bitcoin {
|
||||||
fn test_receive_data_from_input() {
|
fn test_receive_data_from_input() {
|
||||||
let docker = spawn_bitcoin();
|
let docker = spawn_bitcoin();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
let btc = bitcoin(&ops).await;
|
let btc = bitcoin(&ops).await(MemDb::new()).await;
|
||||||
|
|
||||||
// generate a multisig address to receive the coins
|
// generate a multisig address to receive the coins
|
||||||
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
||||||
|
@ -208,23 +208,26 @@ mod bitcoin {
|
||||||
test
|
test
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bitcoin(ops: &DockerOperations) -> Bitcoin {
|
async fn bitcoin(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Bitcoin>>> {
|
||||||
let handle = ops.handle("serai-dev-bitcoin").host_port(8332).unwrap();
|
let handle = ops.handle("serai-dev-bitcoin").host_port(8332).unwrap();
|
||||||
let bitcoin = Bitcoin::new(format!("http://serai:seraidex@{}:{}", handle.0, handle.1)).await;
|
let url = format!("http://serai:seraidex@{}:{}", handle.0, handle.1);
|
||||||
|
let bitcoin = Bitcoin::new(url.clone()).await;
|
||||||
bitcoin.fresh_chain().await;
|
bitcoin.fresh_chain().await;
|
||||||
bitcoin
|
move |_db| Box::pin(Bitcoin::new(url.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
test_network!(
|
test_utxo_network!(
|
||||||
Bitcoin,
|
Bitcoin,
|
||||||
spawn_bitcoin,
|
spawn_bitcoin,
|
||||||
bitcoin,
|
bitcoin,
|
||||||
bitcoin_key_gen,
|
bitcoin_key_gen,
|
||||||
bitcoin_scanner,
|
bitcoin_scanner,
|
||||||
|
bitcoin_no_deadlock_in_multisig_completed,
|
||||||
bitcoin_signer,
|
bitcoin_signer,
|
||||||
bitcoin_wallet,
|
bitcoin_wallet,
|
||||||
bitcoin_addresses,
|
bitcoin_addresses,
|
||||||
bitcoin_no_deadlock_in_multisig_completed,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,24 +255,181 @@ mod monero {
|
||||||
test
|
test
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn monero(ops: &DockerOperations) -> Monero {
|
async fn monero(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Monero>>> {
|
||||||
let handle = ops.handle("serai-dev-monero").host_port(18081).unwrap();
|
let handle = ops.handle("serai-dev-monero").host_port(18081).unwrap();
|
||||||
let monero = Monero::new(format!("http://serai:seraidex@{}:{}", handle.0, handle.1)).await;
|
let url = format!("http://serai:seraidex@{}:{}", handle.0, handle.1);
|
||||||
|
let monero = Monero::new(url.clone()).await;
|
||||||
while monero.get_latest_block_number().await.unwrap() < 150 {
|
while monero.get_latest_block_number().await.unwrap() < 150 {
|
||||||
monero.mine_block().await;
|
monero.mine_block().await;
|
||||||
}
|
}
|
||||||
monero
|
move |_db| Box::pin(Monero::new(url.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
test_network!(
|
test_utxo_network!(
|
||||||
Monero,
|
Monero,
|
||||||
spawn_monero,
|
spawn_monero,
|
||||||
monero,
|
monero,
|
||||||
monero_key_gen,
|
monero_key_gen,
|
||||||
monero_scanner,
|
monero_scanner,
|
||||||
|
monero_no_deadlock_in_multisig_completed,
|
||||||
monero_signer,
|
monero_signer,
|
||||||
monero_wallet,
|
monero_wallet,
|
||||||
monero_addresses,
|
monero_addresses,
|
||||||
monero_no_deadlock_in_multisig_completed,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ethereum")]
|
||||||
|
mod ethereum {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use ciphersuite::{Ciphersuite, Secp256k1};
|
||||||
|
|
||||||
|
use serai_client::validator_sets::primitives::Session;
|
||||||
|
|
||||||
|
use crate::networks::Ethereum;
|
||||||
|
|
||||||
|
fn spawn_ethereum() -> DockerTest {
|
||||||
|
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_log_options(Some(LogOptions {
|
||||||
|
action: LogAction::Forward,
|
||||||
|
policy: LogPolicy::OnError,
|
||||||
|
source: LogSource::Both,
|
||||||
|
}))
|
||||||
|
.set_publish_all_ports(true);
|
||||||
|
|
||||||
|
let mut test = DockerTest::new();
|
||||||
|
test.provide_container(composition);
|
||||||
|
test
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ethereum(
|
||||||
|
ops: &DockerOperations,
|
||||||
|
) -> impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = Ethereum<MemDb>>>> {
|
||||||
|
use std::sync::Arc;
|
||||||
|
use ethereum_serai::{
|
||||||
|
alloy_core::primitives::U256,
|
||||||
|
alloy_simple_request_transport::SimpleRequest,
|
||||||
|
alloy_rpc_client::ClientBuilder,
|
||||||
|
alloy_provider::{Provider, RootProvider},
|
||||||
|
deployer::Deployer,
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = ops.handle("serai-dev-ethereum").host_port(8545).unwrap();
|
||||||
|
let url = format!("http://{}:{}", handle.0, handle.1);
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(15)).await;
|
||||||
|
|
||||||
|
{
|
||||||
|
let provider = Arc::new(RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(url.clone()), true),
|
||||||
|
));
|
||||||
|
provider.raw_request::<_, ()>("evm_setAutomine".into(), [false]).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
|
||||||
|
// Perform deployment
|
||||||
|
{
|
||||||
|
// Make sure the Deployer constructor returns None, as it doesn't exist yet
|
||||||
|
assert!(Deployer::new(provider.clone()).await.unwrap().is_none());
|
||||||
|
|
||||||
|
// Deploy the Deployer
|
||||||
|
let tx = Deployer::deployment_tx();
|
||||||
|
|
||||||
|
provider
|
||||||
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[
|
||||||
|
tx.recover_signer().unwrap().to_string(),
|
||||||
|
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)).to_string(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, sig, _) = tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
|
||||||
|
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
//tokio::time::sleep(core::time::Duration::from_secs(15)).await;
|
||||||
|
let receipt = pending_tx.get_receipt().await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let _ = Deployer::new(provider.clone())
|
||||||
|
.await
|
||||||
|
.expect("network error")
|
||||||
|
.expect("deployer wasn't deployed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move |db| {
|
||||||
|
let url = url.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
{
|
||||||
|
let db = db.clone();
|
||||||
|
let url = url.clone();
|
||||||
|
// Spawn a task to deploy the proper Router when the time comes
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let key = loop {
|
||||||
|
let Some(key) = crate::key_gen::NetworkKeyDb::get(&db, Session(0)) else {
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(1)).await;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
break ethereum_serai::crypto::PublicKey::new(
|
||||||
|
Secp256k1::read_G(&mut key.as_slice()).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
};
|
||||||
|
let provider = Arc::new(RootProvider::new(
|
||||||
|
ClientBuilder::default().transport(SimpleRequest::new(url.clone()), true),
|
||||||
|
));
|
||||||
|
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
|
||||||
|
|
||||||
|
let mut tx = deployer.deploy_router(&key);
|
||||||
|
tx.gas_limit = 1_000_000u64.into();
|
||||||
|
tx.gas_price = 1_000_000_000u64.into();
|
||||||
|
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
|
||||||
|
|
||||||
|
provider
|
||||||
|
.raw_request::<_, ()>(
|
||||||
|
"anvil_setBalance".into(),
|
||||||
|
[
|
||||||
|
tx.recover_signer().unwrap().to_string(),
|
||||||
|
(U256::from(tx.tx().gas_limit) * U256::from(tx.tx().gas_price)).to_string(),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (tx, sig, _) = tx.into_parts();
|
||||||
|
let mut bytes = vec![];
|
||||||
|
tx.encode_with_signature_fields(&sig, &mut bytes);
|
||||||
|
let pending_tx = provider.send_raw_transaction(&bytes).await.unwrap();
|
||||||
|
provider.raw_request::<_, ()>("anvil_mine".into(), [96]).await.unwrap();
|
||||||
|
let receipt = pending_tx.get_receipt().await.unwrap();
|
||||||
|
assert!(receipt.status());
|
||||||
|
|
||||||
|
let _router = deployer.find_router(provider.clone(), &key).await.unwrap().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ethereum::new(db, url.clone()).await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test_network!(
|
||||||
|
Ethereum<MemDb>,
|
||||||
|
spawn_ethereum,
|
||||||
|
ethereum,
|
||||||
|
ethereum_key_gen,
|
||||||
|
ethereum_scanner,
|
||||||
|
ethereum_no_deadlock_in_multisig_completed,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
mod key_gen;
|
mod key_gen;
|
||||||
pub(crate) use key_gen::test_key_gen;
|
|
||||||
|
|
||||||
mod scanner;
|
mod scanner;
|
||||||
pub(crate) use scanner::{test_scanner, test_no_deadlock_in_multisig_completed};
|
|
||||||
|
|
||||||
mod signer;
|
mod signer;
|
||||||
pub(crate) use signer::{sign, test_signer};
|
pub(crate) use signer::sign;
|
||||||
|
|
||||||
mod cosigner;
|
mod cosigner;
|
||||||
mod batch_signer;
|
mod batch_signer;
|
||||||
|
|
||||||
mod wallet;
|
mod wallet;
|
||||||
pub(crate) use wallet::test_wallet;
|
|
||||||
|
|
||||||
mod addresses;
|
mod addresses;
|
||||||
pub(crate) use addresses::test_addresses;
|
|
||||||
|
|
||||||
// Effective Once
|
// Effective Once
|
||||||
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
||||||
|
@ -27,22 +23,21 @@ fn init_logger() {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! test_network {
|
macro_rules! test_network {
|
||||||
(
|
(
|
||||||
$N: ident,
|
$N: ty,
|
||||||
$docker: ident,
|
$docker: ident,
|
||||||
$network: ident,
|
$network: ident,
|
||||||
$key_gen: ident,
|
$key_gen: ident,
|
||||||
$scanner: ident,
|
$scanner: ident,
|
||||||
$signer: ident,
|
|
||||||
$wallet: ident,
|
|
||||||
$addresses: ident,
|
|
||||||
$no_deadlock_in_multisig_completed: ident,
|
$no_deadlock_in_multisig_completed: ident,
|
||||||
) => {
|
) => {
|
||||||
|
use core::{pin::Pin, future::Future};
|
||||||
use $crate::tests::{
|
use $crate::tests::{
|
||||||
init_logger, test_key_gen, test_scanner, test_no_deadlock_in_multisig_completed, test_signer,
|
init_logger,
|
||||||
test_wallet, test_addresses,
|
key_gen::test_key_gen,
|
||||||
|
scanner::{test_scanner, test_no_deadlock_in_multisig_completed},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This doesn't interact with a node and accordingly doesn't need to be run
|
// This doesn't interact with a node and accordingly doesn't need to be spawn one
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn $key_gen() {
|
async fn $key_gen() {
|
||||||
init_logger();
|
init_logger();
|
||||||
|
@ -54,34 +49,8 @@ macro_rules! test_network {
|
||||||
init_logger();
|
init_logger();
|
||||||
let docker = $docker();
|
let docker = $docker();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
test_scanner($network(&ops).await).await;
|
let new_network = $network(&ops).await;
|
||||||
});
|
test_scanner(new_network).await;
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $signer() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_signer($network(&ops).await).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $wallet() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_wallet($network(&ops).await).await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn $addresses() {
|
|
||||||
init_logger();
|
|
||||||
let docker = $docker();
|
|
||||||
docker.run(|ops| async move {
|
|
||||||
test_addresses($network(&ops).await).await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +59,57 @@ macro_rules! test_network {
|
||||||
init_logger();
|
init_logger();
|
||||||
let docker = $docker();
|
let docker = $docker();
|
||||||
docker.run(|ops| async move {
|
docker.run(|ops| async move {
|
||||||
test_no_deadlock_in_multisig_completed($network(&ops).await).await;
|
let new_network = $network(&ops).await;
|
||||||
|
test_no_deadlock_in_multisig_completed(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! test_utxo_network {
|
||||||
|
(
|
||||||
|
$N: ty,
|
||||||
|
$docker: ident,
|
||||||
|
$network: ident,
|
||||||
|
$key_gen: ident,
|
||||||
|
$scanner: ident,
|
||||||
|
$no_deadlock_in_multisig_completed: ident,
|
||||||
|
$signer: ident,
|
||||||
|
$wallet: ident,
|
||||||
|
$addresses: ident,
|
||||||
|
) => {
|
||||||
|
use $crate::tests::{signer::test_signer, wallet::test_wallet, addresses::test_addresses};
|
||||||
|
|
||||||
|
test_network!($N, $docker, $network, $key_gen, $scanner, $no_deadlock_in_multisig_completed,);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $signer() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_signer(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $wallet() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_wallet(new_network).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn $addresses() {
|
||||||
|
init_logger();
|
||||||
|
let docker = $docker();
|
||||||
|
docker.run(|ops| async move {
|
||||||
|
let new_network = $network(&ops).await;
|
||||||
|
test_addresses(new_network).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
use core::time::Duration;
|
use core::{pin::Pin, time::Duration, future::Future};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ciphersuite::Ciphersuite;
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||||
use frost::{Participant, tests::key_gen};
|
use frost::{Participant, tests::key_gen};
|
||||||
|
|
||||||
use tokio::{sync::Mutex, time::timeout};
|
use tokio::{sync::Mutex, time::timeout};
|
||||||
|
|
||||||
use serai_db::{DbTxn, Db, MemDb};
|
use serai_db::{DbTxn, Db, MemDb};
|
||||||
|
use serai_client::validator_sets::primitives::Session;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
networks::{OutputType, Output, Block, UtxoNetwork},
|
networks::{OutputType, Output, Block, Network},
|
||||||
|
key_gen::NetworkKeyDb,
|
||||||
multisigs::scanner::{ScannerEvent, Scanner, ScannerHandle},
|
multisigs::scanner::{ScannerEvent, Scanner, ScannerHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn new_scanner<N: UtxoNetwork, D: Db>(
|
pub async fn new_scanner<N: Network, D: Db>(
|
||||||
network: &N,
|
network: &N,
|
||||||
db: &D,
|
db: &D,
|
||||||
group_key: <N::Curve as Ciphersuite>::G,
|
group_key: <N::Curve as Ciphersuite>::G,
|
||||||
|
@ -40,18 +42,27 @@ pub async fn new_scanner<N: UtxoNetwork, D: Db>(
|
||||||
scanner
|
scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_scanner<N: UtxoNetwork>(network: N) {
|
pub async fn test_scanner<N: Network>(
|
||||||
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) {
|
||||||
let mut keys =
|
let mut keys =
|
||||||
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
||||||
N::tweak_keys(&mut keys);
|
N::tweak_keys(&mut keys);
|
||||||
let group_key = keys.group_key();
|
let group_key = keys.group_key();
|
||||||
|
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
{
|
||||||
|
let mut txn = db.txn();
|
||||||
|
NetworkKeyDb::set(&mut txn, Session(0), &group_key.to_bytes().as_ref().to_vec());
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let db = MemDb::new();
|
|
||||||
let first = Arc::new(Mutex::new(true));
|
let first = Arc::new(Mutex::new(true));
|
||||||
let scanner = new_scanner(&network, &db, group_key, &first).await;
|
let scanner = new_scanner(&network, &db, group_key, &first).await;
|
||||||
|
|
||||||
|
@ -101,13 +112,17 @@ pub async fn test_scanner<N: UtxoNetwork>(network: N) {
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_no_deadlock_in_multisig_completed<N: UtxoNetwork>(network: N) {
|
pub async fn test_no_deadlock_in_multisig_completed<N: Network>(
|
||||||
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) {
|
||||||
|
let mut db = MemDb::new();
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
// Mine blocks so there's a confirmed block
|
// Mine blocks so there's a confirmed block
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use core::{pin::Pin, future::Future};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{RngCore, OsRng};
|
||||||
|
@ -153,8 +154,9 @@ pub async fn sign<N: UtxoNetwork>(
|
||||||
typed_claim
|
typed_claim
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn test_signer<N: UtxoNetwork>(network: N)
|
pub async fn test_signer<N: UtxoNetwork>(
|
||||||
where
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
|
) where
|
||||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||||
{
|
{
|
||||||
let mut keys = key_gen(&mut OsRng);
|
let mut keys = key_gen(&mut OsRng);
|
||||||
|
@ -163,6 +165,9 @@ where
|
||||||
}
|
}
|
||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
|
let db = MemDb::new();
|
||||||
|
let network = new_network(db).await;
|
||||||
|
|
||||||
let outputs = network
|
let outputs = network
|
||||||
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::{time::Duration, collections::HashMap};
|
use core::{time::Duration, pin::Pin, future::Future};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use rand_core::OsRng;
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
@ -24,12 +25,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tests the Scanner, Scheduler, and Signer together
|
// Tests the Scanner, Scheduler, and Signer together
|
||||||
pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
pub async fn test_wallet<N: UtxoNetwork>(
|
||||||
// Mine blocks so there's a confirmed block
|
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||||
for _ in 0 .. N::CONFIRMATIONS {
|
) {
|
||||||
network.mine_block().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut keys = key_gen(&mut OsRng);
|
let mut keys = key_gen(&mut OsRng);
|
||||||
for keys in keys.values_mut() {
|
for keys in keys.values_mut() {
|
||||||
N::tweak_keys(keys);
|
N::tweak_keys(keys);
|
||||||
|
@ -37,6 +35,13 @@ pub async fn test_wallet<N: UtxoNetwork>(network: N) {
|
||||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||||
|
|
||||||
let mut db = MemDb::new();
|
let mut db = MemDb::new();
|
||||||
|
let network = new_network(db.clone()).await;
|
||||||
|
|
||||||
|
// Mine blocks so there's a confirmed block
|
||||||
|
for _ in 0 .. N::CONFIRMATIONS {
|
||||||
|
network.mine_block().await;
|
||||||
|
}
|
||||||
|
|
||||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||||
assert!(current_keys.is_empty());
|
assert!(current_keys.is_empty());
|
||||||
let (block_id, outputs) = {
|
let (block_id, outputs) = {
|
||||||
|
|
Loading…
Reference in a new issue