mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user