14 Commits

Author SHA1 Message Date
Luke Parker
cca9e8cf16 fmt 2024-06-10 13:46:57 -04:00
Luke Parker
97ef70fbd7 Correct ThresholdParams assert_eq 2024-06-10 13:44:44 -04:00
Luke Parker
92275988dd Add note on origin of publish_tx function in tests/coordinator 2024-06-10 13:36:32 -04:00
Luke Parker
c8f690e2f8 Minor nits 2024-06-10 08:45:38 -04:00
Luke Parker
70add5b270 Remove EPOCH_INTERVAL 2024-06-10 08:40:31 -04:00
Luke Parker
6a7d803fe7 Merge branch 'develop' into HEAD 2024-06-06 02:46:18 -04:00
akildemir
4dfaf31c58 bug fixes 2024-02-27 17:32:50 +03:00
akildemir
360cd023a0 add coordinator side rotation test 2024-02-26 15:16:44 +03:00
akildemir
5f2e15604c Merge branch 'develop' of https://github.com/serai-dex/serai into vs-rotation-tests 2024-02-25 14:13:28 +03:00
akildemir
2292d2d2af fix pr comments 2024-02-19 18:32:55 +03:00
akildemir
c04afa032f set up the fast-epoch docker file 2024-02-12 16:05:17 +03:00
akildemir
d46e24de8f update to develop latest 2024-02-12 16:02:09 +03:00
akildemir
e93dbedd6a complete rotation test for all networks 2024-02-12 14:36:56 +03:00
akildemir
24ff866684 add node side unit test 2024-02-09 11:22:56 +03:00
9 changed files with 428 additions and 211 deletions

View File

@@ -196,10 +196,10 @@ impl Serai {
}
async fn active_network_validators(&self, network: NetworkId) -> Result<Vec<Public>, SeraiError> {
let hash: String = self
let validators: String = self
.call("state_call", ["SeraiRuntimeApi_validators".to_string(), hex::encode(network.encode())])
.await?;
let bytes = Self::hex_decode(hash)?;
let bytes = Self::hex_decode(validators)?;
let r = Vec::<Public>::decode(&mut bytes.as_slice())
.map_err(|e| SeraiError::ErrorInResponse(e.to_string()))?;
Ok(r)

View File

@@ -14,8 +14,6 @@ use serai_client::{
mod common;
use common::validator_sets::{set_keys, allocate_stake, deallocate_stake};
const EPOCH_INTERVAL: u64 = 5;
serai_test!(
set_keys_test: (|serai: Serai| async move {
let network = NetworkId::Bitcoin;
@@ -223,20 +221,38 @@ async fn validator_set_rotation() {
.await;
}
async fn epoch_for_block(serai: &Serai, block: [u8; 32]) -> u64 {
let epoch: String = serai
.call("state_call", ["BabeApi_current_epoch".to_string(), String::new(), hex::encode(block)])
.await
.unwrap();
<u64 as scale::Decode>::decode(
&mut hex::decode(epoch.strip_prefix("0x").unwrap()).unwrap().as_slice(),
)
.unwrap()
}
async fn verify_session_and_active_validators(
serai: &Serai,
network: NetworkId,
session: u64,
participants: &[Public],
) {
// wait untill the epoch block finalized
let epoch_block = (session * EPOCH_INTERVAL) + 1;
while serai.finalized_block_by_number(epoch_block).await.unwrap().is_none() {
// sleep 1 block
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
}
let serai_for_block =
serai.as_of(serai.finalized_block_by_number(epoch_block).await.unwrap().unwrap().hash());
// wait untill the epoch block finalizes
let block = loop {
let mut block = serai.latest_finalized_block_hash().await.unwrap();
if epoch_for_block(serai, block).await < session {
// Sleep a block
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
continue;
}
while epoch_for_block(serai, block).await > session {
block = serai.block(block).await.unwrap().unwrap().header.parent_hash.0;
}
assert_eq!(epoch_for_block(serai, block).await, session);
break block;
};
let serai_for_block = serai.as_of(block);
// verify session
let s = serai_for_block.validator_sets().session(network).await.unwrap().unwrap();
@@ -249,9 +265,10 @@ async fn verify_session_and_active_validators(
assert_eq!(validators, participants);
// make sure finalization continues as usual after the changes
let current_finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
tokio::time::timeout(tokio::time::Duration::from_secs(60), async move {
let mut finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
while finalized_block <= epoch_block + 2 {
while finalized_block <= current_finalized_block + 2 {
tokio::time::sleep(tokio::time::Duration::from_secs(6)).await;
finalized_block = serai.latest_finalized_block().await.unwrap().header.number;
}
@@ -263,8 +280,7 @@ async fn verify_session_and_active_validators(
}
async fn get_active_session(serai: &Serai, network: NetworkId, hash: [u8; 32]) -> u64 {
let block_number = serai.block(hash).await.unwrap().unwrap().header.number;
let epoch = block_number / EPOCH_INTERVAL;
let epoch = epoch_for_block(serai, hash).await;
// changes should be active in the next session
if network == NetworkId::Serai {

View File

@@ -60,12 +60,18 @@ pub fn coordinator_instance(
)
}
pub fn serai_composition(name: &str) -> TestBodySpecification {
serai_docker_tests::build("serai".to_string());
TestBodySpecification::with_image(
Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never),
)
pub fn serai_composition(name: &str, fast_epoch: bool) -> TestBodySpecification {
(if fast_epoch {
serai_docker_tests::build("serai-fast-epoch".to_string());
TestBodySpecification::with_image(
Image::with_repository("serai-dev-serai-fast-epoch").pull_policy(PullPolicy::Never),
)
} else {
serai_docker_tests::build("serai".to_string());
TestBodySpecification::with_image(
Image::with_repository("serai-dev-serai").pull_policy(PullPolicy::Never),
)
})
.replace_env(
[("SERAI_NAME".to_string(), name.to_lowercase()), ("KEY".to_string(), " ".to_string())].into(),
)

View File

@@ -260,21 +260,29 @@ pub async fn batch(
#[tokio::test]
async fn batch_test() {
new_test(|mut processors: Vec<Processor>| async move {
let (processor_is, substrate_key, _) = key_gen::<Secp256k1>(&mut processors).await;
batch(
&mut processors,
&processor_is,
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
id: 0,
block: BlockHash([0x22; 32]),
instructions: vec![],
},
)
.await;
})
new_test(
|mut processors: Vec<Processor>| async move {
// pop the last participant since genesis keygen has only 4 participants
processors.pop().unwrap();
assert_eq!(processors.len(), COORDINATORS);
let (processor_is, substrate_key, _) =
key_gen::<Secp256k1>(&mut processors, Session(0)).await;
batch(
&mut processors,
&processor_is,
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
id: 0,
block: BlockHash([0x22; 32]),
instructions: vec![],
},
)
.await;
},
false,
)
.await;
}

View File

@@ -23,38 +23,39 @@ use crate::tests::*;
pub async fn key_gen<C: Ciphersuite>(
processors: &mut [Processor],
session: Session,
) -> (Vec<u8>, Zeroizing<<Ristretto as Ciphersuite>::F>, Zeroizing<C::F>) {
let coordinators = processors.len();
let mut participant_is = vec![];
let set = ValidatorSet { session: Session(0), network: NetworkId::Bitcoin };
let set = ValidatorSet { session, network: NetworkId::Bitcoin };
let id = KeyGenId { session: set.session, attempt: 0 };
for (i, processor) in processors.iter_mut().enumerate() {
let msg = processor.recv_message().await;
match &msg {
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
params,
..
}) => {
participant_is.push(params.i());
loop {
let msg = processor.recv_message().await;
match &msg {
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
id: this_id,
params,
shares,
}) => {
assert_eq!(id, *this_id);
assert_eq!(params.t(), u16::try_from(((coordinators * 2) / 3) + 1).unwrap(),);
assert_eq!(params.n(), u16::try_from(coordinators).unwrap(),);
assert_eq!(*shares, 1);
participant_is.push(params.i());
break;
}
CoordinatorMessage::Substrate(
messages::substrate::CoordinatorMessage::ConfirmKeyPair { .. },
) => {
continue;
}
_ => panic!("unexpected message: {msg:?}"),
}
_ => panic!("unexpected message: {msg:?}"),
}
assert_eq!(
msg,
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
id,
params: ThresholdParams::new(
u16::try_from(((COORDINATORS * 2) / 3) + 1).unwrap(),
u16::try_from(COORDINATORS).unwrap(),
participant_is[i],
)
.unwrap(),
shares: 1,
})
);
processor
.send_message(messages::key_gen::ProcessorMessage::Commitments {
id,
@@ -65,7 +66,7 @@ pub async fn key_gen<C: Ciphersuite>(
wait_for_tributary().await;
for (i, processor) in processors.iter_mut().enumerate() {
let mut commitments = (0 .. u8::try_from(COORDINATORS).unwrap())
let mut commitments = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
@@ -83,7 +84,7 @@ pub async fn key_gen<C: Ciphersuite>(
);
// Recipient it's for -> (Sender i, Recipient i)
let mut shares = (0 .. u8::try_from(COORDINATORS).unwrap())
let mut shares = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
@@ -118,7 +119,7 @@ pub async fn key_gen<C: Ciphersuite>(
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::Shares {
id,
shares: {
let mut shares = (0 .. u8::try_from(COORDINATORS).unwrap())
let mut shares = (0 .. u8::try_from(coordinators).unwrap())
.map(|l| {
(
participant_is[usize::from(l)],
@@ -182,14 +183,14 @@ pub async fn key_gen<C: Ciphersuite>(
.unwrap()
.as_secs()
.abs_diff(context.serai_time) <
70
(60 * 60 * 3) // 3 hours, which should exceed the length of any test we run
);
assert_eq!(context.network_latest_finalized_block.0, [0; 32]);
assert_eq!(set.session, session);
assert_eq!(key_pair.0 .0, substrate_key);
assert_eq!(&key_pair.1, &network_key);
}
_ => panic!("coordinator didn't respond with ConfirmKeyPair"),
_ => panic!("coordinator didn't respond with ConfirmKeyPair. msg: {msg:?}"),
}
message = Some(msg);
} else {
@@ -220,8 +221,15 @@ pub async fn key_gen<C: Ciphersuite>(
#[tokio::test]
async fn key_gen_test() {
new_test(|mut processors: Vec<Processor>| async move {
key_gen::<Secp256k1>(&mut processors).await;
})
new_test(
|mut processors: Vec<Processor>| async move {
// pop the last participant since genesis keygen has only 4 participants
processors.pop().unwrap();
assert_eq!(processors.len(), COORDINATORS);
key_gen::<Secp256k1>(&mut processors, Session(0)).await;
},
false,
)
.await;
}

View File

@@ -22,6 +22,8 @@ mod sign;
#[allow(unused_imports)]
pub use sign::sign;
mod rotation;
pub(crate) const COORDINATORS: usize = 4;
pub(crate) const THRESHOLD: usize = ((COORDINATORS * 2) / 3) + 1;
@@ -39,13 +41,15 @@ impl<F: Send + Future, TB: 'static + Send + Sync + Fn(Vec<Processor>) -> F> Test
}
}
pub(crate) async fn new_test(test_body: impl TestBody) {
pub(crate) async fn new_test(test_body: impl TestBody, fast_epoch: bool) {
let mut unique_id_lock = UNIQUE_ID.get_or_init(|| Mutex::new(0)).lock().await;
let mut coordinators = vec![];
let mut test = DockerTest::new().with_network(dockertest::Network::Isolated);
let mut coordinator_compositions = vec![];
for i in 0 .. COORDINATORS {
// Spawn one extra coordinator which isn't in-set
#[allow(clippy::range_plus_one)]
for i in 0 .. (COORDINATORS + 1) {
let name = match i {
0 => "Alice",
1 => "Bob",
@@ -55,7 +59,7 @@ pub(crate) async fn new_test(test_body: impl TestBody) {
5 => "Ferdie",
_ => panic!("needed a 7th name for a serai node"),
};
let serai_composition = serai_composition(name);
let serai_composition = serai_composition(name, fast_epoch);
let (processor_key, message_queue_keys, message_queue_composition) =
serai_message_queue_tests::instance();

View File

@@ -0,0 +1,164 @@
use tokio::time::{sleep, Duration};
use ciphersuite::Secp256k1;
use serai_client::{
primitives::{insecure_pair_from_name, NetworkId},
validator_sets::{
self,
primitives::{Session, ValidatorSet},
ValidatorSetsEvent,
},
Amount, Pair, Transaction,
};
use crate::{*, tests::*};
// TODO: This is duplicated with serai-client's tests
async fn publish_tx(serai: &Serai, tx: &Transaction) -> [u8; 32] {
let mut latest = serai
.block(serai.latest_finalized_block_hash().await.unwrap())
.await
.unwrap()
.unwrap()
.number();
serai.publish(tx).await.unwrap();
// Get the block it was included in
// TODO: Add an RPC method for this/check the guarantee on the subscription
let mut ticks = 0;
loop {
latest += 1;
let block = {
let mut block;
while {
block = serai.finalized_block_by_number(latest).await.unwrap();
block.is_none()
} {
sleep(Duration::from_secs(1)).await;
ticks += 1;
if ticks > 60 {
panic!("60 seconds without inclusion in a finalized block");
}
}
block.unwrap()
};
for transaction in &block.transactions {
if transaction == tx {
return block.hash();
}
}
}
}
#[allow(dead_code)]
async fn allocate_stake(
serai: &Serai,
network: NetworkId,
amount: Amount,
pair: &Pair,
nonce: u32,
) -> [u8; 32] {
// get the call
let tx =
serai.sign(pair, validator_sets::SeraiValidatorSets::allocate(network, amount), nonce, 0);
publish_tx(serai, &tx).await
}
#[allow(dead_code)]
async fn deallocate_stake(
serai: &Serai,
network: NetworkId,
amount: Amount,
pair: &Pair,
nonce: u32,
) -> [u8; 32] {
// get the call
let tx =
serai.sign(pair, validator_sets::SeraiValidatorSets::deallocate(network, amount), nonce, 0);
publish_tx(serai, &tx).await
}
async fn wait_till_next_epoch(serai: &Serai, current_epoch: u32) -> Session {
let mut session = Session(current_epoch);
while session.0 < current_epoch + 1 {
sleep(Duration::from_secs(6)).await;
session = serai
.as_of_latest_finalized_block()
.await
.unwrap()
.validator_sets()
.session(NetworkId::Serai)
.await
.unwrap()
.unwrap();
}
session
}
async fn get_session(serai: &Serai, block: [u8; 32], network: NetworkId) -> Session {
serai.as_of(block).validator_sets().session(network).await.unwrap().unwrap()
}
async fn new_set_events(
serai: &Serai,
session: Session,
network: NetworkId,
) -> Vec<ValidatorSetsEvent> {
let mut current_block = serai.latest_finalized_block().await.unwrap();
let mut current_session = get_session(serai, current_block.hash(), network).await;
while current_session == session {
let events = serai.as_of(current_block.hash()).validator_sets().new_set_events().await.unwrap();
if !events.is_empty() {
return events;
}
current_block = serai.block(current_block.header.parent_hash.0).await.unwrap().unwrap();
current_session = get_session(serai, current_block.hash(), network).await;
}
panic!("can't find the new set events for session: {} ", session.0);
}
#[tokio::test]
async fn set_rotation_test() {
new_test(
|mut processors: Vec<Processor>| async move {
// exclude the last processor from keygen since we will add him later
let excluded = processors.pop().unwrap();
assert_eq!(processors.len(), COORDINATORS);
// genesis keygen
let _ = key_gen::<Secp256k1>(&mut processors, Session(0)).await;
let pair5 = insecure_pair_from_name("Eve");
let network = NetworkId::Bitcoin;
let amount = Amount(1_000_000 * 10_u64.pow(8));
let serai = processors[0].serai().await;
// add the last participant into validator set for btc network
let block = allocate_stake(&serai, network, amount, &pair5, 0).await;
// wait until next session to see the effect on coordinator
let current_epoch = get_session(&serai, block, NetworkId::Serai).await;
let session = wait_till_next_epoch(&serai, current_epoch.0).await;
// verfiy that coordinator received new_set
let events = new_set_events(&serai, session, network).await;
assert!(
events.contains(&ValidatorSetsEvent::NewSet { set: ValidatorSet { session, network } })
);
// add the last participant & do the keygen
processors.push(excluded);
let _ = key_gen::<Secp256k1>(&mut processors, session).await;
},
true,
)
.await;
}

View File

@@ -168,161 +168,172 @@ pub async fn sign(
#[tokio::test]
async fn sign_test() {
new_test(|mut processors: Vec<Processor>| async move {
let (participant_is, substrate_key, _) = key_gen::<Secp256k1>(&mut processors).await;
new_test(
|mut processors: Vec<Processor>| async move {
// pop the last participant since genesis keygen has only 4 participant.
processors.pop().unwrap();
assert_eq!(processors.len(), COORDINATORS);
// 'Send' external coins into Serai
let serai = processors[0].serai().await;
let (serai_pair, serai_addr) = {
let mut name = [0; 4];
OsRng.fill_bytes(&mut name);
let pair = insecure_pair_from_name(&hex::encode(name));
let address = SeraiAddress::from(pair.public());
let (participant_is, substrate_key, _) =
key_gen::<Secp256k1>(&mut processors, Session(0)).await;
// Fund the new account to pay for fees
let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) };
// 'Send' external coins into Serai
let serai = processors[0].serai().await;
let (serai_pair, serai_addr) = {
let mut name = [0; 4];
OsRng.fill_bytes(&mut name);
let pair = insecure_pair_from_name(&hex::encode(name));
let address = SeraiAddress::from(pair.public());
// Fund the new account to pay for fees
let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) };
serai
.publish(&serai.sign(
&insecure_pair_from_name("Ferdie"),
SeraiCoins::transfer(address, balance),
0,
Default::default(),
))
.await
.unwrap();
(pair, address)
};
#[allow(clippy::inconsistent_digit_grouping)]
let amount = Amount(1_000_000_00);
let balance = Balance { coin: Coin::Bitcoin, amount };
let coin_block = BlockHash([0x33; 32]);
let block_included_in = batch(
&mut processors,
&participant_is,
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
id: 0,
block: coin_block,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Transfer(serai_addr),
balance,
}],
},
)
.await;
{
let block_included_in_hash =
serai.finalized_block_by_number(block_included_in).await.unwrap().unwrap().hash();
let serai = serai.as_of(block_included_in_hash);
let serai = serai.coins();
assert_eq!(
serai.coin_balance(Coin::Serai, serai_addr).await.unwrap(),
Amount(1_000_000_000)
);
// Verify the mint occurred as expected
assert_eq!(
serai.mint_events().await.unwrap(),
vec![CoinsEvent::Mint { to: serai_addr, balance }]
);
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), amount);
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount);
}
// Trigger a burn
let out_instruction = OutInstructionWithBalance {
balance,
instruction: OutInstruction {
address: ExternalAddress::new(b"external".to_vec()).unwrap(),
data: None,
},
};
serai
.publish(&serai.sign(
&insecure_pair_from_name("Ferdie"),
SeraiCoins::transfer(address, balance),
&serai_pair,
SeraiCoins::burn_with_instruction(out_instruction.clone()),
0,
Default::default(),
))
.await
.unwrap();
(pair, address)
};
#[allow(clippy::inconsistent_digit_grouping)]
let amount = Amount(1_000_000_00);
let balance = Balance { coin: Coin::Bitcoin, amount };
let coin_block = BlockHash([0x33; 32]);
let block_included_in = batch(
&mut processors,
&participant_is,
Session(0),
&substrate_key,
Batch {
network: NetworkId::Bitcoin,
id: 0,
block: coin_block,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Transfer(serai_addr),
balance,
}],
},
)
.await;
{
let block_included_in_hash =
serai.finalized_block_by_number(block_included_in).await.unwrap().unwrap().hash();
let serai = serai.as_of(block_included_in_hash);
let serai = serai.coins();
assert_eq!(serai.coin_balance(Coin::Serai, serai_addr).await.unwrap(), Amount(1_000_000_000));
// Verify the mint occurred as expected
assert_eq!(
serai.mint_events().await.unwrap(),
vec![CoinsEvent::Mint { to: serai_addr, balance }]
);
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), amount);
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount);
}
// Trigger a burn
let out_instruction = OutInstructionWithBalance {
balance,
instruction: OutInstruction {
address: ExternalAddress::new(b"external".to_vec()).unwrap(),
data: None,
},
};
serai
.publish(&serai.sign(
&serai_pair,
SeraiCoins::burn_with_instruction(out_instruction.clone()),
0,
Default::default(),
))
.await
.unwrap();
// TODO: We *really* need a helper for this pattern
let mut last_serai_block = block_included_in;
'outer: for _ in 0 .. 20 {
tokio::time::sleep(Duration::from_secs(6)).await;
if std::env::var("GITHUB_CI") == Ok("true".to_string()) {
// TODO: We *really* need a helper for this pattern
let mut last_serai_block = block_included_in;
'outer: for _ in 0 .. 20 {
tokio::time::sleep(Duration::from_secs(6)).await;
}
while last_serai_block <= serai.latest_finalized_block().await.unwrap().number() {
let burn_events = serai
.as_of(serai.finalized_block_by_number(last_serai_block).await.unwrap().unwrap().hash())
.coins()
.burn_with_instruction_events()
.await
.unwrap();
if !burn_events.is_empty() {
assert_eq!(burn_events.len(), 1);
assert_eq!(
burn_events[0],
CoinsEvent::BurnWithInstruction {
from: serai_addr,
instruction: out_instruction.clone()
}
);
break 'outer;
if std::env::var("GITHUB_CI") == Ok("true".to_string()) {
tokio::time::sleep(Duration::from_secs(6)).await;
}
last_serai_block += 1;
}
}
let last_serai_block =
serai.finalized_block_by_number(last_serai_block).await.unwrap().unwrap();
let last_serai_block_hash = last_serai_block.hash();
let serai = serai.as_of(last_serai_block_hash);
let serai = serai.coins();
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), Amount(0));
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0));
while last_serai_block <= serai.latest_finalized_block().await.unwrap().number() {
let burn_events = serai
.as_of(serai.finalized_block_by_number(last_serai_block).await.unwrap().unwrap().hash())
.coins()
.burn_with_instruction_events()
.await
.unwrap();
let mut plan_id = [0; 32];
OsRng.fill_bytes(&mut plan_id);
let plan_id = plan_id;
// We should now get a SubstrateBlock
for processor in &mut processors {
assert_eq!(
processor.recv_message().await,
messages::CoordinatorMessage::Substrate(
messages::substrate::CoordinatorMessage::SubstrateBlock {
context: SubstrateContext {
serai_time: last_serai_block.time().unwrap() / 1000,
network_latest_finalized_block: coin_block,
},
block: last_serai_block.number(),
burns: vec![out_instruction.clone()],
batches: vec![],
if !burn_events.is_empty() {
assert_eq!(burn_events.len(), 1);
assert_eq!(
burn_events[0],
CoinsEvent::BurnWithInstruction {
from: serai_addr,
instruction: out_instruction.clone()
}
);
break 'outer;
}
)
);
last_serai_block += 1;
}
}
// Send the ACK, claiming there's a plan to sign
processor
.send_message(messages::ProcessorMessage::Coordinator(
messages::coordinator::ProcessorMessage::SubstrateBlockAck {
block: last_serai_block.number(),
plans: vec![PlanMeta { session: Session(0), id: plan_id }],
},
))
.await;
}
let last_serai_block =
serai.finalized_block_by_number(last_serai_block).await.unwrap().unwrap();
let last_serai_block_hash = last_serai_block.hash();
let serai = serai.as_of(last_serai_block_hash);
let serai = serai.coins();
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), Amount(0));
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0));
sign(&mut processors, &participant_is, Session(0), plan_id).await;
})
let mut plan_id = [0; 32];
OsRng.fill_bytes(&mut plan_id);
let plan_id = plan_id;
// We should now get a SubstrateBlock
for processor in &mut processors {
assert_eq!(
processor.recv_message().await,
messages::CoordinatorMessage::Substrate(
messages::substrate::CoordinatorMessage::SubstrateBlock {
context: SubstrateContext {
serai_time: last_serai_block.time().unwrap() / 1000,
network_latest_finalized_block: coin_block,
},
block: last_serai_block.number(),
burns: vec![out_instruction.clone()],
batches: vec![],
}
)
);
// Send the ACK, claiming there's a plan to sign
processor
.send_message(messages::ProcessorMessage::Coordinator(
messages::coordinator::ProcessorMessage::SubstrateBlockAck {
block: last_serai_block.number(),
plans: vec![PlanMeta { session: Session(0), id: plan_id }],
},
))
.await;
}
sign(&mut processors, &participant_is, Session(0), plan_id).await;
},
false,
)
.await;
}

View File

@@ -69,7 +69,7 @@ pub(crate) async fn new_test(test_body: impl TestBody) {
let monero_processor_composition = monero_processor_composition.swap_remove(0);
let coordinator_composition = coordinator_instance(name, coord_key);
let serai_composition = serai_composition(name);
let serai_composition = serai_composition(name, false);
// Give every item in this stack a unique ID
// Uses a Mutex as we can't generate a 8-byte random ID without hitting hostname length limits