mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Merge branch 'develop' into next
This resolves the conflicts and gets the workspace `Cargo.toml`s to not be invalid. It doesn't actually get clippy to pass again yet. Does move `crypto/dkg/src/evrf` into a new `crypto/dkg/evrf` crate (which does not yet compile).
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
async fn handle_coordinator_msg<D: Db, N: Network, Co: Coordinator>(
|
||||
txn: &mut D::Transaction<'_>,
|
||||
network: &N,
|
||||
coordinator: &mut Co,
|
||||
tributary_mutable: &mut TributaryMutable<N, D>,
|
||||
substrate_mutable: &mut SubstrateMutable<N, D>,
|
||||
msg: &Message,
|
||||
) {
|
||||
match msg.msg.clone() {
|
||||
CoordinatorMessage::Substrate(msg) => {
|
||||
match msg {
|
||||
messages::substrate::CoordinatorMessage::SubstrateBlock {
|
||||
context,
|
||||
block: substrate_block,
|
||||
burns,
|
||||
batches,
|
||||
} => {
|
||||
// Send SubstrateBlockAck, with relevant plan IDs, before we trigger the signing of these
|
||||
// plans
|
||||
if !tributary_mutable.signers.is_empty() {
|
||||
coordinator
|
||||
.send(messages::coordinator::ProcessorMessage::SubstrateBlockAck {
|
||||
block: substrate_block,
|
||||
plans: to_sign
|
||||
.iter()
|
||||
.filter_map(|signable| {
|
||||
SessionDb::get(txn, signable.0.to_bytes().as_ref())
|
||||
.map(|session| PlanMeta { session, id: signable.1 })
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use core::{time::Duration, pin::Pin, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use frost::{Participant, ThresholdKeys};
|
||||
|
||||
use tokio::time::timeout;
|
||||
|
||||
use serai_client::validator_sets::primitives::Session;
|
||||
|
||||
use serai_db::{DbTxn, MemDb};
|
||||
|
||||
use crate::{
|
||||
Plan, Db,
|
||||
networks::{OutputType, Output, Block, UtxoNetwork},
|
||||
multisigs::{
|
||||
scheduler::Scheduler,
|
||||
scanner::{ScannerEvent, Scanner, ScannerHandle},
|
||||
},
|
||||
tests::sign,
|
||||
};
|
||||
|
||||
async fn spend<N: UtxoNetwork, D: Db>(
|
||||
db: &mut D,
|
||||
network: &N,
|
||||
keys: &HashMap<Participant, ThresholdKeys<N::Curve>>,
|
||||
scanner: &mut ScannerHandle<N, D>,
|
||||
outputs: Vec<N::Output>,
|
||||
) where
|
||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||
{
|
||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||
|
||||
let mut keys_txs = HashMap::new();
|
||||
for (i, keys) in keys {
|
||||
keys_txs.insert(
|
||||
*i,
|
||||
(
|
||||
keys.clone(),
|
||||
network
|
||||
.prepare_send(
|
||||
network.get_latest_block_number().await.unwrap() - N::CONFIRMATIONS,
|
||||
// Send to a change output
|
||||
Plan {
|
||||
key,
|
||||
inputs: outputs.clone(),
|
||||
payments: vec![],
|
||||
change: Some(N::change_address(key).unwrap()),
|
||||
scheduler_addendum: ().into(),
|
||||
},
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.tx
|
||||
.unwrap(),
|
||||
),
|
||||
);
|
||||
}
|
||||
sign(network.clone(), Session(0), keys_txs).await;
|
||||
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(outputs.len(), 1);
|
||||
// Make sure this is actually a change output
|
||||
assert_eq!(outputs[0].kind(), OutputType::Change);
|
||||
assert_eq!(outputs[0].key(), key);
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_addresses<N: UtxoNetwork>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) where
|
||||
<N::Scheduler as Scheduler<N>>::Addendum: From<()>,
|
||||
{
|
||||
let mut keys = frost::tests::key_gen::<_, N::Curve>(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
N::tweak_keys(keys);
|
||||
}
|
||||
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
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||
assert!(current_keys.is_empty());
|
||||
let mut txn = db.txn();
|
||||
scanner.register_key(&mut txn, network.get_latest_block_number().await.unwrap(), key).await;
|
||||
txn.commit();
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
// Receive funds to the various addresses and make sure they're properly identified
|
||||
let mut received_outputs = vec![];
|
||||
for (kind, address) in [
|
||||
(OutputType::External, N::external_address(&network, key).await),
|
||||
(OutputType::Branch, N::branch_address(key).unwrap()),
|
||||
(OutputType::Change, N::change_address(key).unwrap()),
|
||||
(OutputType::Forwarded, N::forward_address(key).unwrap()),
|
||||
] {
|
||||
let block_id = network.test_send(address).await.id();
|
||||
|
||||
// Verify the Scanner picked them up
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block, block_id);
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].kind(), kind);
|
||||
assert_eq!(outputs[0].key(), key);
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
received_outputs.extend(outputs);
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Spend the branch output, creating a change output and ensuring we actually get change
|
||||
spend(&mut db, &network, &keys, &mut scanner, received_outputs).await;
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
use frost::{
|
||||
curve::Ristretto,
|
||||
Participant,
|
||||
dkg::tests::{key_gen, clone_without},
|
||||
};
|
||||
|
||||
use sp_application_crypto::{RuntimePublic, sr25519::Public};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
#[rustfmt::skip]
|
||||
use serai_client::{primitives::*, in_instructions::primitives::*, validator_sets::primitives::Session};
|
||||
|
||||
use messages::{
|
||||
substrate,
|
||||
coordinator::{self, SubstrateSignableId, SubstrateSignId, CoordinatorMessage},
|
||||
ProcessorMessage,
|
||||
};
|
||||
use crate::batch_signer::BatchSigner;
|
||||
|
||||
#[test]
|
||||
fn test_batch_signer() {
|
||||
let keys = key_gen::<_, Ristretto>(&mut OsRng);
|
||||
|
||||
let participant_one = Participant::new(1).unwrap();
|
||||
|
||||
let id: u32 = 5;
|
||||
let block = BlockHash([0xaa; 32]);
|
||||
|
||||
let batch = Batch {
|
||||
network: ExternalNetworkId::Monero,
|
||||
id,
|
||||
block,
|
||||
instructions: vec![
|
||||
InInstructionWithBalance {
|
||||
instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])),
|
||||
balance: ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(1000) },
|
||||
},
|
||||
InInstructionWithBalance {
|
||||
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(SeraiAddress([0xbb; 32]))),
|
||||
balance: ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(9999999999999999) },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let actual_id =
|
||||
SubstrateSignId { session: Session(0), id: SubstrateSignableId::Batch(batch.id), attempt: 0 };
|
||||
|
||||
let mut signing_set = vec![];
|
||||
while signing_set.len() < usize::from(keys.values().next().unwrap().params().t()) {
|
||||
let candidate = Participant::new(
|
||||
u16::try_from((OsRng.next_u64() % u64::try_from(keys.len()).unwrap()) + 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
if signing_set.contains(&candidate) {
|
||||
continue;
|
||||
}
|
||||
signing_set.push(candidate);
|
||||
}
|
||||
|
||||
let mut signers = HashMap::new();
|
||||
let mut dbs = HashMap::new();
|
||||
let mut preprocesses = HashMap::new();
|
||||
for i in 1 ..= keys.len() {
|
||||
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
||||
let keys = keys.get(&i).unwrap().clone();
|
||||
|
||||
let mut signer = BatchSigner::<MemDb>::new(ExternalNetworkId::Monero, Session(0), vec![keys]);
|
||||
let mut db = MemDb::new();
|
||||
|
||||
let mut txn = db.txn();
|
||||
match signer.sign(&mut txn, batch.clone()).unwrap() {
|
||||
// All participants should emit a preprocess
|
||||
coordinator::ProcessorMessage::BatchPreprocess {
|
||||
id,
|
||||
block: batch_block,
|
||||
preprocesses: mut these_preprocesses,
|
||||
} => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(batch_block, block);
|
||||
assert_eq!(these_preprocesses.len(), 1);
|
||||
if signing_set.contains(&i) {
|
||||
preprocesses.insert(i, these_preprocesses.swap_remove(0));
|
||||
}
|
||||
}
|
||||
_ => panic!("didn't get preprocess back"),
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
signers.insert(i, signer);
|
||||
dbs.insert(i, db);
|
||||
}
|
||||
|
||||
let mut shares = HashMap::new();
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::SubstratePreprocesses {
|
||||
id: actual_id.clone(),
|
||||
preprocesses: clone_without(&preprocesses, i),
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::Coordinator(coordinator::ProcessorMessage::SubstrateShare {
|
||||
id,
|
||||
shares: mut these_shares,
|
||||
}) => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(these_shares.len(), 1);
|
||||
shares.insert(*i, these_shares.swap_remove(0));
|
||||
}
|
||||
_ => panic!("didn't get share back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::SubstrateShares {
|
||||
id: actual_id.clone(),
|
||||
shares: clone_without(&shares, i),
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::Substrate(substrate::ProcessorMessage::SignedBatch {
|
||||
batch: signed_batch,
|
||||
}) => {
|
||||
assert_eq!(signed_batch.batch, batch);
|
||||
assert!(Public::from_raw(keys[&participant_one].group_key().to_bytes())
|
||||
.verify(&batch_message(&batch), &signed_batch.signature));
|
||||
}
|
||||
_ => panic!("didn't get signed batch back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
use frost::{
|
||||
curve::Ristretto,
|
||||
Participant,
|
||||
dkg::tests::{key_gen, clone_without},
|
||||
};
|
||||
|
||||
use sp_application_crypto::{RuntimePublic, sr25519::Public};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
use serai_client::{primitives::*, validator_sets::primitives::Session};
|
||||
|
||||
use messages::coordinator::*;
|
||||
use crate::cosigner::Cosigner;
|
||||
|
||||
#[test]
|
||||
fn test_cosigner() {
|
||||
let keys = key_gen::<_, Ristretto>(&mut OsRng);
|
||||
|
||||
let participant_one = Participant::new(1).unwrap();
|
||||
|
||||
let block_number = OsRng.next_u64();
|
||||
let block = [0xaa; 32];
|
||||
|
||||
let actual_id = SubstrateSignId {
|
||||
session: Session(0),
|
||||
id: SubstrateSignableId::CosigningSubstrateBlock(block),
|
||||
attempt: (OsRng.next_u64() >> 32).try_into().unwrap(),
|
||||
};
|
||||
|
||||
let mut signing_set = vec![];
|
||||
while signing_set.len() < usize::from(keys.values().next().unwrap().params().t()) {
|
||||
let candidate = Participant::new(
|
||||
u16::try_from((OsRng.next_u64() % u64::try_from(keys.len()).unwrap()) + 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
if signing_set.contains(&candidate) {
|
||||
continue;
|
||||
}
|
||||
signing_set.push(candidate);
|
||||
}
|
||||
|
||||
let mut signers = HashMap::new();
|
||||
let mut dbs = HashMap::new();
|
||||
let mut preprocesses = HashMap::new();
|
||||
for i in 1 ..= keys.len() {
|
||||
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
||||
let keys = keys.get(&i).unwrap().clone();
|
||||
|
||||
let mut db = MemDb::new();
|
||||
let mut txn = db.txn();
|
||||
let (signer, preprocess) =
|
||||
Cosigner::new(&mut txn, Session(0), vec![keys], block_number, block, actual_id.attempt)
|
||||
.unwrap();
|
||||
|
||||
match preprocess {
|
||||
// All participants should emit a preprocess
|
||||
ProcessorMessage::CosignPreprocess { id, preprocesses: mut these_preprocesses } => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(these_preprocesses.len(), 1);
|
||||
if signing_set.contains(&i) {
|
||||
preprocesses.insert(i, these_preprocesses.swap_remove(0));
|
||||
}
|
||||
}
|
||||
_ => panic!("didn't get preprocess back"),
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
signers.insert(i, signer);
|
||||
dbs.insert(i, db);
|
||||
}
|
||||
|
||||
let mut shares = HashMap::new();
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::SubstratePreprocesses {
|
||||
id: actual_id.clone(),
|
||||
preprocesses: clone_without(&preprocesses, i),
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::SubstrateShare { id, shares: mut these_shares } => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(these_shares.len(), 1);
|
||||
shares.insert(*i, these_shares.swap_remove(0));
|
||||
}
|
||||
_ => panic!("didn't get share back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::SubstrateShares {
|
||||
id: actual_id.clone(),
|
||||
shares: clone_without(&shares, i),
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::CosignedBlock { block_number, block: signed_block, signature } => {
|
||||
assert_eq!(signed_block, block);
|
||||
assert!(Public::from_raw(keys[&participant_one].group_key().to_bytes()).verify(
|
||||
&cosign_block_msg(block_number, block),
|
||||
&Signature(signature.try_into().unwrap())
|
||||
));
|
||||
}
|
||||
_ => panic!("didn't get cosigned block back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{
|
||||
group::{ff::Field, GroupEncoding},
|
||||
Ciphersuite, Ristretto,
|
||||
};
|
||||
use dkg::{Participant, ThresholdParams, evrf::*};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
use sp_application_crypto::sr25519;
|
||||
use serai_client::validator_sets::primitives::{Session, KeyPair};
|
||||
|
||||
use messages::key_gen::*;
|
||||
use crate::{
|
||||
networks::Network,
|
||||
key_gen::{KeyConfirmed, KeyGen},
|
||||
};
|
||||
|
||||
const SESSION: Session = Session(1);
|
||||
|
||||
pub fn test_key_gen<N: Network>() {
|
||||
let mut dbs = HashMap::new();
|
||||
let mut substrate_evrf_keys = HashMap::new();
|
||||
let mut network_evrf_keys = HashMap::new();
|
||||
let mut evrf_public_keys = vec![];
|
||||
let mut key_gens = HashMap::new();
|
||||
for i in 1 ..= 5 {
|
||||
let db = MemDb::new();
|
||||
dbs.insert(i, db.clone());
|
||||
|
||||
let substrate_evrf_key = Zeroizing::new(
|
||||
<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng),
|
||||
);
|
||||
substrate_evrf_keys.insert(i, substrate_evrf_key.clone());
|
||||
let network_evrf_key = Zeroizing::new(
|
||||
<<N::Curve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::F::random(&mut OsRng),
|
||||
);
|
||||
network_evrf_keys.insert(i, network_evrf_key.clone());
|
||||
|
||||
evrf_public_keys.push((
|
||||
(<<Ristretto as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator() * *substrate_evrf_key)
|
||||
.to_bytes(),
|
||||
(<<N::Curve as EvrfCurve>::EmbeddedCurve as Ciphersuite>::generator() * *network_evrf_key)
|
||||
.to_bytes()
|
||||
.as_ref()
|
||||
.to_vec(),
|
||||
));
|
||||
key_gens
|
||||
.insert(i, KeyGen::<N, MemDb>::new(db, substrate_evrf_key.clone(), network_evrf_key.clone()));
|
||||
}
|
||||
|
||||
let mut participations = HashMap::new();
|
||||
for i in 1 ..= 5 {
|
||||
let key_gen = key_gens.get_mut(&i).unwrap();
|
||||
let mut txn = dbs.get_mut(&i).unwrap().txn();
|
||||
let mut msgs = key_gen.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::GenerateKey {
|
||||
session: SESSION,
|
||||
threshold: 3,
|
||||
evrf_public_keys: evrf_public_keys.clone(),
|
||||
},
|
||||
);
|
||||
assert_eq!(msgs.len(), 1);
|
||||
let ProcessorMessage::Participation { session, participation } = msgs.swap_remove(0) else {
|
||||
panic!("didn't get a participation")
|
||||
};
|
||||
assert_eq!(session, SESSION);
|
||||
participations.insert(i, participation);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
let mut res = None;
|
||||
for i in 1 ..= 5 {
|
||||
let key_gen = key_gens.get_mut(&i).unwrap();
|
||||
let mut txn = dbs.get_mut(&i).unwrap().txn();
|
||||
for j in 1 ..= 5 {
|
||||
let mut msgs = key_gen.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::Participation {
|
||||
session: SESSION,
|
||||
participant: Participant::new(u16::try_from(j).unwrap()).unwrap(),
|
||||
participation: participations[&j].clone(),
|
||||
},
|
||||
);
|
||||
if j != 3 {
|
||||
assert!(msgs.is_empty());
|
||||
}
|
||||
if j == 3 {
|
||||
assert_eq!(msgs.len(), 1);
|
||||
let ProcessorMessage::GeneratedKeyPair { session, substrate_key, network_key } =
|
||||
msgs.swap_remove(0)
|
||||
else {
|
||||
panic!("didn't get a generated key pair")
|
||||
};
|
||||
assert_eq!(session, SESSION);
|
||||
|
||||
if res.is_none() {
|
||||
res = Some((substrate_key, network_key.clone()));
|
||||
}
|
||||
assert_eq!(res.as_ref().unwrap(), &(substrate_key, network_key));
|
||||
}
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
let res = res.unwrap();
|
||||
|
||||
for i in 1 ..= 5 {
|
||||
let key_gen = key_gens.get_mut(&i).unwrap();
|
||||
let mut txn = dbs.get_mut(&i).unwrap().txn();
|
||||
let KeyConfirmed { mut substrate_keys, mut network_keys } = key_gen.confirm(
|
||||
&mut txn,
|
||||
SESSION,
|
||||
&KeyPair(sr25519::Public(res.0), res.1.clone().try_into().unwrap()),
|
||||
);
|
||||
txn.commit();
|
||||
|
||||
assert_eq!(substrate_keys.len(), 1);
|
||||
let substrate_keys = substrate_keys.swap_remove(0);
|
||||
assert_eq!(network_keys.len(), 1);
|
||||
let network_keys = network_keys.swap_remove(0);
|
||||
|
||||
let params =
|
||||
ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap();
|
||||
assert_eq!(substrate_keys.params(), params);
|
||||
assert_eq!(network_keys.params(), params);
|
||||
assert_eq!(
|
||||
(
|
||||
substrate_keys.group_key().to_bytes(),
|
||||
network_keys.group_key().to_bytes().as_ref().to_vec()
|
||||
),
|
||||
res
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,443 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use dockertest::{
|
||||
PullPolicy, StartPolicy, LogOptions, LogAction, LogPolicy, LogSource, Image,
|
||||
TestBodySpecification, DockerOperations, DockerTest,
|
||||
};
|
||||
|
||||
use serai_db::MemDb;
|
||||
|
||||
#[cfg(feature = "bitcoin")]
|
||||
mod bitcoin {
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use frost::Participant;
|
||||
|
||||
use bitcoin_serai::bitcoin::{
|
||||
secp256k1::{SECP256K1, SecretKey, Message},
|
||||
PrivateKey, PublicKey,
|
||||
hashes::{HashEngine, Hash, sha256::Hash as Sha256},
|
||||
sighash::{SighashCache, EcdsaSighashType},
|
||||
absolute::LockTime,
|
||||
Amount as BAmount, Sequence, Script, Witness, OutPoint,
|
||||
address::Address as BAddress,
|
||||
transaction::{Version, Transaction, TxIn, TxOut},
|
||||
Network as BNetwork, ScriptBuf,
|
||||
opcodes::all::{OP_SHA256, OP_EQUALVERIFY},
|
||||
};
|
||||
|
||||
use scale::Encode;
|
||||
use sp_application_crypto::Pair;
|
||||
use serai_client::{in_instructions::primitives::Shorthand, primitives::insecure_pair_from_name};
|
||||
|
||||
use tokio::{
|
||||
time::{timeout, Duration},
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
networks::{Network, Bitcoin, Output, OutputType, Block},
|
||||
tests::scanner::new_scanner,
|
||||
multisigs::scanner::ScannerEvent,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_dust_constant() {
|
||||
struct IsTrue<const V: bool>;
|
||||
trait True {}
|
||||
impl True for IsTrue<true> {}
|
||||
fn check<T: True>() {
|
||||
core::hint::black_box(());
|
||||
}
|
||||
check::<IsTrue<{ Bitcoin::DUST >= bitcoin_serai::wallet::DUST }>>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_receive_data_from_input() {
|
||||
let docker = spawn_bitcoin();
|
||||
docker.run(|ops| async move {
|
||||
let btc = bitcoin(&ops).await(MemDb::new()).await;
|
||||
|
||||
// generate a multisig address to receive the coins
|
||||
let mut keys = frost::tests::key_gen::<_, <Bitcoin as Network>::Curve>(&mut OsRng)
|
||||
.remove(&Participant::new(1).unwrap())
|
||||
.unwrap();
|
||||
<Bitcoin as Network>::tweak_keys(&mut keys);
|
||||
let group_key = keys.group_key();
|
||||
let serai_btc_address = <Bitcoin as Network>::external_address(&btc, group_key).await;
|
||||
|
||||
// btc key pair to send from
|
||||
let private_key = PrivateKey::new(SecretKey::new(&mut rand_core::OsRng), BNetwork::Regtest);
|
||||
let public_key = PublicKey::from_private_key(SECP256K1, &private_key);
|
||||
let main_addr = BAddress::p2pkh(public_key, BNetwork::Regtest);
|
||||
|
||||
// get unlocked coins
|
||||
let new_block = btc.get_latest_block_number().await.unwrap() + 1;
|
||||
btc
|
||||
.rpc
|
||||
.rpc_call::<Vec<String>>("generatetoaddress", serde_json::json!([100, main_addr]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// create a scanner
|
||||
let db = MemDb::new();
|
||||
let mut scanner = new_scanner(&btc, &db, group_key, &Arc::new(Mutex::new(true))).await;
|
||||
|
||||
// make a transfer instruction & hash it for script.
|
||||
let serai_address = insecure_pair_from_name("alice").public();
|
||||
let message = Shorthand::transfer(None, serai_address.into()).encode();
|
||||
let mut data = Sha256::engine();
|
||||
data.input(&message);
|
||||
|
||||
// make the output script => msg_script(OP_SHA256 PUSH MSG_HASH OP_EQUALVERIFY) + any_script
|
||||
let mut script = ScriptBuf::builder()
|
||||
.push_opcode(OP_SHA256)
|
||||
.push_slice(Sha256::from_engine(data).as_byte_array())
|
||||
.push_opcode(OP_EQUALVERIFY)
|
||||
.into_script();
|
||||
// append a regular spend script
|
||||
for i in main_addr.script_pubkey().instructions() {
|
||||
script.push_instruction(i.unwrap());
|
||||
}
|
||||
|
||||
// Create the first transaction
|
||||
let tx = btc.get_block(new_block).await.unwrap().txdata.swap_remove(0);
|
||||
let mut tx = Transaction {
|
||||
version: Version(2),
|
||||
lock_time: LockTime::ZERO,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
|
||||
script_sig: Script::new().into(),
|
||||
sequence: Sequence(u32::MAX),
|
||||
witness: Witness::default(),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
value: tx.output[0].value - BAmount::from_sat(10000),
|
||||
script_pubkey: ScriptBuf::new_p2wsh(&script.wscript_hash()),
|
||||
}],
|
||||
};
|
||||
tx.input[0].script_sig = Bitcoin::sign_btc_input_for_p2pkh(&tx, 0, &private_key);
|
||||
let initial_output_value = tx.output[0].value;
|
||||
|
||||
// send it
|
||||
btc.rpc.send_raw_transaction(&tx).await.unwrap();
|
||||
|
||||
// Chain a transaction spending it with the InInstruction embedded in the input
|
||||
let mut tx = Transaction {
|
||||
version: Version(2),
|
||||
lock_time: LockTime::ZERO,
|
||||
input: vec![TxIn {
|
||||
previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
|
||||
script_sig: Script::new().into(),
|
||||
sequence: Sequence(u32::MAX),
|
||||
witness: Witness::new(),
|
||||
}],
|
||||
output: vec![TxOut {
|
||||
value: tx.output[0].value - BAmount::from_sat(10000),
|
||||
script_pubkey: serai_btc_address.into(),
|
||||
}],
|
||||
};
|
||||
|
||||
// add the witness script
|
||||
// This is the standard script with an extra argument of the InInstruction
|
||||
let mut sig = SECP256K1
|
||||
.sign_ecdsa_low_r(
|
||||
&Message::from_digest_slice(
|
||||
SighashCache::new(&tx)
|
||||
.p2wsh_signature_hash(0, &script, initial_output_value, EcdsaSighashType::All)
|
||||
.unwrap()
|
||||
.to_raw_hash()
|
||||
.as_ref(),
|
||||
)
|
||||
.unwrap(),
|
||||
&private_key.inner,
|
||||
)
|
||||
.serialize_der()
|
||||
.to_vec();
|
||||
sig.push(1);
|
||||
tx.input[0].witness.push(sig);
|
||||
tx.input[0].witness.push(public_key.inner.serialize());
|
||||
tx.input[0].witness.push(message.clone());
|
||||
tx.input[0].witness.push(script);
|
||||
|
||||
// Send it immediately, as Bitcoin allows mempool chaining
|
||||
btc.rpc.send_raw_transaction(&tx).await.unwrap();
|
||||
|
||||
// Mine enough confirmations
|
||||
let block_number = btc.get_latest_block_number().await.unwrap() + 1;
|
||||
for _ in 0 .. <Bitcoin as Network>::CONFIRMATIONS {
|
||||
btc.mine_block().await;
|
||||
}
|
||||
let tx_block = btc.get_block(block_number).await.unwrap();
|
||||
|
||||
// verify that scanner picked up the output
|
||||
let outputs =
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block, tx_block.id());
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].kind(), OutputType::External);
|
||||
outputs
|
||||
}
|
||||
_ => panic!("unexpectedly got eventuality completion"),
|
||||
};
|
||||
|
||||
// verify that the amount and message are correct
|
||||
assert_eq!(outputs[0].balance().amount.0, tx.output[0].value.to_sat());
|
||||
assert_eq!(outputs[0].data(), message);
|
||||
});
|
||||
}
|
||||
|
||||
fn spawn_bitcoin() -> DockerTest {
|
||||
serai_docker_tests::build("bitcoin".to_string());
|
||||
|
||||
let composition = TestBodySpecification::with_image(
|
||||
Image::with_repository("serai-dev-bitcoin").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().with_network(dockertest::Network::Isolated);
|
||||
test.provide_container(composition);
|
||||
test
|
||||
}
|
||||
|
||||
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 url = format!("http://serai:seraidex@{}:{}", handle.0, handle.1);
|
||||
let bitcoin = Bitcoin::new(url.clone()).await;
|
||||
bitcoin.fresh_chain().await;
|
||||
move |_db| Box::pin(Bitcoin::new(url.clone()))
|
||||
}
|
||||
|
||||
test_utxo_network!(
|
||||
Bitcoin,
|
||||
spawn_bitcoin,
|
||||
bitcoin,
|
||||
bitcoin_key_gen,
|
||||
bitcoin_scanner,
|
||||
bitcoin_no_deadlock_in_multisig_completed,
|
||||
bitcoin_signer,
|
||||
bitcoin_wallet,
|
||||
bitcoin_addresses,
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "monero")]
|
||||
mod monero {
|
||||
use super::*;
|
||||
use crate::networks::{Network, Monero};
|
||||
|
||||
fn spawn_monero() -> DockerTest {
|
||||
serai_docker_tests::build("monero".to_string());
|
||||
|
||||
let composition = TestBodySpecification::with_image(
|
||||
Image::with_repository("serai-dev-monero").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 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 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 {
|
||||
monero.mine_block().await;
|
||||
}
|
||||
move |_db| Box::pin(Monero::new(url.clone()))
|
||||
}
|
||||
|
||||
test_utxo_network!(
|
||||
Monero,
|
||||
spawn_monero,
|
||||
monero,
|
||||
monero_key_gen,
|
||||
monero_scanner,
|
||||
monero_no_deadlock_in_multisig_completed,
|
||||
monero_signer,
|
||||
monero_wallet,
|
||||
monero_addresses,
|
||||
);
|
||||
}
|
||||
|
||||
#[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::{
|
||||
primitives::U256,
|
||||
simple_request_transport::SimpleRequest,
|
||||
rpc_client::ClientBuilder,
|
||||
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;
|
||||
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(), String::new()).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
test_network!(
|
||||
Ethereum<MemDb>,
|
||||
spawn_ethereum,
|
||||
ethereum,
|
||||
ethereum_key_gen,
|
||||
ethereum_scanner,
|
||||
ethereum_no_deadlock_in_multisig_completed,
|
||||
ethereum_signer,
|
||||
ethereum_wallet,
|
||||
);
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
mod key_gen;
|
||||
|
||||
mod scanner;
|
||||
|
||||
mod signer;
|
||||
pub(crate) use signer::sign;
|
||||
|
||||
mod cosigner;
|
||||
mod batch_signer;
|
||||
|
||||
mod wallet;
|
||||
|
||||
mod addresses;
|
||||
|
||||
// Effective Once
|
||||
static INIT_LOGGER_CELL: OnceLock<()> = OnceLock::new();
|
||||
fn init_logger() {
|
||||
*INIT_LOGGER_CELL.get_or_init(env_logger::init)
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_network {
|
||||
(
|
||||
$N: ty,
|
||||
$docker: ident,
|
||||
$network: ident,
|
||||
$key_gen: ident,
|
||||
$scanner: ident,
|
||||
$no_deadlock_in_multisig_completed: ident,
|
||||
$signer: ident,
|
||||
$wallet: ident,
|
||||
) => {
|
||||
use core::{pin::Pin, future::Future};
|
||||
use $crate::tests::{
|
||||
init_logger,
|
||||
key_gen::test_key_gen,
|
||||
scanner::{test_scanner, test_no_deadlock_in_multisig_completed},
|
||||
signer::test_signer,
|
||||
wallet::test_wallet,
|
||||
};
|
||||
|
||||
// This doesn't interact with a node and accordingly doesn't need to be spawn one
|
||||
#[tokio::test]
|
||||
async fn $key_gen() {
|
||||
init_logger();
|
||||
test_key_gen::<$N>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn $scanner() {
|
||||
init_logger();
|
||||
let docker = $docker();
|
||||
docker.run(|ops| async move {
|
||||
let new_network = $network(&ops).await;
|
||||
test_scanner(new_network).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn $no_deadlock_in_multisig_completed() {
|
||||
init_logger();
|
||||
let docker = $docker();
|
||||
docker.run(|ops| async move {
|
||||
let new_network = $network(&ops).await;
|
||||
test_no_deadlock_in_multisig_completed(new_network).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[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;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[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::addresses::test_addresses;
|
||||
|
||||
test_network!(
|
||||
$N,
|
||||
$docker,
|
||||
$network,
|
||||
$key_gen,
|
||||
$scanner,
|
||||
$no_deadlock_in_multisig_completed,
|
||||
$signer,
|
||||
$wallet,
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn $addresses() {
|
||||
init_logger();
|
||||
let docker = $docker();
|
||||
docker.run(|ops| async move {
|
||||
let new_network = $network(&ops).await;
|
||||
test_addresses(new_network).await;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mod literal;
|
||||
@@ -1,203 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use core::{pin::Pin, time::Duration, future::Future};
|
||||
use std::sync::Arc;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
||||
use frost::{Participant, tests::key_gen};
|
||||
|
||||
use tokio::{sync::Mutex, time::timeout};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
use serai_client::validator_sets::primitives::Session;
|
||||
|
||||
use crate::{
|
||||
networks::{OutputType, Output, Block, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
multisigs::scanner::{ScannerEvent, Scanner, ScannerHandle},
|
||||
};
|
||||
|
||||
pub async fn new_scanner<N: Network, D: Db>(
|
||||
network: &N,
|
||||
db: &D,
|
||||
group_key: <N::Curve as Ciphersuite>::G,
|
||||
first: &Arc<Mutex<bool>>,
|
||||
) -> ScannerHandle<N, D> {
|
||||
let activation_number = network.get_latest_block_number().await.unwrap();
|
||||
let mut db = db.clone();
|
||||
let (mut scanner, current_keys) = Scanner::new(network.clone(), db.clone());
|
||||
let mut first = first.lock().await;
|
||||
if *first {
|
||||
assert!(current_keys.is_empty());
|
||||
let mut txn = db.txn();
|
||||
scanner.register_key(&mut txn, activation_number, group_key).await;
|
||||
txn.commit();
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
*first = false;
|
||||
} else {
|
||||
assert_eq!(current_keys.len(), 1);
|
||||
}
|
||||
scanner
|
||||
}
|
||||
|
||||
pub async fn test_scanner<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
let mut keys =
|
||||
frost::tests::key_gen::<_, N::Curve>(&mut OsRng).remove(&Participant::new(1).unwrap()).unwrap();
|
||||
N::tweak_keys(&mut keys);
|
||||
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
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
let first = Arc::new(Mutex::new(true));
|
||||
let scanner = new_scanner(&network, &db, group_key, &first).await;
|
||||
|
||||
// Receive funds
|
||||
let block = network.test_send(N::external_address(&network, keys.group_key()).await).await;
|
||||
let block_id = block.id();
|
||||
|
||||
// Verify the Scanner picked them up
|
||||
let verify_event = |mut scanner: ScannerHandle<N, MemDb>| async move {
|
||||
let outputs =
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block, block_id);
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert_eq!(outputs[0].kind(), OutputType::External);
|
||||
outputs
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
};
|
||||
(scanner, outputs)
|
||||
};
|
||||
let (mut scanner, outputs) = verify_event(scanner).await;
|
||||
|
||||
// Create a new scanner off the current DB and verify it re-emits the above events
|
||||
verify_event(new_scanner(&network, &db, group_key, &first).await).await;
|
||||
|
||||
// Acknowledge the block
|
||||
let mut cloned_db = db.clone();
|
||||
let mut txn = cloned_db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block_id).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
|
||||
// There should be no more events
|
||||
assert!(timeout(Duration::from_secs(30), scanner.events.recv()).await.is_err());
|
||||
|
||||
// Create a new scanner off the current DB and make sure it also does nothing
|
||||
assert!(timeout(
|
||||
Duration::from_secs(30),
|
||||
new_scanner(&network, &db, group_key, &first).await.events.recv()
|
||||
)
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
|
||||
pub async fn test_no_deadlock_in_multisig_completed<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
// This test scans two blocks then acknowledges one, yet a network with one confirm won't scan
|
||||
// two blocks before the first is acknowledged (due to the look-ahead limit)
|
||||
if N::CONFIRMATIONS <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
assert!(current_keys.is_empty());
|
||||
|
||||
// Register keys to cause Block events at CONFIRMATIONS (dropped since first keys),
|
||||
// CONFIRMATIONS + 1, and CONFIRMATIONS + 2
|
||||
for i in 0 .. 3 {
|
||||
let key = {
|
||||
let mut keys = key_gen(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
N::tweak_keys(keys);
|
||||
}
|
||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||
if i == 0 {
|
||||
let mut txn = db.txn();
|
||||
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||
txn.commit();
|
||||
|
||||
// Sleep for 5 seconds as setting the Network key value will trigger an async task for
|
||||
// Ethereum
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
key
|
||||
};
|
||||
|
||||
let mut txn = db.txn();
|
||||
scanner
|
||||
.register_key(
|
||||
&mut txn,
|
||||
network.get_latest_block_number().await.unwrap() + N::CONFIRMATIONS + i,
|
||||
key,
|
||||
)
|
||||
.await;
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
for _ in 0 .. (3 * N::CONFIRMATIONS) {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
// Block for the second set of keys registered
|
||||
let block_id =
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs: _ } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
block
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
};
|
||||
|
||||
// Block for the third set of keys registered
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { .. } => {}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
};
|
||||
|
||||
// The ack_block acquisition shows the Scanner isn't maintaining the lock on its own thread after
|
||||
// emitting the Block event
|
||||
// TODO: This is incomplete. Also test after emitting Completed
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block_id).await.1, vec![]);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use core::{pin::Pin, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
use frost::{
|
||||
Participant, ThresholdKeys,
|
||||
dkg::tests::{key_gen, clone_without},
|
||||
};
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
use serai_client::{
|
||||
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
|
||||
validator_sets::primitives::Session,
|
||||
};
|
||||
|
||||
use messages::sign::*;
|
||||
use crate::{
|
||||
Payment,
|
||||
networks::{Output, Transaction, Eventuality, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
multisigs::scheduler::Scheduler,
|
||||
signer::Signer,
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub async fn sign<N: Network>(
|
||||
network: N,
|
||||
session: Session,
|
||||
mut keys_txs: HashMap<
|
||||
Participant,
|
||||
(ThresholdKeys<N::Curve>, (N::SignableTransaction, N::Eventuality)),
|
||||
>,
|
||||
) -> <N::Eventuality as Eventuality>::Claim {
|
||||
let actual_id = SignId { session, id: [0xaa; 32], attempt: 0 };
|
||||
|
||||
let mut keys = HashMap::new();
|
||||
let mut txs = HashMap::new();
|
||||
for (i, (these_keys, this_tx)) in keys_txs.drain() {
|
||||
keys.insert(i, these_keys);
|
||||
txs.insert(i, this_tx);
|
||||
}
|
||||
|
||||
let mut signers = HashMap::new();
|
||||
let mut dbs = HashMap::new();
|
||||
let mut t = 0;
|
||||
for i in 1 ..= keys.len() {
|
||||
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
||||
let keys = keys.remove(&i).unwrap();
|
||||
t = keys.params().t();
|
||||
signers.insert(i, Signer::<_, MemDb>::new(network.clone(), Session(0), vec![keys]));
|
||||
dbs.insert(i, MemDb::new());
|
||||
}
|
||||
drop(keys);
|
||||
|
||||
let mut signing_set = vec![];
|
||||
while signing_set.len() < usize::from(t) {
|
||||
let candidate = Participant::new(
|
||||
u16::try_from((OsRng.next_u64() % u64::try_from(signers.len()).unwrap()) + 1).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
if signing_set.contains(&candidate) {
|
||||
continue;
|
||||
}
|
||||
signing_set.push(candidate);
|
||||
}
|
||||
|
||||
let mut preprocesses = HashMap::new();
|
||||
|
||||
let mut eventuality = None;
|
||||
for i in 1 ..= signers.len() {
|
||||
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
||||
let (tx, this_eventuality) = txs.remove(&i).unwrap();
|
||||
let mut txn = dbs.get_mut(&i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(&i)
|
||||
.unwrap()
|
||||
.sign_transaction(&mut txn, actual_id.id, tx, &this_eventuality)
|
||||
.await
|
||||
{
|
||||
// All participants should emit a preprocess
|
||||
Some(ProcessorMessage::Preprocess { id, preprocesses: mut these_preprocesses }) => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(these_preprocesses.len(), 1);
|
||||
if signing_set.contains(&i) {
|
||||
preprocesses.insert(i, these_preprocesses.swap_remove(0));
|
||||
}
|
||||
}
|
||||
_ => panic!("didn't get preprocess back"),
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
if eventuality.is_none() {
|
||||
eventuality = Some(this_eventuality.clone());
|
||||
}
|
||||
assert_eq!(eventuality, Some(this_eventuality));
|
||||
}
|
||||
|
||||
let mut shares = HashMap::new();
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::Preprocesses {
|
||||
id: actual_id.clone(),
|
||||
preprocesses: clone_without(&preprocesses, i),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::Share { id, shares: mut these_shares } => {
|
||||
assert_eq!(id, actual_id);
|
||||
assert_eq!(these_shares.len(), 1);
|
||||
shares.insert(*i, these_shares.swap_remove(0));
|
||||
}
|
||||
_ => panic!("didn't get share back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
let mut tx_id = None;
|
||||
for i in &signing_set {
|
||||
let mut txn = dbs.get_mut(i).unwrap().txn();
|
||||
match signers
|
||||
.get_mut(i)
|
||||
.unwrap()
|
||||
.handle(
|
||||
&mut txn,
|
||||
CoordinatorMessage::Shares { id: actual_id.clone(), shares: clone_without(&shares, i) },
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
ProcessorMessage::Completed { session, id, tx } => {
|
||||
assert_eq!(session, Session(0));
|
||||
assert_eq!(id, actual_id.id);
|
||||
if tx_id.is_none() {
|
||||
tx_id = Some(tx.clone());
|
||||
}
|
||||
assert_eq!(tx_id, Some(tx));
|
||||
}
|
||||
_ => panic!("didn't get TX back"),
|
||||
}
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
let mut typed_claim = <N::Eventuality as Eventuality>::Claim::default();
|
||||
typed_claim.as_mut().copy_from_slice(tx_id.unwrap().as_ref());
|
||||
assert!(network.check_eventuality_by_claim(&eventuality.unwrap(), &typed_claim).await);
|
||||
typed_claim
|
||||
}
|
||||
|
||||
pub async fn test_signer<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
let mut keys = key_gen(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
N::tweak_keys(keys);
|
||||
}
|
||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||
|
||||
let mut db = MemDb::new();
|
||||
{
|
||||
let mut txn = db.txn();
|
||||
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||
txn.commit();
|
||||
}
|
||||
let network = new_network(db.clone()).await;
|
||||
|
||||
let outputs = network
|
||||
.get_outputs(&network.test_send(N::external_address(&network, key).await).await, key)
|
||||
.await;
|
||||
let sync_block = network.get_latest_block_number().await.unwrap() - N::CONFIRMATIONS;
|
||||
|
||||
let amount = (2 * N::DUST) + 1000;
|
||||
let plan = {
|
||||
let mut txn = db.txn();
|
||||
let mut scheduler = N::Scheduler::new::<MemDb>(&mut txn, key, N::NETWORK);
|
||||
let payments = vec![Payment {
|
||||
address: N::external_address(&network, key).await,
|
||||
balance: ExternalBalance {
|
||||
coin: match N::NETWORK {
|
||||
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||
},
|
||||
amount: Amount(amount),
|
||||
},
|
||||
}];
|
||||
let mut plans = scheduler.schedule::<MemDb>(&mut txn, outputs.clone(), payments, key, false);
|
||||
assert_eq!(plans.len(), 1);
|
||||
plans.swap_remove(0)
|
||||
};
|
||||
|
||||
let mut keys_txs = HashMap::new();
|
||||
let mut eventualities = vec![];
|
||||
for (i, keys) in keys.drain() {
|
||||
let (signable, eventuality) =
|
||||
network.prepare_send(sync_block, plan.clone(), 0).await.unwrap().tx.unwrap();
|
||||
|
||||
eventualities.push(eventuality.clone());
|
||||
keys_txs.insert(i, (keys, (signable, eventuality)));
|
||||
}
|
||||
|
||||
let claim = sign(network.clone(), Session(0), keys_txs).await;
|
||||
|
||||
// Mine a block, and scan it, to ensure that the TX actually made it on chain
|
||||
network.mine_block().await;
|
||||
let block_number = network.get_latest_block_number().await.unwrap();
|
||||
let tx = network.get_transaction_by_eventuality(block_number, &eventualities[0]).await;
|
||||
let outputs = network
|
||||
.get_outputs(
|
||||
&network.get_block(network.get_latest_block_number().await.unwrap()).await.unwrap(),
|
||||
key,
|
||||
)
|
||||
.await;
|
||||
// Don't run if Ethereum as the received output will revert by the contract
|
||||
// (and therefore not actually exist)
|
||||
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
|
||||
// Adjust the amount for the fees
|
||||
let amount = amount - tx.fee(&network).await;
|
||||
if plan.change.is_some() {
|
||||
// Check either output since Monero will randomize its output order
|
||||
assert!(
|
||||
(outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount)
|
||||
);
|
||||
} else {
|
||||
assert!(outputs[0].balance().amount.0 == amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the eventualities pass
|
||||
for eventuality in eventualities {
|
||||
let completion = network.confirm_completion(&eventuality, &claim).await.unwrap().unwrap();
|
||||
assert_eq!(N::Eventuality::claim(&completion), claim);
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
// TODO
|
||||
|
||||
use core::{time::Duration, pin::Pin, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use ciphersuite::group::GroupEncoding;
|
||||
use frost::{Participant, dkg::tests::key_gen};
|
||||
|
||||
use tokio::time::timeout;
|
||||
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
use serai_client::{
|
||||
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
|
||||
validator_sets::primitives::Session,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Payment, Plan,
|
||||
networks::{Output, Transaction, Eventuality, Block, Network},
|
||||
key_gen::NetworkKeyDb,
|
||||
multisigs::{
|
||||
scanner::{ScannerEvent, Scanner},
|
||||
scheduler::{self, Scheduler},
|
||||
},
|
||||
tests::sign,
|
||||
};
|
||||
|
||||
// Tests the Scanner, Scheduler, and Signer together
|
||||
pub async fn test_wallet<N: Network>(
|
||||
new_network: impl Fn(MemDb) -> Pin<Box<dyn Send + Future<Output = N>>>,
|
||||
) {
|
||||
let mut keys = key_gen(&mut OsRng);
|
||||
for keys in keys.values_mut() {
|
||||
N::tweak_keys(keys);
|
||||
}
|
||||
let key = keys[&Participant::new(1).unwrap()].group_key();
|
||||
|
||||
let mut db = MemDb::new();
|
||||
{
|
||||
let mut txn = db.txn();
|
||||
NetworkKeyDb::set(&mut txn, Session(0), &key.to_bytes().as_ref().to_vec());
|
||||
txn.commit();
|
||||
}
|
||||
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());
|
||||
assert!(current_keys.is_empty());
|
||||
let (block_id, outputs) = {
|
||||
let mut txn = db.txn();
|
||||
scanner.register_key(&mut txn, network.get_latest_block_number().await.unwrap(), key).await;
|
||||
txn.commit();
|
||||
for _ in 0 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
let block = network.test_send(N::external_address(&network, key).await).await;
|
||||
let block_id = block.id();
|
||||
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block, outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block, block_id);
|
||||
assert_eq!(outputs.len(), 1);
|
||||
(block_id, outputs)
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block_id.clone()).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
|
||||
let mut txn = db.txn();
|
||||
let mut scheduler = N::Scheduler::new::<MemDb>(&mut txn, key, N::NETWORK);
|
||||
let amount = 2 * N::DUST;
|
||||
let plans = scheduler.schedule::<MemDb>(
|
||||
&mut txn,
|
||||
outputs.clone(),
|
||||
vec![Payment {
|
||||
address: N::external_address(&network, key).await,
|
||||
balance: ExternalBalance {
|
||||
coin: match N::NETWORK {
|
||||
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||
},
|
||||
amount: Amount(amount),
|
||||
},
|
||||
}],
|
||||
key,
|
||||
false,
|
||||
);
|
||||
txn.commit();
|
||||
assert_eq!(plans.len(), 1);
|
||||
assert_eq!(plans[0].key, key);
|
||||
if std::any::TypeId::of::<N::Scheduler>() ==
|
||||
std::any::TypeId::of::<scheduler::smart_contract::Scheduler<N>>()
|
||||
{
|
||||
assert_eq!(plans[0].inputs, vec![]);
|
||||
} else {
|
||||
assert_eq!(plans[0].inputs, outputs);
|
||||
}
|
||||
assert_eq!(
|
||||
plans[0].payments,
|
||||
vec![Payment {
|
||||
address: N::external_address(&network, key).await,
|
||||
balance: ExternalBalance {
|
||||
coin: match N::NETWORK {
|
||||
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||
},
|
||||
amount: Amount(amount),
|
||||
}
|
||||
}]
|
||||
);
|
||||
assert_eq!(plans[0].change, N::change_address(key));
|
||||
|
||||
{
|
||||
let mut buf = vec![];
|
||||
plans[0].write(&mut buf).unwrap();
|
||||
assert_eq!(plans[0], Plan::<N>::read::<&[u8]>(&mut buf.as_ref()).unwrap());
|
||||
}
|
||||
|
||||
// Execute the plan
|
||||
let mut keys_txs = HashMap::new();
|
||||
let mut eventualities = vec![];
|
||||
for (i, keys) in keys.drain() {
|
||||
let (signable, eventuality) = network
|
||||
.prepare_send(network.get_block_number(&block_id).await, plans[0].clone(), 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.tx
|
||||
.unwrap();
|
||||
|
||||
eventualities.push(eventuality.clone());
|
||||
keys_txs.insert(i, (keys, (signable, eventuality)));
|
||||
}
|
||||
|
||||
let claim = sign(network.clone(), Session(0), keys_txs).await;
|
||||
network.mine_block().await;
|
||||
let block_number = network.get_latest_block_number().await.unwrap();
|
||||
let tx = network.get_transaction_by_eventuality(block_number, &eventualities[0]).await;
|
||||
let block = network.get_block(block_number).await.unwrap();
|
||||
let outputs = network.get_outputs(&block, key).await;
|
||||
|
||||
// Don't run if Ethereum as the received output will revert by the contract
|
||||
// (and therefore not actually exist)
|
||||
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plans[0].change.is_some())));
|
||||
// Adjust the amount for the fees
|
||||
let amount = amount - tx.fee(&network).await;
|
||||
if plans[0].change.is_some() {
|
||||
// Check either output since Monero will randomize its output order
|
||||
assert!(
|
||||
(outputs[0].balance().amount.0 == amount) || (outputs[1].balance().amount.0 == amount)
|
||||
);
|
||||
} else {
|
||||
assert!(outputs[0].balance().amount.0 == amount);
|
||||
}
|
||||
}
|
||||
|
||||
for eventuality in eventualities {
|
||||
let completion = network.confirm_completion(&eventuality, &claim).await.unwrap().unwrap();
|
||||
assert_eq!(N::Eventuality::claim(&completion), claim);
|
||||
}
|
||||
|
||||
for _ in 1 .. N::CONFIRMATIONS {
|
||||
network.mine_block().await;
|
||||
}
|
||||
|
||||
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
||||
scanner.multisig_completed.send(false).unwrap();
|
||||
assert!(!is_retirement_block);
|
||||
assert_eq!(block_id, block.id());
|
||||
assert_eq!(these_outputs, outputs);
|
||||
}
|
||||
ScannerEvent::Completed(_, _, _, _, _) => {
|
||||
panic!("unexpectedly got eventuality completion");
|
||||
}
|
||||
}
|
||||
|
||||
// Check the Scanner DB can reload the outputs
|
||||
let mut txn = db.txn();
|
||||
assert_eq!(scanner.ack_block(&mut txn, block.id()).await.1, outputs);
|
||||
scanner.release_lock().await;
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] }
|
||||
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] }
|
||||
|
||||
serai-client = { path = "../../substrate/client", default-features = false }
|
||||
serai-cosign = { path = "../../coordinator/cosign" }
|
||||
|
||||
@@ -24,8 +24,8 @@ hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "secp256k1"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||
|
||||
secp256k1 = { version = "0.29", default-features = false, features = ["std", "global-context", "rand-std"] }
|
||||
|
||||
@@ -25,8 +25,8 @@ hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] }
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "secp256k1"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["secp256k1"] }
|
||||
|
||||
k256 = { version = "^0.13.1", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -23,7 +23,7 @@ workspace = true
|
||||
[dependencies]
|
||||
rand_core = { version = "0.6", default-features = false, features = ["std", "getrandom"] }
|
||||
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "^0.8.1", default-features = false }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
|
||||
@@ -31,9 +31,10 @@ rand_chacha = { version = "0.3", default-features = false, features = ["std"] }
|
||||
# Cryptography
|
||||
blake2 = { version = "0.10", default-features = false, features = ["std"] }
|
||||
transcript = { package = "flexible-transcript", path = "../../crypto/transcript", default-features = false, features = ["std"] }
|
||||
ec-divisors = { package = "ec-divisors", path = "../../crypto/evrf/divisors", default-features = false }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "ristretto"] }
|
||||
dkg = { package = "dkg", path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] }
|
||||
ec-divisors = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, features = ["std"] }
|
||||
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ristretto"] }
|
||||
|
||||
# Substrate
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -26,12 +26,12 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", default-features = false, features = ["std"] }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "ed25519"] }
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ed25519"] }
|
||||
ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std"] }
|
||||
dkg = { package = "dkg-evrf", path = "../../crypto/dkg/evrf", default-features = false, features = ["std", "ed25519"] }
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false }
|
||||
|
||||
monero-wallet = { path = "../../networks/monero/wallet", default-features = false, features = ["std", "multisig"] }
|
||||
monero-simple-request-rpc = { path = "../../networks/monero/rpc/simple-request", default-features = false }
|
||||
monero-wallet = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false, features = ["std", "multisig"] }
|
||||
monero-simple-request-rpc = { git = "https://github.com/kayabaNerve/monero-oxide", rev = "b6dd1a9ff7ac6b96eb7cb488a4501fd1f6f2dd1e", default-features = false }
|
||||
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["monero"] }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user