From 396e5322b47cfc0a5b87188b217e2b5dd008575d Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Tue, 18 Apr 2023 03:04:52 -0400 Subject: [PATCH] Code a method to determine the activation block before any block has consensus [0; 32] is a magic for no block has been set yet due to this being the first key pair. If [0; 32] is the latest finalized block, the processor determines an activation block based on timestamps. This doesn't use an Option for ergonomic reasons. --- coordinator/src/substrate.rs | 10 +++- processor/messages/src/lib.rs | 10 +++- processor/src/coins/bitcoin.rs | 4 ++ processor/src/coins/mod.rs | 1 + processor/src/coins/monero.rs | 4 ++ processor/src/key_gen.rs | 15 ++---- processor/src/main.rs | 94 ++++++++++++++++++++++++++-------- processor/src/tests/key_gen.rs | 14 ++--- 8 files changed, 105 insertions(+), 47 deletions(-) diff --git a/coordinator/src/substrate.rs b/coordinator/src/substrate.rs index f852ac41..404d4642 100644 --- a/coordinator/src/substrate.rs +++ b/coordinator/src/substrate.rs @@ -119,10 +119,13 @@ async fn handle_key_gen( .send(CoordinatorMessage::Substrate( processor_messages::substrate::CoordinatorMessage::ConfirmKeyPair { context: SubstrateContext { + serai_time: block.time().unwrap(), coin_latest_finalized_block: serai .get_latest_block_for_network(block.hash(), set.network) .await? - .unwrap_or(BlockHash([0; 32])), // TODO: Have the processor override this + // The processor treats this as a magic value which will cause it to find a network + // block which has a time greater than or equal to the Serai time + .unwrap_or(BlockHash([0; 32])), }, set, key_pair, @@ -206,7 +209,10 @@ async fn handle_batch_and_burns( processor .send(CoordinatorMessage::Substrate( processor_messages::substrate::CoordinatorMessage::SubstrateBlock { - context: SubstrateContext { coin_latest_finalized_block }, + context: SubstrateContext { + serai_time: block.time().unwrap(), + coin_latest_finalized_block, + }, key: get_coin_key( serai, // TODO2 diff --git a/processor/messages/src/lib.rs b/processor/messages/src/lib.rs index 00821649..b07f79a0 100644 --- a/processor/messages/src/lib.rs +++ b/processor/messages/src/lib.rs @@ -13,6 +13,7 @@ use validator_sets_primitives::{ValidatorSet, KeyPair}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Serialize, Deserialize)] pub struct SubstrateContext { + pub serai_time: u64, pub coin_latest_finalized_block: BlockHash, } @@ -181,12 +182,19 @@ pub enum CoordinatorMessage { impl CoordinatorMessage { pub fn required_block(&self) -> Option { - match self { + let required = match self { CoordinatorMessage::KeyGen(msg) => msg.required_block(), CoordinatorMessage::Sign(msg) => msg.required_block(), CoordinatorMessage::Coordinator(msg) => msg.required_block(), CoordinatorMessage::Substrate(msg) => msg.required_block(), + }; + + // 0 is used when Serai hasn't acknowledged *any* block for this network, which also means + // there's no need to wait for the block in question + if required == Some(BlockHash([0; 32])) { + return None; } + required } } diff --git a/processor/src/coins/bitcoin.rs b/processor/src/coins/bitcoin.rs index d0cdab88..1de34bca 100644 --- a/processor/src/coins/bitcoin.rs +++ b/processor/src/coins/bitcoin.rs @@ -204,6 +204,10 @@ impl BlockTrait for Block { hash } + fn time(&self) -> u64 { + self.header.time.into() + } + fn median_fee(&self) -> Fee { // TODO Fee(20) diff --git a/processor/src/coins/mod.rs b/processor/src/coins/mod.rs index 1f4d212a..fc46f4d1 100644 --- a/processor/src/coins/mod.rs +++ b/processor/src/coins/mod.rs @@ -176,6 +176,7 @@ pub trait Block: Send + Sync + Sized + Clone + Debug { type Id: 'static + Id; fn id(&self) -> Self::Id; fn parent(&self) -> Self::Id; + fn time(&self) -> u64; fn median_fee(&self) -> C::Fee; } diff --git a/processor/src/coins/monero.rs b/processor/src/coins/monero.rs index daaa3d23..345d7664 100644 --- a/processor/src/coins/monero.rs +++ b/processor/src/coins/monero.rs @@ -146,6 +146,10 @@ impl BlockTrait for Block { self.1.header.previous } + fn time(&self) -> u64 { + self.1.header.timestamp + } + fn median_fee(&self) -> Fee { // TODO Fee { per_weight: 80000, mask: 10000 } diff --git a/processor/src/key_gen.rs b/processor/src/key_gen.rs index f7f5f802..6a9f692d 100644 --- a/processor/src/key_gen.rs +++ b/processor/src/key_gen.rs @@ -15,17 +15,13 @@ use frost::{ use log::info; -use serai_client::{ - primitives::BlockHash, - validator_sets::primitives::{ValidatorSet, KeyPair}, -}; -use messages::{SubstrateContext, key_gen::*}; +use serai_client::validator_sets::primitives::{ValidatorSet, KeyPair}; +use messages::key_gen::*; use crate::{Get, DbTxn, Db, coins::Coin}; #[derive(Debug)] pub struct KeyConfirmed { - pub activation_block: BlockHash, pub substrate_keys: ThresholdKeys, pub coin_keys: ThresholdKeys, } @@ -364,7 +360,6 @@ impl KeyGen { pub async fn confirm( &mut self, txn: &mut D::Transaction<'_>, - context: SubstrateContext, set: ValidatorSet, key_pair: KeyPair, ) -> KeyConfirmed { @@ -377,10 +372,6 @@ impl KeyGen { set, ); - KeyConfirmed { - activation_block: context.coin_latest_finalized_block, - substrate_keys, - coin_keys, - } + KeyConfirmed { substrate_keys, coin_keys } } } diff --git a/processor/src/main.rs b/processor/src/main.rs index 01d8a8c9..79362116 100644 --- a/processor/src/main.rs +++ b/processor/src/main.rs @@ -70,26 +70,42 @@ pub(crate) fn additional_key(k: u64) -> ::F { ) } -async fn get_fee(coin: &C, block_number: usize) -> C::Fee { +async fn get_latest_block_number(coin: &C) -> usize { loop { - // TODO2: Use an fee representative of several blocks - match coin.get_block(block_number).await { - Ok(block) => { - return block.median_fee(); + match coin.get_latest_block_number().await { + Ok(number) => { + return number; } Err(e) => { error!( - "couldn't get block {block_number} in get_fee. {} {}", + "couldn't get the latest block number in main's error-free get_block. {} {}", "this should only happen if the node is offline. error: ", e ); - // Since this block is considered finalized, we shouldn't be unable to get it unless the - // node is offline, hence the long sleep - sleep(Duration::from_secs(60)).await; + sleep(Duration::from_secs(10)).await; } } } } +async fn get_block(coin: &C, block_number: usize) -> C::Block { + loop { + match coin.get_block(block_number).await { + Ok(block) => { + return block; + } + Err(e) => { + error!("couldn't get block {block_number} in main's error-free get_block. error: {}", e); + sleep(Duration::from_secs(10)).await; + } + } + } +} + +async fn get_fee(coin: &C, block_number: usize) -> C::Fee { + // TODO2: Use an fee representative of several blocks + get_block(coin, block_number).await.median_fee() +} + async fn prepare_send( coin: &C, keys: ThresholdKeys, @@ -261,11 +277,11 @@ async fn handle_coordinator_msg( let synced = |context: &SubstrateContext, key| -> Result<(), ()> { // Check that we've synced this block and can actually operate on it ourselves let latest = scanner.latest_scanned(key); - if usize::try_from(context.coin_latest_block_number).unwrap() < latest { + if usize::try_from(context.coin_latest_finalized_block).unwrap() < latest { log::warn!( "coin node disconnected/desynced from rest of the network. \ our block: {latest:?}, network's acknowledged: {}", - context.coin_latest_block_number + context.coin_latest_finalized_block, ); Err(())?; } @@ -302,9 +318,52 @@ async fn handle_coordinator_msg( CoordinatorMessage::Substrate(msg) => { match msg { messages::substrate::CoordinatorMessage::ConfirmKeyPair { context, set, key_pair } => { + // This is the first key pair for this coin so no block has been finalized yet + let activation_number = if context.coin_latest_finalized_block.0 == [0; 32] { + assert!(tributary_mutable.signers.is_empty()); + assert!(tributary_mutable.substrate_signers.is_empty()); + assert!(substrate_mutable.schedulers.is_empty()); + + // Wait until a coin's block's time exceeds Serai's time + while get_block( + coin, + get_latest_block_number(coin).await.saturating_sub(C::CONFIRMATIONS), + ) + .await + .time() < + context.serai_time + { + info!( + "serai confirmed the first key pair for a set. {} {}", + "we're waiting for a coin's finalized block's time to exceed unix time ", + context.serai_time, + ); + sleep(Duration::from_secs(5)).await; + } + + // Find the first block to do so + let mut earliest = get_latest_block_number(coin).await.saturating_sub(C::CONFIRMATIONS); + assert!(get_block(coin, earliest).await.time() >= context.serai_time); + while get_block(coin, earliest - 1).await.time() >= context.serai_time { + earliest -= 1; + } + + // Use this as the activation block + earliest + } else { + let mut activation_block = >::Id::default(); + activation_block.as_mut().copy_from_slice(&context.coin_latest_finalized_block.0); + // This block_number call is safe since it unwraps + substrate_mutable + .scanner + .block_number(&activation_block) + .await + .expect("KeyConfirmed from context we haven't synced") + }; + // See TributaryMutable's struct definition for why this block is safe - let KeyConfirmed { activation_block, substrate_keys, coin_keys } = - tributary_mutable.key_gen.confirm(txn, context, set, key_pair).await; + let KeyConfirmed { substrate_keys, coin_keys } = + tributary_mutable.key_gen.confirm(txn, set, key_pair).await; tributary_mutable.substrate_signers.insert( substrate_keys.group_key().to_bytes().to_vec(), SubstrateSigner::new(substrate_keys), @@ -312,15 +371,6 @@ async fn handle_coordinator_msg( let key = coin_keys.group_key(); - let mut activation_block_hash = >::Id::default(); - activation_block_hash.as_mut().copy_from_slice(&activation_block.0); - // This block_number call is safe since it unwraps - let activation_number = substrate_mutable - .scanner - .block_number(&activation_block_hash) - .await - .expect("KeyConfirmed from context we haven't synced"); - substrate_mutable.scanner.rotate_key(txn, activation_number, key).await; substrate_mutable .schedulers diff --git a/processor/src/tests/key_gen.rs b/processor/src/tests/key_gen.rs index 35bc8513..4a52f2d1 100644 --- a/processor/src/tests/key_gen.rs +++ b/processor/src/tests/key_gen.rs @@ -11,11 +11,11 @@ use serai_db::{DbTxn, Db, MemDb}; use sp_application_crypto::sr25519; use serai_client::{ - primitives::{BlockHash, NetworkId}, + primitives::NetworkId, validator_sets::primitives::{Session, ValidatorSet}, }; -use messages::{SubstrateContext, key_gen::*}; +use messages::key_gen::*; use crate::{ coins::Coin, key_gen::{KeyConfirmed, KeyGen}, @@ -134,17 +134,11 @@ pub async fn test_key_gen() { 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 { activation_block, substrate_keys, coin_keys } = key_gen - .confirm( - &mut txn, - SubstrateContext { coin_latest_finalized_block: BlockHash([0x11; 32]) }, - ID.set, - (sr25519::Public(res.0), res.1.clone().try_into().unwrap()), - ) + let KeyConfirmed { substrate_keys, coin_keys } = key_gen + .confirm(&mut txn, ID.set, (sr25519::Public(res.0), res.1.clone().try_into().unwrap())) .await; txn.commit(); - assert_eq!(activation_block, BlockHash([0x11; 32])); let params = ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap(); assert_eq!(substrate_keys.params(), params);