16 Commits

Author SHA1 Message Date
Luke Parker
44d0eeeb18 Restore key gen message match from develop
It was modified in response to the handover completion bug, which has now been
resolved.
2024-06-10 19:04:22 -04:00
Luke Parker
5a9ebc8cdc Correct detection of handover completion 2024-06-10 18:34:58 -04:00
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
10 changed files with 433 additions and 205 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

@@ -643,8 +643,9 @@ pub mod pallet {
// Checks if this session has completed the handover from the prior session.
fn handover_completed(network: NetworkId, session: Session) -> bool {
let Some(current_session) = Self::session(network) else { return false };
// No handover occurs on genesis
if current_session.0 == 0 {
// If the session we've been queried about is old, it must have completed its handover
if current_session.0 > session.0 {
return true;
}
// If the session we've been queried about has yet to start, it can't have completed its
@@ -652,19 +653,21 @@ pub mod pallet {
if current_session.0 < session.0 {
return false;
}
if current_session.0 == session.0 {
// Handover is automatically complete for Serai as it doesn't have a handover protocol
// If not Serai, check the prior session had its keys cleared, which happens once its
// retired
return (network == NetworkId::Serai) ||
(!Keys::<T>::contains_key(ValidatorSet {
network,
session: Session(current_session.0 - 1),
}));
// Handover is automatically complete for Serai as it doesn't have a handover protocol
if network == NetworkId::Serai {
return true;
}
// We're currently in a future session, meaning this session definitely performed itself
// handover
true
// The current session must have set keys for its handover to be completed
if !Keys::<T>::contains_key(ValidatorSet { network, session }) {
return false;
}
// This must be the first session (which has set keys) OR the prior session must have been
// retired (signified by its keys no longer being present)
(session.0 == 0) ||
(!Keys::<T>::contains_key(ValidatorSet { network, session: Session(session.0 - 1) }))
}
fn new_session() {
@@ -682,6 +685,8 @@ pub mod pallet {
}
}
// TODO: This is called retire_set, yet just starts retiring the set
// Update the nomenclature within this function
pub fn retire_set(set: ValidatorSet) {
// If the prior prior set didn't report, emit they're retired now
if PendingSlashReport::<T>::get(set.network).is_some() {

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,10 +23,12 @@ 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() {
@@ -46,8 +48,8 @@ pub async fn key_gen<C: Ciphersuite>(
CoordinatorMessage::KeyGen(messages::key_gen::CoordinatorMessage::GenerateKey {
id,
params: ThresholdParams::new(
u16::try_from(((COORDINATORS * 2) / 3) + 1).unwrap(),
u16::try_from(COORDINATORS).unwrap(),
u16::try_from(((coordinators * 2) / 3) + 1).unwrap(),
u16::try_from(coordinators).unwrap(),
participant_is[i],
)
.unwrap(),
@@ -65,7 +67,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 +85,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 +120,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 +184,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 +222,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,169 @@
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 get_session(serai: &Serai, network: NetworkId) -> Session {
serai
.as_of_latest_finalized_block()
.await
.unwrap()
.validator_sets()
.session(network)
.await
.unwrap()
.unwrap()
}
async fn wait_till_next_epoch(serai: &Serai) -> Session {
let starting_session = get_session(serai, NetworkId::Serai).await;
let mut session = starting_session;
while session == starting_session {
sleep(Duration::from_secs(6)).await;
session = get_session(serai, NetworkId::Serai).await;
}
session
}
async fn most_recent_new_set_event(serai: &Serai, network: NetworkId) -> ValidatorSetsEvent {
let mut current_block = serai.latest_finalized_block().await.unwrap();
loop {
let events = serai.as_of(current_block.hash()).validator_sets().new_set_events().await.unwrap();
for event in events {
match event {
ValidatorSetsEvent::NewSet { set } => {
if set.network == network {
return event;
}
}
_ => panic!("new_set_events gave non-NewSet event: {event:?}"),
}
}
current_block = serai.block(current_block.header.parent_hash.0).await.unwrap().unwrap();
}
}
#[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 mut excluded = processors.pop().unwrap();
assert_eq!(processors.len(), COORDINATORS);
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
allocate_stake(&serai, network, amount, &pair5, 0).await;
// genesis keygen
let _ = key_gen::<Secp256k1>(&mut processors, Session(0)).await;
// Even the excluded processor should receive the key pair confirmation
match excluded.recv_message().await {
CoordinatorMessage::Substrate(
messages::substrate::CoordinatorMessage::ConfirmKeyPair { session, .. },
) => assert_eq!(session, Session(0)),
_ => panic!("excluded got message other than ConfirmKeyPair"),
}
// wait until next session to see the effect on coordinator
wait_till_next_epoch(&serai).await;
// verfiy that coordinator received new_set
assert_eq!(
most_recent_new_set_event(&serai, network).await,
ValidatorSetsEvent::NewSet { set: ValidatorSet { session: Session(1), network } },
);
// add the last participant & do the keygen
processors.push(excluded);
let _ = key_gen::<Secp256k1>(&mut processors, Session(1)).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