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.
This commit is contained in:
Luke Parker
2023-04-18 03:04:52 -04:00
parent 9da0eb69c7
commit 396e5322b4
8 changed files with 105 additions and 47 deletions

View File

@@ -119,10 +119,13 @@ async fn handle_key_gen<D: Db, Pro: Processor>(
.send(CoordinatorMessage::Substrate( .send(CoordinatorMessage::Substrate(
processor_messages::substrate::CoordinatorMessage::ConfirmKeyPair { processor_messages::substrate::CoordinatorMessage::ConfirmKeyPair {
context: SubstrateContext { context: SubstrateContext {
serai_time: block.time().unwrap(),
coin_latest_finalized_block: serai coin_latest_finalized_block: serai
.get_latest_block_for_network(block.hash(), set.network) .get_latest_block_for_network(block.hash(), set.network)
.await? .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, set,
key_pair, key_pair,
@@ -206,7 +209,10 @@ async fn handle_batch_and_burns<D: Db, Pro: Processor>(
processor processor
.send(CoordinatorMessage::Substrate( .send(CoordinatorMessage::Substrate(
processor_messages::substrate::CoordinatorMessage::SubstrateBlock { 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( key: get_coin_key(
serai, serai,
// TODO2 // TODO2

View File

@@ -13,6 +13,7 @@ use validator_sets_primitives::{ValidatorSet, KeyPair};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Serialize, Deserialize)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Serialize, Deserialize)]
pub struct SubstrateContext { pub struct SubstrateContext {
pub serai_time: u64,
pub coin_latest_finalized_block: BlockHash, pub coin_latest_finalized_block: BlockHash,
} }
@@ -181,12 +182,19 @@ pub enum CoordinatorMessage {
impl CoordinatorMessage { impl CoordinatorMessage {
pub fn required_block(&self) -> Option<BlockHash> { pub fn required_block(&self) -> Option<BlockHash> {
match self { let required = match self {
CoordinatorMessage::KeyGen(msg) => msg.required_block(), CoordinatorMessage::KeyGen(msg) => msg.required_block(),
CoordinatorMessage::Sign(msg) => msg.required_block(), CoordinatorMessage::Sign(msg) => msg.required_block(),
CoordinatorMessage::Coordinator(msg) => msg.required_block(), CoordinatorMessage::Coordinator(msg) => msg.required_block(),
CoordinatorMessage::Substrate(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
} }
} }

View File

@@ -204,6 +204,10 @@ impl BlockTrait<Bitcoin> for Block {
hash hash
} }
fn time(&self) -> u64 {
self.header.time.into()
}
fn median_fee(&self) -> Fee { fn median_fee(&self) -> Fee {
// TODO // TODO
Fee(20) Fee(20)

View File

@@ -176,6 +176,7 @@ pub trait Block<C: Coin>: Send + Sync + Sized + Clone + Debug {
type Id: 'static + Id; type Id: 'static + Id;
fn id(&self) -> Self::Id; fn id(&self) -> Self::Id;
fn parent(&self) -> Self::Id; fn parent(&self) -> Self::Id;
fn time(&self) -> u64;
fn median_fee(&self) -> C::Fee; fn median_fee(&self) -> C::Fee;
} }

View File

@@ -146,6 +146,10 @@ impl BlockTrait<Monero> for Block {
self.1.header.previous self.1.header.previous
} }
fn time(&self) -> u64 {
self.1.header.timestamp
}
fn median_fee(&self) -> Fee { fn median_fee(&self) -> Fee {
// TODO // TODO
Fee { per_weight: 80000, mask: 10000 } Fee { per_weight: 80000, mask: 10000 }

View File

@@ -15,17 +15,13 @@ use frost::{
use log::info; use log::info;
use serai_client::{ use serai_client::validator_sets::primitives::{ValidatorSet, KeyPair};
primitives::BlockHash, use messages::key_gen::*;
validator_sets::primitives::{ValidatorSet, KeyPair},
};
use messages::{SubstrateContext, key_gen::*};
use crate::{Get, DbTxn, Db, coins::Coin}; use crate::{Get, DbTxn, Db, coins::Coin};
#[derive(Debug)] #[derive(Debug)]
pub struct KeyConfirmed<C: Ciphersuite> { pub struct KeyConfirmed<C: Ciphersuite> {
pub activation_block: BlockHash,
pub substrate_keys: ThresholdKeys<Ristretto>, pub substrate_keys: ThresholdKeys<Ristretto>,
pub coin_keys: ThresholdKeys<C>, pub coin_keys: ThresholdKeys<C>,
} }
@@ -364,7 +360,6 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
pub async fn confirm( pub async fn confirm(
&mut self, &mut self,
txn: &mut D::Transaction<'_>, txn: &mut D::Transaction<'_>,
context: SubstrateContext,
set: ValidatorSet, set: ValidatorSet,
key_pair: KeyPair, key_pair: KeyPair,
) -> KeyConfirmed<C::Curve> { ) -> KeyConfirmed<C::Curve> {
@@ -377,10 +372,6 @@ impl<C: Coin, D: Db> KeyGen<C, D> {
set, set,
); );
KeyConfirmed { KeyConfirmed { substrate_keys, coin_keys }
activation_block: context.coin_latest_finalized_block,
substrate_keys,
coin_keys,
}
} }
} }

View File

@@ -70,26 +70,42 @@ pub(crate) fn additional_key<C: Coin>(k: u64) -> <C::Curve as Ciphersuite>::F {
) )
} }
async fn get_fee<C: Coin>(coin: &C, block_number: usize) -> C::Fee { async fn get_latest_block_number<C: Coin>(coin: &C) -> usize {
loop { loop {
// TODO2: Use an fee representative of several blocks match coin.get_latest_block_number().await {
match coin.get_block(block_number).await { Ok(number) => {
Ok(block) => { return number;
return block.median_fee();
} }
Err(e) => { Err(e) => {
error!( 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 "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 sleep(Duration::from_secs(10)).await;
// node is offline, hence the long sleep
sleep(Duration::from_secs(60)).await;
} }
} }
} }
} }
async fn get_block<C: Coin>(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<C: Coin>(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<C: Coin>( async fn prepare_send<C: Coin>(
coin: &C, coin: &C,
keys: ThresholdKeys<C::Curve>, keys: ThresholdKeys<C::Curve>,
@@ -261,11 +277,11 @@ async fn handle_coordinator_msg<D: Db, C: Coin, Co: Coordinator>(
let synced = |context: &SubstrateContext, key| -> Result<(), ()> { let synced = |context: &SubstrateContext, key| -> Result<(), ()> {
// Check that we've synced this block and can actually operate on it ourselves // Check that we've synced this block and can actually operate on it ourselves
let latest = scanner.latest_scanned(key); 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!( log::warn!(
"coin node disconnected/desynced from rest of the network. \ "coin node disconnected/desynced from rest of the network. \
our block: {latest:?}, network's acknowledged: {}", our block: {latest:?}, network's acknowledged: {}",
context.coin_latest_block_number context.coin_latest_finalized_block,
); );
Err(())?; Err(())?;
} }
@@ -302,9 +318,52 @@ async fn handle_coordinator_msg<D: Db, C: Coin, Co: Coordinator>(
CoordinatorMessage::Substrate(msg) => { CoordinatorMessage::Substrate(msg) => {
match msg { match msg {
messages::substrate::CoordinatorMessage::ConfirmKeyPair { context, set, key_pair } => { 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 = <C::Block as Block<C>>::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 // See TributaryMutable's struct definition for why this block is safe
let KeyConfirmed { activation_block, substrate_keys, coin_keys } = let KeyConfirmed { substrate_keys, coin_keys } =
tributary_mutable.key_gen.confirm(txn, context, set, key_pair).await; tributary_mutable.key_gen.confirm(txn, set, key_pair).await;
tributary_mutable.substrate_signers.insert( tributary_mutable.substrate_signers.insert(
substrate_keys.group_key().to_bytes().to_vec(), substrate_keys.group_key().to_bytes().to_vec(),
SubstrateSigner::new(substrate_keys), SubstrateSigner::new(substrate_keys),
@@ -312,15 +371,6 @@ async fn handle_coordinator_msg<D: Db, C: Coin, Co: Coordinator>(
let key = coin_keys.group_key(); let key = coin_keys.group_key();
let mut activation_block_hash = <C::Block as Block<C>>::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.scanner.rotate_key(txn, activation_number, key).await;
substrate_mutable substrate_mutable
.schedulers .schedulers

View File

@@ -11,11 +11,11 @@ use serai_db::{DbTxn, Db, MemDb};
use sp_application_crypto::sr25519; use sp_application_crypto::sr25519;
use serai_client::{ use serai_client::{
primitives::{BlockHash, NetworkId}, primitives::NetworkId,
validator_sets::primitives::{Session, ValidatorSet}, validator_sets::primitives::{Session, ValidatorSet},
}; };
use messages::{SubstrateContext, key_gen::*}; use messages::key_gen::*;
use crate::{ use crate::{
coins::Coin, coins::Coin,
key_gen::{KeyConfirmed, KeyGen}, key_gen::{KeyConfirmed, KeyGen},
@@ -134,17 +134,11 @@ pub async fn test_key_gen<C: Coin>() {
for i in 1 ..= 5 { for i in 1 ..= 5 {
let key_gen = key_gens.get_mut(&i).unwrap(); let key_gen = key_gens.get_mut(&i).unwrap();
let mut txn = dbs.get_mut(&i).unwrap().txn(); let mut txn = dbs.get_mut(&i).unwrap().txn();
let KeyConfirmed { activation_block, substrate_keys, coin_keys } = key_gen let KeyConfirmed { substrate_keys, coin_keys } = key_gen
.confirm( .confirm(&mut txn, ID.set, (sr25519::Public(res.0), res.1.clone().try_into().unwrap()))
&mut txn,
SubstrateContext { coin_latest_finalized_block: BlockHash([0x11; 32]) },
ID.set,
(sr25519::Public(res.0), res.1.clone().try_into().unwrap()),
)
.await; .await;
txn.commit(); txn.commit();
assert_eq!(activation_block, BlockHash([0x11; 32]));
let params = let params =
ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap(); ThresholdParams::new(3, 5, Participant::new(u16::try_from(i).unwrap()).unwrap()).unwrap();
assert_eq!(substrate_keys.params(), params); assert_eq!(substrate_keys.params(), params);