Update all of serai-coordinator to compile with the new serai-client-serai

This commit is contained in:
Luke Parker
2025-11-16 11:50:24 -05:00
parent 9891ccade8
commit 7a314baa9f
37 changed files with 1014 additions and 1081 deletions

View File

@@ -107,8 +107,8 @@ jobs:
- name: Run Tests
run: |
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-serai
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-bitcoin
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-ethereum
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-monero
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client-serai
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features -p serai-client

1403
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -93,10 +93,10 @@ members = [
"substrate/runtime",
"substrate/node",
"substrate/client/serai",
"substrate/client/bitcoin",
"substrate/client/ethereum",
"substrate/client/monero",
"substrate/client/serai",
"substrate/client",
"orchestration",

View File

@@ -42,7 +42,7 @@ messages = { package = "serai-processor-messages", path = "../processor/messages
message-queue = { package = "serai-message-queue", path = "../message-queue" }
tributary-sdk = { path = "./tributary-sdk" }
serai-client = { path = "../substrate/client", default-features = false, features = ["serai"] }
serai-client-serai = { path = "../substrate/client/serai", default-features = false }
log = { version = "0.4", default-features = false, features = ["std"] }
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }

View File

@@ -21,7 +21,6 @@ workspace = true
blake2 = { version = "0.11.0-rc.0", default-features = false, features = ["alloc"] }
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
serai-abi = { path = "../../substrate/abi", default-features = false, features = ["std"] }
serai-client-serai = { path = "../../substrate/client/serai", default-features = false }
log = { version = "0.4", default-features = false, features = ["std"] }

View File

@@ -3,11 +3,13 @@ use std::{sync::Arc, collections::HashMap};
use blake2::{Digest, Blake2b256};
use serai_abi::primitives::{
balance::Amount, validator_sets::ExternalValidatorSet, address::SeraiAddress,
merkle::IncrementalUnbalancedMerkleTree,
use serai_client_serai::{
abi::primitives::{
balance::Amount, validator_sets::ExternalValidatorSet, address::SeraiAddress,
merkle::IncrementalUnbalancedMerkleTree,
},
Serai,
};
use serai_client_serai::Serai;
use serai_db::*;
use serai_task::ContinuallyRan;
@@ -85,7 +87,7 @@ impl<D: Db> ContinuallyRan for CosignIntendTask<D> {
// Check we are indexing a linear chain
if block.header.builds_upon() !=
builds_upon.clone().calculate(serai_abi::BLOCK_HEADER_BRANCH_TAG)
builds_upon.clone().calculate(serai_client_serai::abi::BLOCK_HEADER_BRANCH_TAG)
{
Err(format!(
"node's block #{block_number} doesn't build upon the block #{} prior indexed",
@@ -95,8 +97,8 @@ impl<D: Db> ContinuallyRan for CosignIntendTask<D> {
let block_hash = block.header.hash();
SubstrateBlockHash::set(&mut txn, block_number, &block_hash);
builds_upon.append(
serai_abi::BLOCK_HEADER_BRANCH_TAG,
Blake2b256::new_with_prefix([serai_abi::BLOCK_HEADER_LEAF_TAG])
serai_client_serai::abi::BLOCK_HEADER_BRANCH_TAG,
Blake2b256::new_with_prefix([serai_client_serai::abi::BLOCK_HEADER_LEAF_TAG])
.chain_update(block_hash.0)
.finalize()
.into(),

View File

@@ -9,22 +9,24 @@ use blake2::{Digest, Blake2s256};
use borsh::{BorshSerialize, BorshDeserialize};
use serai_abi::{
primitives::{
BlockHash,
crypto::{Public, KeyPair},
network_id::ExternalNetworkId,
validator_sets::{Session, ExternalValidatorSet},
address::SeraiAddress,
use serai_client_serai::{
abi::{
primitives::{
BlockHash,
crypto::{Public, KeyPair},
network_id::ExternalNetworkId,
validator_sets::{Session, ExternalValidatorSet},
address::SeraiAddress,
},
Block,
},
Block,
Serai, TemporalSerai,
};
use serai_client_serai::{Serai, TemporalSerai};
use serai_db::*;
use serai_task::*;
use serai_cosign_types::*;
pub use serai_cosign_types::*;
/// The cosigns which are intended to be performed.
mod intend;

View File

@@ -29,7 +29,7 @@ schnorrkel = { version = "0.11", default-features = false, features = ["std"] }
hex = { version = "0.4", default-features = false, features = ["std"] }
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
serai-client = { path = "../../../substrate/client", default-features = false, features = ["serai"] }
serai-client-serai = { path = "../../../substrate/client/serai", default-features = false }
serai-cosign = { path = "../../cosign" }
tributary-sdk = { path = "../../tributary-sdk" }

View File

@@ -7,7 +7,7 @@ use rand_core::{RngCore, OsRng};
use blake2::{Digest, Blake2s256};
use schnorrkel::{Keypair, PublicKey, Signature};
use serai_client::primitives::PublicKey as Public;
use serai_client_serai::abi::primitives::crypto::Public;
use futures_util::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use libp2p::{
@@ -104,7 +104,7 @@ impl OnlyValidators {
.verify_simple(PROTOCOL.as_bytes(), &msg, &sig)
.map_err(|_| io::Error::other("invalid signature"))?;
Ok(peer_id_from_public(Public::from_raw(public_key.to_bytes())))
Ok(peer_id_from_public(Public(public_key.to_bytes())))
}
}

View File

@@ -1,11 +1,11 @@
use core::future::Future;
use core::{future::Future, str::FromStr};
use std::{sync::Arc, collections::HashSet};
use rand_core::{RngCore, OsRng};
use tokio::sync::mpsc;
use serai_client::{SeraiError, Serai};
use serai_client_serai::{RpcError, Serai};
use libp2p::{
core::multiaddr::{Protocol, Multiaddr},
@@ -50,7 +50,7 @@ impl ContinuallyRan for DialTask {
const DELAY_BETWEEN_ITERATIONS: u64 = 5 * 60;
const MAX_DELAY_BETWEEN_ITERATIONS: u64 = 10 * 60;
type Error = SeraiError;
type Error = RpcError;
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, Self::Error>> {
async move {
@@ -94,6 +94,13 @@ impl ContinuallyRan for DialTask {
usize::try_from(OsRng.next_u64() % u64::try_from(potential_peers.len()).unwrap())
.unwrap();
let randomly_selected_peer = potential_peers.swap_remove(index_to_dial);
let Ok(randomly_selected_peer) = libp2p::Multiaddr::from_str(&randomly_selected_peer)
else {
log::error!(
"peer from substrate wasn't a valid `Multiaddr`: {randomly_selected_peer}"
);
continue;
};
log::info!("found peer from substrate: {randomly_selected_peer}");

View File

@@ -13,9 +13,10 @@ use rand_core::{RngCore, OsRng};
use zeroize::Zeroizing;
use schnorrkel::Keypair;
use serai_client::{
primitives::{ExternalNetworkId, PublicKey},
validator_sets::primitives::ExternalValidatorSet,
use serai_client_serai::{
abi::primitives::{
crypto::Public, network_id::ExternalNetworkId, validator_sets::ExternalValidatorSet,
},
Serai,
};
@@ -66,7 +67,7 @@ use dial::DialTask;
const PORT: u16 = 30563; // 5132 ^ (('c' << 8) | 'o')
fn peer_id_from_public(public: PublicKey) -> PeerId {
fn peer_id_from_public(public: Public) -> PeerId {
// 0 represents the identity Multihash, that no hash was performed
// It's an internal constant so we can't refer to the constant inside libp2p
PeerId::from_multihash(Multihash::wrap(0, &public.0).unwrap()).unwrap()

View File

@@ -6,7 +6,7 @@ use std::{
use borsh::BorshDeserialize;
use serai_client::validator_sets::primitives::ExternalValidatorSet;
use serai_client_serai::abi::primitives::validator_sets::ExternalValidatorSet;
use tokio::sync::{mpsc, oneshot, RwLock};

View File

@@ -4,9 +4,8 @@ use std::{
collections::{HashSet, HashMap},
};
use serai_client::{
primitives::ExternalNetworkId, validator_sets::primitives::Session, SeraiError, Serai,
};
use serai_client_serai::abi::primitives::{network_id::ExternalNetworkId, validator_sets::Session};
use serai_client_serai::{RpcError, Serai};
use serai_task::{Task, ContinuallyRan};
@@ -52,7 +51,7 @@ impl Validators {
async fn session_changes(
serai: impl Borrow<Serai>,
sessions: impl Borrow<HashMap<ExternalNetworkId, Session>>,
) -> Result<Vec<(ExternalNetworkId, Session, HashSet<PeerId>)>, SeraiError> {
) -> Result<Vec<(ExternalNetworkId, Session, HashSet<PeerId>)>, RpcError> {
/*
This uses the latest finalized block, not the latest cosigned block, which should be fine as
in the worst case, we'd connect to unexpected validators. They still shouldn't be able to
@@ -69,10 +68,11 @@ impl Validators {
// FuturesUnordered can be bad practice as it'll cause timeouts if infrequently polled, but
// we poll it till it yields all futures with the most minimal processing possible
let mut futures = FuturesUnordered::new();
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
let sessions = sessions.borrow();
let temporal_serai = temporal_serai.borrow();
futures.push(async move {
let session = match temporal_serai.session(network.into()).await {
let session = match temporal_serai.current_session(network.into()).await {
Ok(Some(session)) => session,
Ok(None) => return Ok(None),
Err(e) => return Err(e),
@@ -81,12 +81,16 @@ impl Validators {
if sessions.get(&network) == Some(&session) {
Ok(None)
} else {
match temporal_serai.active_network_validators(network.into()).await {
Ok(validators) => Ok(Some((
match temporal_serai.current_validators(network.into()).await {
Ok(Some(validators)) => Ok(Some((
network,
session,
validators.into_iter().map(peer_id_from_public).collect(),
validators
.into_iter()
.map(|validator| peer_id_from_public(validator.into()))
.collect(),
))),
Ok(None) => panic!("network has session yet no validators"),
Err(e) => Err(e),
}
}
@@ -153,7 +157,7 @@ impl Validators {
}
/// Update the view of the validators.
pub(crate) async fn update(&mut self) -> Result<(), SeraiError> {
pub(crate) async fn update(&mut self) -> Result<(), RpcError> {
let session_changes = Self::session_changes(&*self.serai, &self.sessions).await?;
self.incorporate_session_changes(session_changes);
Ok(())
@@ -206,7 +210,7 @@ impl ContinuallyRan for UpdateValidatorsTask {
const DELAY_BETWEEN_ITERATIONS: u64 = 60;
const MAX_DELAY_BETWEEN_ITERATIONS: u64 = 5 * 60;
type Error = SeraiError;
type Error = RpcError;
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, Self::Error>> {
async move {

View File

@@ -1,7 +1,7 @@
use core::future::Future;
use std::time::{Duration, SystemTime};
use serai_primitives::{MAX_KEY_SHARES_PER_SET, ExternalValidatorSet};
use serai_primitives::validator_sets::{ExternalValidatorSet, KeyShares};
use futures_lite::FutureExt;
@@ -30,7 +30,7 @@ pub const MIN_BLOCKS_PER_BATCH: usize = BLOCKS_PER_MINUTE + 1;
/// commit is `8 + (validators * 32) + (32 + (validators * 32))` (for the time, list of validators,
/// and aggregate signature). Accordingly, this should be a safe over-estimate.
pub const BATCH_SIZE_LIMIT: usize = MIN_BLOCKS_PER_BATCH *
(tributary_sdk::BLOCK_SIZE_LIMIT + 32 + ((MAX_KEY_SHARES_PER_SET as usize) * 128));
(tributary_sdk::BLOCK_SIZE_LIMIT + 32 + ((KeyShares::MAX_PER_SET as usize) * 128));
/// Sends a heartbeat to other validators on regular intervals informing them of our Tributary's
/// tip.

View File

@@ -5,9 +5,10 @@ use serai_db::{create_db, db_channel};
use dkg::Participant;
use serai_client::{
primitives::ExternalNetworkId,
validator_sets::primitives::{Session, ExternalValidatorSet, KeyPair},
use serai_client_serai::abi::primitives::{
crypto::KeyPair,
network_id::ExternalNetworkId,
validator_sets::{Session, ExternalValidatorSet},
};
use serai_cosign::SignedCosign;
@@ -49,6 +50,7 @@ fn tributary_db_folder(set: ExternalValidatorSet) -> String {
ExternalNetworkId::Bitcoin => "Bitcoin",
ExternalNetworkId::Ethereum => "Ethereum",
ExternalNetworkId::Monero => "Monero",
_ => panic!("unrecognized `ExternalNetworkId`"),
};
format!("{root_path}/tributary-{network}-{}", set.session.0)
}
@@ -103,7 +105,7 @@ mod _internal_db {
// Tributary transactions to publish from the DKG confirmation task
TributaryTransactionsFromDkgConfirmation: (set: ExternalValidatorSet) -> Transaction,
// Participants to remove
RemoveParticipant: (set: ExternalValidatorSet) -> Participant,
RemoveParticipant: (set: ExternalValidatorSet) -> u16,
}
}
}
@@ -139,10 +141,11 @@ impl RemoveParticipant {
pub(crate) fn send(txn: &mut impl DbTxn, set: ExternalValidatorSet, participant: Participant) {
// If this set has yet to be retired, send this transaction
if RetiredTributary::get(txn, set.network).map(|session| session.0) < Some(set.session.0) {
_internal_db::RemoveParticipant::send(txn, set, &participant);
_internal_db::RemoveParticipant::send(txn, set, &u16::from(participant));
}
}
pub(crate) fn try_recv(txn: &mut impl DbTxn, set: ExternalValidatorSet) -> Option<Participant> {
_internal_db::RemoveParticipant::try_recv(txn, set)
.map(|i| Participant::new(i).expect("sent invalid participant index for removal"))
}
}

View File

@@ -12,10 +12,8 @@ use frost_schnorrkel::{
use serai_db::{DbTxn, Db as DbTrait};
use serai_client::{
primitives::SeraiAddress,
validator_sets::primitives::{ExternalValidatorSet, musig_context, set_keys_message},
};
#[rustfmt::skip]
use serai_client_serai::abi::primitives::{validator_sets::ExternalValidatorSet, address::SeraiAddress};
use serai_task::{DoesNotError, ContinuallyRan};
@@ -160,7 +158,7 @@ impl<CD: DbTrait, TD: DbTrait> ConfirmDkgTask<CD, TD> {
let (machine, preprocess) = AlgorithmMachine::new(
schnorrkel(),
// We use a 1-of-1 Musig here as we don't know who will actually be in this Musig yet
musig(musig_context(set.into()), key, &[public_key]).unwrap(),
musig(ExternalValidatorSet::musig_context(&set), key, &[public_key]).unwrap(),
)
.preprocess(&mut OsRng);
// We take the preprocess so we can use it in a distinct machine with the actual Musig
@@ -260,9 +258,12 @@ impl<CD: DbTrait, TD: DbTrait> ContinuallyRan for ConfirmDkgTask<CD, TD> {
})
.collect::<Vec<_>>();
let keys =
musig(musig_context(self.set.set.into()), self.key.clone(), &musig_public_keys)
.unwrap();
let keys = musig(
ExternalValidatorSet::musig_context(&self.set.set),
self.key.clone(),
&musig_public_keys,
)
.unwrap();
// Rebuild the machine
let (machine, preprocess_from_cache) =
@@ -296,9 +297,10 @@ impl<CD: DbTrait, TD: DbTrait> ContinuallyRan for ConfirmDkgTask<CD, TD> {
};
// Calculate our share
let (machine, share) = match handle_frost_error(
machine.sign(preprocesses, &set_keys_message(&self.set.set, &key_pair)),
) {
let (machine, share) = match handle_frost_error(machine.sign(
preprocesses,
&ExternalValidatorSet::set_keys_message(&self.set.set, &key_pair),
)) {
Ok((machine, share)) => (machine, share),
// This yields the *musig participant index*
Err(participant) => {

View File

@@ -14,9 +14,14 @@ use borsh::BorshDeserialize;
use tokio::sync::mpsc;
use serai_client::{
primitives::{ExternalNetworkId, PublicKey, SeraiAddress, Signature},
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
use serai_client_serai::{
abi::primitives::{
BlockHash,
crypto::{Public, Signature, ExternalKey, KeyPair},
network_id::ExternalNetworkId,
validator_sets::ExternalValidatorSet,
address::SeraiAddress,
},
Serai,
};
use message_queue::{Service, client::MessageQueue};
@@ -61,9 +66,7 @@ async fn serai() -> Arc<Serai> {
let Ok(serai) = Serai::new(format!(
"http://{}:9944",
serai_env::var("SERAI_HOSTNAME").expect("Serai hostname wasn't provided")
))
.await
else {
)) else {
log::error!("couldn't connect to the Serai node");
tokio::time::sleep(delay).await;
delay = (delay + SERAI_CONNECTION_DELAY).min(MAX_SERAI_CONNECTION_DELAY);
@@ -213,10 +216,12 @@ async fn handle_network(
&mut txn,
ExternalValidatorSet { network, session },
&KeyPair(
PublicKey::from_raw(substrate_key),
network_key
.try_into()
.expect("generated a network key which exceeds the maximum key length"),
Public(substrate_key),
ExternalKey(
network_key
.try_into()
.expect("generated a network key which exceeds the maximum key length"),
),
),
);
}
@@ -284,12 +289,13 @@ async fn handle_network(
&mut txn,
ExternalValidatorSet { network, session },
slash_report,
Signature::from(signature),
Signature(signature),
);
}
},
messages::ProcessorMessage::Substrate(msg) => match msg {
messages::substrate::ProcessorMessage::SubstrateBlockAck { block, plans } => {
let block = BlockHash(block);
let mut by_session = HashMap::new();
for plan in plans {
by_session
@@ -481,7 +487,7 @@ async fn main() {
);
// Handle each of the networks
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
tokio::spawn(handle_network(db.clone(), message_queue.clone(), serai.clone(), network));
}

View File

@@ -10,7 +10,10 @@ use tokio::sync::mpsc;
use serai_db::{DbTxn, Db as DbTrait};
use serai_client::validator_sets::primitives::{Session, ExternalValidatorSet};
use serai_client_serai::abi::primitives::{
network_id::ExternalNetworkId,
validator_sets::{Session, ExternalValidatorSet},
};
use message_queue::{Service, Metadata, client::MessageQueue};
use tributary_sdk::Tributary;
@@ -39,7 +42,7 @@ impl<P: P2p> ContinuallyRan for SubstrateTask<P> {
let mut made_progress = false;
// Handle the Canonical events
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
loop {
let mut txn = self.db.txn();
let Some(msg) = serai_coordinator_substrate::Canonical::try_recv(&mut txn, network)

View File

@@ -11,7 +11,7 @@ use tokio::sync::mpsc;
use serai_db::{Get, DbTxn, Db as DbTrait, create_db, db_channel};
use serai_client::validator_sets::primitives::ExternalValidatorSet;
use serai_client_serai::abi::primitives::validator_sets::ExternalValidatorSet;
use tributary_sdk::{TransactionKind, TransactionError, ProvidedError, TransactionTrait, Tributary};

View File

@@ -24,7 +24,7 @@ borsh = { version = "1", default-features = false, features = ["std", "derive",
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std"] }
serai-client = { path = "../../substrate/client", version = "0.1", default-features = false, features = ["serai"] }
serai-client-serai = { path = "../../substrate/client/serai", default-features = false }
log = { version = "0.4", default-features = false, features = ["std"] }

View File

@@ -3,7 +3,13 @@ use std::sync::Arc;
use futures::stream::{StreamExt, FuturesOrdered};
use serai_client::{validator_sets::primitives::ExternalValidatorSet, Serai};
use serai_client_serai::{
abi::{
self,
primitives::{network_id::ExternalNetworkId, validator_sets::ExternalValidatorSet},
},
Serai,
};
use messages::substrate::{InInstructionResult, ExecutedBatch, CoordinatorMessage};
@@ -15,6 +21,7 @@ use serai_cosign::Cosigning;
create_db!(
CoordinatorSubstrateCanonical {
NextBlock: () -> u64,
LastIndexedBatchId: (network: ExternalNetworkId) -> u32,
}
);
@@ -45,10 +52,10 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
// These are all the events which generate canonical messages
struct CanonicalEvents {
time: u64,
key_gen_events: Vec<serai_client::validator_sets::ValidatorSetsEvent>,
set_retired_events: Vec<serai_client::validator_sets::ValidatorSetsEvent>,
batch_events: Vec<serai_client::in_instructions::InInstructionsEvent>,
burn_events: Vec<serai_client::coins::CoinsEvent>,
set_keys_events: Vec<abi::validator_sets::Event>,
slash_report_events: Vec<abi::validator_sets::Event>,
batch_events: Vec<abi::in_instructions::Event>,
burn_events: Vec<abi::coins::Event>,
}
// For a cosigned block, fetch all relevant events
@@ -66,16 +73,16 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
}
Err(serai_cosign::Faulted) => return Err("cosigning process faulted".to_string()),
};
let temporal_serai = serai.as_of(block_hash);
let temporal_serai = serai.as_of(block_hash).await.map_err(|e| format!("{e}"))?;
let temporal_serai_validators = temporal_serai.validator_sets();
let temporal_serai_instructions = temporal_serai.in_instructions();
let temporal_serai_coins = temporal_serai.coins();
let (block, key_gen_events, set_retired_events, batch_events, burn_events) =
let (block, set_keys_events, slash_report_events, batch_events, burn_events) =
tokio::try_join!(
serai.block(block_hash),
temporal_serai_validators.key_gen_events(),
temporal_serai_validators.set_retired_events(),
temporal_serai_validators.set_keys_events(),
temporal_serai_validators.slash_report_events(),
temporal_serai_instructions.batch_events(),
temporal_serai_coins.burn_with_instruction_events(),
)
@@ -84,22 +91,14 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
Err(format!("Serai node didn't have cosigned block #{block_number}"))?
};
let time = if block_number == 0 {
block.time().unwrap_or(0)
} else {
// Serai's block time is in milliseconds
block
.time()
.ok_or_else(|| "non-genesis Serai block didn't have a time".to_string())? /
1000
};
// We use time in seconds, not milliseconds, here
let time = block.header.unix_time_in_millis() / 1000;
Ok((
block_number,
CanonicalEvents {
time,
key_gen_events,
set_retired_events,
set_keys_events,
slash_report_events,
batch_events,
burn_events,
},
@@ -131,10 +130,9 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
let mut txn = self.db.txn();
for key_gen in block.key_gen_events {
let serai_client::validator_sets::ValidatorSetsEvent::KeyGen { set, key_pair } = &key_gen
else {
panic!("KeyGen event wasn't a KeyGen event: {key_gen:?}");
for set_keys in block.set_keys_events {
let abi::validator_sets::Event::SetKeys { set, key_pair } = &set_keys else {
panic!("`SetKeys` event wasn't a `SetKeys` event: {set_keys:?}");
};
crate::Canonical::send(
&mut txn,
@@ -147,12 +145,10 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
);
}
for set_retired in block.set_retired_events {
let serai_client::validator_sets::ValidatorSetsEvent::SetRetired { set } = &set_retired
else {
panic!("SetRetired event wasn't a SetRetired event: {set_retired:?}");
for slash_report in block.slash_report_events {
let abi::validator_sets::Event::SlashReport { set } = &slash_report else {
panic!("`SlashReport` event wasn't a `SlashReport` event: {slash_report:?}");
};
let Ok(set) = ExternalValidatorSet::try_from(*set) else { continue };
crate::Canonical::send(
&mut txn,
set.network,
@@ -160,10 +156,12 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
);
}
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
let mut batch = None;
for this_batch in &block.batch_events {
let serai_client::in_instructions::InInstructionsEvent::Batch {
// Only irrefutable as this is the only member of the enum at this time
#[expect(irrefutable_let_patterns)]
let abi::in_instructions::Event::Batch {
network: batch_network,
publishing_session,
id,
@@ -194,14 +192,19 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
})
.collect(),
});
if LastIndexedBatchId::get(&txn, network) != id.checked_sub(1) {
panic!(
"next batch from Serai's ID was not an increment of the last indexed batch's ID"
);
}
LastIndexedBatchId::set(&mut txn, network, id);
}
}
let mut burns = vec![];
for burn in &block.burn_events {
let serai_client::coins::CoinsEvent::BurnWithInstruction { from: _, instruction } =
&burn
else {
let abi::coins::Event::BurnWithInstruction { from: _, instruction } = &burn else {
panic!("BurnWithInstruction event wasn't a BurnWithInstruction event: {burn:?}");
};
if instruction.balance.coin.network() == network {
@@ -223,3 +226,7 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
}
}
}
pub(crate) fn last_indexed_batch_id(txn: &impl DbTxn, network: ExternalNetworkId) -> Option<u32> {
LastIndexedBatchId::get(txn, network)
}

View File

@@ -3,9 +3,14 @@ use std::sync::Arc;
use futures::stream::{StreamExt, FuturesOrdered};
use serai_client::{
primitives::{SeraiAddress, EmbeddedEllipticCurve},
validator_sets::primitives::{MAX_KEY_SHARES_PER_SET, ExternalValidatorSet},
use serai_client_serai::{
abi::primitives::{
BlockHash,
crypto::EmbeddedEllipticCurveKeys,
network_id::ExternalNetworkId,
validator_sets::{KeyShares, ExternalValidatorSet},
address::SeraiAddress,
},
Serai,
};
@@ -49,10 +54,10 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
// These are all the events which generate canonical messages
struct EphemeralEvents {
block_hash: [u8; 32],
block_hash: BlockHash,
time: u64,
new_set_events: Vec<serai_client::validator_sets::ValidatorSetsEvent>,
accepted_handover_events: Vec<serai_client::validator_sets::ValidatorSetsEvent>,
set_decided_events: Vec<serai_client_serai::abi::validator_sets::Event>,
accepted_handover_events: Vec<serai_client_serai::abi::validator_sets::Event>,
}
// For a cosigned block, fetch all relevant events
@@ -71,11 +76,11 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
Err(serai_cosign::Faulted) => return Err("cosigning process faulted".to_string()),
};
let temporal_serai = serai.as_of(block_hash);
let temporal_serai = serai.as_of(block_hash).await.map_err(|e| format!("{e}"))?;
let temporal_serai_validators = temporal_serai.validator_sets();
let (block, new_set_events, accepted_handover_events) = tokio::try_join!(
let (block, set_decided_events, accepted_handover_events) = tokio::try_join!(
serai.block(block_hash),
temporal_serai_validators.new_set_events(),
temporal_serai_validators.set_decided_events(),
temporal_serai_validators.accepted_handover_events(),
)
.map_err(|e| format!("{e:?}"))?;
@@ -83,19 +88,11 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
Err(format!("Serai node didn't have cosigned block #{block_number}"))?
};
let time = if block_number == 0 {
block.time().unwrap_or(0)
} else {
// Serai's block time is in milliseconds
block
.time()
.ok_or_else(|| "non-genesis Serai block didn't have a time".to_string())? /
1000
};
// We use time in seconds, not milliseconds, here
let time = block.header.unix_time_in_millis() / 1000;
Ok((
block_number,
EphemeralEvents { block_hash, time, new_set_events, accepted_handover_events },
EphemeralEvents { block_hash, time, set_decided_events, accepted_handover_events },
))
}
}
@@ -126,48 +123,40 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
let mut txn = self.db.txn();
for new_set in block.new_set_events {
let serai_client::validator_sets::ValidatorSetsEvent::NewSet { set } = &new_set else {
panic!("NewSet event wasn't a NewSet event: {new_set:?}");
for set_decided in block.set_decided_events {
let serai_client_serai::abi::validator_sets::Event::SetDecided { set, validators } =
&set_decided
else {
panic!("`SetDecided` event wasn't a `SetDecided` event: {set_decided:?}");
};
// We only coordinate over external networks
let Ok(set) = ExternalValidatorSet::try_from(*set) else { continue };
let serai = self.serai.as_of(block.block_hash);
let serai = self.serai.as_of(block.block_hash).await.map_err(|e| format!("{e}"))?;
let serai = serai.validator_sets();
let Some(validators) =
serai.participants(set.network.into()).await.map_err(|e| format!("{e:?}"))?
else {
Err(format!(
"block #{block_number} declared a new set but didn't have the participants"
))?
};
let validators = validators
.into_iter()
.map(|(validator, weight)| (SeraiAddress::from(validator), weight))
.collect::<Vec<_>>();
let validators =
validators.iter().map(|(validator, weight)| (*validator, weight)).collect::<Vec<_>>();
let in_set = validators.iter().any(|(validator, _)| *validator == self.validator);
if in_set {
if u16::try_from(validators.len()).is_err() {
Err("more than u16::MAX validators sent")?;
}
let Ok(validators) = validators
let validators = validators
.into_iter()
.map(|(validator, weight)| u16::try_from(weight).map(|weight| (validator, weight)))
.collect::<Result<Vec<_>, _>>()
else {
Err("validator's weight exceeded u16::MAX".to_string())?
};
.map(|(validator, weight)| (validator, weight.0))
.collect::<Vec<_>>();
// Do the summation in u32 so we don't risk a u16 overflow
let total_weight = validators.iter().map(|(_, weight)| u32::from(*weight)).sum::<u32>();
if total_weight > u32::from(MAX_KEY_SHARES_PER_SET) {
if total_weight > u32::from(KeyShares::MAX_PER_SET) {
Err(format!(
"{set:?} has {total_weight} key shares when the max is {MAX_KEY_SHARES_PER_SET}"
"{set:?} has {total_weight} key shares when the max is {}",
KeyShares::MAX_PER_SET
))?;
}
let total_weight = u16::try_from(total_weight).unwrap();
let total_weight = u16::try_from(total_weight)
.expect("value smaller than `u16` constant but doesn't fit in `u16`");
// Fetch all of the validators' embedded elliptic curve keys
let mut embedded_elliptic_curve_keys = FuturesOrdered::new();
@@ -175,52 +164,51 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
let validator = *validator;
// try_join doesn't return a future so we need to wrap it in this additional async
// block
embedded_elliptic_curve_keys.push_back(async move {
tokio::try_join!(
// One future to fetch the substrate embedded key
serai.embedded_elliptic_curve_key(
validator.into(),
EmbeddedEllipticCurve::Embedwards25519
),
// One future to fetch the external embedded key, if there is a distinct curve
async {
// `embedded_elliptic_curves` is documented to have the second entry be the
// network-specific curve (if it exists and is distinct from Embedwards25519)
if let Some(curve) = set.network.embedded_elliptic_curves().get(1) {
serai.embedded_elliptic_curve_key(validator.into(), *curve).await.map(Some)
} else {
Ok(None)
}
embedded_elliptic_curve_keys.push_back({
let serai = serai.clone();
async move {
match serai.embedded_elliptic_curve_keys(validator, set.network).await {
Ok(Some(keys)) => Ok(Some((
validator,
match keys {
EmbeddedEllipticCurveKeys::Bitcoin(substrate, external) => {
assert_eq!(set.network, ExternalNetworkId::Bitcoin);
(substrate, external.as_slice().to_vec())
}
EmbeddedEllipticCurveKeys::Ethereum(substrate, external) => {
assert_eq!(set.network, ExternalNetworkId::Ethereum);
(substrate, external.as_slice().to_vec())
}
EmbeddedEllipticCurveKeys::Monero(substrate) => {
assert_eq!(set.network, ExternalNetworkId::Monero);
(substrate, substrate.as_slice().to_vec())
}
},
))),
Ok(None) => Ok(None),
Err(e) => Err(e),
}
)
.map(|(substrate_embedded_key, external_embedded_key)| {
(validator, substrate_embedded_key, external_embedded_key)
})
}
});
}
let mut evrf_public_keys = Vec::with_capacity(usize::from(total_weight));
for (validator, weight) in &validators {
let (future_validator, substrate_embedded_key, external_embedded_key) =
embedded_elliptic_curve_keys.next().await.unwrap().map_err(|e| format!("{e:?}"))?;
let Some((future_validator, (substrate_embedded_key, external_embedded_key))) =
embedded_elliptic_curve_keys.next().await.unwrap().map_err(|e| format!("{e:?}"))?
else {
Err("`SetDecided` with validator missing an embedded key".to_string())?
};
assert_eq!(*validator, future_validator);
let external_embedded_key =
external_embedded_key.unwrap_or(substrate_embedded_key.clone());
match (substrate_embedded_key, external_embedded_key) {
(Some(substrate_embedded_key), Some(external_embedded_key)) => {
let substrate_embedded_key = <[u8; 32]>::try_from(substrate_embedded_key)
.map_err(|_| "Embedwards25519 key wasn't 32 bytes".to_string())?;
for _ in 0 .. *weight {
evrf_public_keys.push((substrate_embedded_key, external_embedded_key.clone()));
}
}
_ => Err("NewSet with validator missing an embedded key".to_string())?,
for _ in 0 .. *weight {
evrf_public_keys.push((substrate_embedded_key, external_embedded_key.clone()));
}
}
let mut new_set = NewSetInformation {
set,
serai_block: block.block_hash,
serai_block: block.block_hash.0,
declaration_time: block.time,
// TODO: This should be inlined into the Processor's key gen code
// It's legacy from when we removed participants from the key gen
@@ -238,7 +226,7 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
}
for accepted_handover in block.accepted_handover_events {
let serai_client::validator_sets::ValidatorSetsEvent::AcceptedHandover { set } =
let serai_client_serai::abi::validator_sets::Event::AcceptedHandover { set } =
&accepted_handover
else {
panic!("AcceptedHandover event wasn't a AcceptedHandover event: {accepted_handover:?}");

View File

@@ -8,10 +8,14 @@ use borsh::{BorshSerialize, BorshDeserialize};
use dkg::Participant;
use serai_client::{
primitives::{ExternalNetworkId, SeraiAddress, Signature},
validator_sets::primitives::{Session, ExternalValidatorSet, KeyPair, SlashReport},
in_instructions::primitives::SignedBatch,
use serai_client_serai::abi::{
primitives::{
network_id::ExternalNetworkId,
validator_sets::{Session, ExternalValidatorSet, SlashReport},
crypto::{Signature, KeyPair},
address::SeraiAddress,
instructions::SignedBatch,
},
Transaction,
};
@@ -19,6 +23,7 @@ use serai_db::*;
mod canonical;
pub use canonical::CanonicalEventStream;
use canonical::last_indexed_batch_id;
mod ephemeral;
pub use ephemeral::EphemeralEventStream;
@@ -37,7 +42,7 @@ pub struct NewSetInformation {
pub set: ExternalValidatorSet,
/// The Serai block which declared it.
pub serai_block: [u8; 32],
/// The time of the block which declared it, in seconds.
/// The time of the block which declared it, in seconds since the epoch.
pub declaration_time: u64,
/// The threshold to use.
pub threshold: u16,
@@ -96,9 +101,9 @@ mod _public_db {
create_db!(
CoordinatorSubstrate {
// Keys to set on the Serai network
Keys: (network: ExternalNetworkId) -> (Session, Vec<u8>),
Keys: (network: ExternalNetworkId) -> (Session, Transaction),
// Slash reports to publish onto the Serai network
SlashReports: (network: ExternalNetworkId) -> (Session, Vec<u8>),
SlashReports: (network: ExternalNetworkId) -> (Session, Transaction),
}
);
}
@@ -171,7 +176,7 @@ impl Keys {
}
}
let tx = serai_client::validator_sets::SeraiValidatorSets::set_keys(
let tx = serai_client_serai::ValidatorSets::set_keys(
set.network,
key_pair,
signature_participants,
@@ -192,7 +197,7 @@ pub struct SignedBatches;
impl SignedBatches {
/// Send a `SignedBatch` to publish onto Serai.
pub fn send(txn: &mut impl DbTxn, batch: &SignedBatch) {
_public_db::SignedBatches::send(txn, batch.batch.network, batch);
_public_db::SignedBatches::send(txn, batch.batch.network(), batch);
}
pub(crate) fn try_recv(txn: &mut impl DbTxn, network: ExternalNetworkId) -> Option<SignedBatch> {
_public_db::SignedBatches::try_recv(txn, network)
@@ -219,11 +224,8 @@ impl SlashReports {
}
}
let tx = serai_client::validator_sets::SeraiValidatorSets::report_slashes(
set.network,
slash_report,
signature,
);
let tx =
serai_client_serai::ValidatorSets::report_slashes(set.network, slash_report, signature);
_public_db::SlashReports::set(txn, set.network, &(set.session, tx));
}
pub(crate) fn take(

View File

@@ -1,8 +1,10 @@
use core::future::Future;
use std::sync::Arc;
#[rustfmt::skip]
use serai_client::{primitives::ExternalNetworkId, in_instructions::primitives::SignedBatch, SeraiError, Serai};
use serai_client_serai::{
abi::primitives::{network_id::ExternalNetworkId, instructions::SignedBatch},
RpcError, Serai,
};
use serai_db::{Get, DbTxn, Db, create_db};
use serai_task::ContinuallyRan;
@@ -31,7 +33,7 @@ impl<D: Db> PublishBatchTask<D> {
}
impl<D: Db> ContinuallyRan for PublishBatchTask<D> {
type Error = SeraiError;
type Error = RpcError;
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, Self::Error>> {
async move {
@@ -43,8 +45,8 @@ impl<D: Db> ContinuallyRan for PublishBatchTask<D> {
};
// If this is a Batch not yet published, save it into our unordered mapping
if LastPublishedBatch::get(&txn, self.network) < Some(batch.batch.id) {
BatchesToPublish::set(&mut txn, self.network, batch.batch.id, &batch);
if LastPublishedBatch::get(&txn, self.network) < Some(batch.batch.id()) {
BatchesToPublish::set(&mut txn, self.network, batch.batch.id(), &batch);
}
txn.commit();
@@ -52,12 +54,8 @@ impl<D: Db> ContinuallyRan for PublishBatchTask<D> {
// Synchronize our last published batch with the Serai network's
let next_to_publish = {
// This uses the latest finalized block, not the latest cosigned block, which should be
// fine as in the worst case, the only impact is no longer attempting TX publication
let serai = self.serai.as_of_latest_finalized_block().await?;
let last_batch = serai.in_instructions().last_batch_for_network(self.network).await?;
let mut txn = self.db.txn();
let last_batch = crate::last_indexed_batch_id(&txn, self.network);
let mut our_last_batch = LastPublishedBatch::get(&txn, self.network);
while our_last_batch < last_batch {
let next_batch = our_last_batch.map(|batch| batch + 1).unwrap_or(0);
@@ -68,6 +66,7 @@ impl<D: Db> ContinuallyRan for PublishBatchTask<D> {
if let Some(last_batch) = our_last_batch {
LastPublishedBatch::set(&mut txn, self.network, &last_batch);
}
txn.commit();
last_batch.map(|batch| batch + 1).unwrap_or(0)
};
@@ -75,7 +74,7 @@ impl<D: Db> ContinuallyRan for PublishBatchTask<D> {
if let Some(batch) = BatchesToPublish::get(&self.db, self.network, next_to_publish) {
self
.serai
.publish(&serai_client::in_instructions::SeraiInInstructions::execute_batch(batch))
.publish_transaction(&serai_client_serai::InInstructions::execute_batch(batch))
.await?;
true
} else {

View File

@@ -3,7 +3,10 @@ use std::sync::Arc;
use serai_db::{DbTxn, Db};
use serai_client::{primitives::ExternalNetworkId, validator_sets::primitives::Session, Serai};
use serai_client_serai::{
abi::primitives::{network_id::ExternalNetworkId, validator_sets::Session},
Serai,
};
use serai_task::ContinuallyRan;
@@ -36,7 +39,8 @@ impl<D: Db> PublishSlashReportTask<D> {
let serai = self.serai.as_of_latest_finalized_block().await.map_err(|e| format!("{e:?}"))?;
let serai = serai.validator_sets();
let session_after_slash_report = Session(session.0 + 1);
let current_session = serai.session(network.into()).await.map_err(|e| format!("{e:?}"))?;
let current_session =
serai.current_session(network.into()).await.map_err(|e| format!("{e:?}"))?;
let current_session = current_session.map(|session| session.0);
// Only attempt to publish the slash report for session #n while session #n+1 is still
// active
@@ -55,14 +59,13 @@ impl<D: Db> PublishSlashReportTask<D> {
}
// If this session which should publish a slash report already has, move on
let key_pending_slash_report =
serai.key_pending_slash_report(network).await.map_err(|e| format!("{e:?}"))?;
if key_pending_slash_report.is_none() {
if !serai.pending_slash_report(network).await.map_err(|e| format!("{e:?}"))? {
txn.commit();
return Ok(false);
};
match self.serai.publish(&slash_report).await {
// Since this slash report is still pending, publish it
match self.serai.publish_transaction(&slash_report).await {
Ok(()) => {
txn.commit();
Ok(true)
@@ -84,7 +87,7 @@ impl<D: Db> ContinuallyRan for PublishSlashReportTask<D> {
async move {
let mut made_progress = false;
let mut error = None;
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
let network_res = self.publish(network).await;
// We made progress if any network successfully published their slash report
made_progress |= network_res == Ok(true);

View File

@@ -3,7 +3,10 @@ use std::sync::Arc;
use serai_db::{DbTxn, Db};
use serai_client::{validator_sets::primitives::ExternalValidatorSet, Serai};
use serai_client_serai::{
abi::primitives::{network_id::ExternalNetworkId, validator_sets::ExternalValidatorSet},
Serai,
};
use serai_task::ContinuallyRan;
@@ -28,7 +31,7 @@ impl<D: Db> ContinuallyRan for SetKeysTask<D> {
fn run_iteration(&mut self) -> impl Send + Future<Output = Result<bool, Self::Error>> {
async move {
let mut made_progress = false;
for network in serai_client::primitives::EXTERNAL_NETWORKS {
for network in ExternalNetworkId::all() {
let mut txn = self.db.txn();
let Some((session, keys)) = Keys::take(&mut txn, network) else {
// No keys to set
@@ -40,7 +43,8 @@ impl<D: Db> ContinuallyRan for SetKeysTask<D> {
let serai =
self.serai.as_of_latest_finalized_block().await.map_err(|e| format!("{e:?}"))?;
let serai = serai.validator_sets();
let current_session = serai.session(network.into()).await.map_err(|e| format!("{e:?}"))?;
let current_session =
serai.current_session(network.into()).await.map_err(|e| format!("{e:?}"))?;
let current_session = current_session.map(|session| session.0);
// Only attempt to set these keys if this isn't a retired session
if Some(session.0) < current_session {
@@ -67,7 +71,7 @@ impl<D: Db> ContinuallyRan for SetKeysTask<D> {
continue;
};
match self.serai.publish(&keys).await {
match self.serai.publish_transaction(&keys).await {
Ok(()) => {
txn.commit();
made_progress = true;

View File

@@ -36,7 +36,7 @@ serai-task = { path = "../../common/task", version = "0.1" }
tributary-sdk = { path = "../tributary-sdk" }
serai-cosign = { path = "../cosign" }
serai-cosign-types = { path = "../cosign/types" }
serai-coordinator-substrate = { path = "../substrate" }
messages = { package = "serai-processor-messages", path = "../../processor/messages" }

View File

@@ -1,16 +1,14 @@
#![expect(clippy::cast_possible_truncation)]
use std::collections::HashMap;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_primitives::{address::SeraiAddress, validator_sets::primitives::ExternalValidatorSet};
use serai_primitives::{BlockHash, validator_sets::ExternalValidatorSet, address::SeraiAddress};
use messages::sign::{VariantSignId, SignId};
use serai_db::*;
use serai_cosign::CosignIntent;
use serai_cosign_types::CosignIntent;
use crate::transaction::SigningProtocolRound;
@@ -124,7 +122,7 @@ impl Topic {
Topic::DkgConfirmation { attempt, round: _ } => Some({
let id = {
let mut id = [0; 32];
let encoded_set = borsh::to_vec(set).unwrap();
let encoded_set = borsh::to_vec(&set).unwrap();
id[.. encoded_set.len()].copy_from_slice(&encoded_set);
VariantSignId::Batch(id)
};
@@ -234,18 +232,18 @@ create_db!(
SlashPoints: (set: ExternalValidatorSet, validator: SeraiAddress) -> u32,
// The cosign intent for a Substrate block
CosignIntents: (set: ExternalValidatorSet, substrate_block_hash: [u8; 32]) -> CosignIntent,
CosignIntents: (set: ExternalValidatorSet, substrate_block_hash: BlockHash) -> CosignIntent,
// The latest Substrate block to cosign.
LatestSubstrateBlockToCosign: (set: ExternalValidatorSet) -> [u8; 32],
LatestSubstrateBlockToCosign: (set: ExternalValidatorSet) -> BlockHash,
// The hash of the block we're actively cosigning.
ActivelyCosigning: (set: ExternalValidatorSet) -> [u8; 32],
ActivelyCosigning: (set: ExternalValidatorSet) -> BlockHash,
// If this block has already been cosigned.
Cosigned: (set: ExternalValidatorSet, substrate_block_hash: [u8; 32]) -> (),
Cosigned: (set: ExternalValidatorSet, substrate_block_hash: BlockHash) -> (),
// The plans to recognize upon a `Transaction::SubstrateBlock` being included on-chain.
SubstrateBlockPlans: (
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32]
substrate_block_hash: BlockHash
) -> Vec<[u8; 32]>,
// The weight accumulated for a topic.
@@ -293,26 +291,26 @@ impl TributaryDb {
pub(crate) fn latest_substrate_block_to_cosign(
getter: &impl Get,
set: ExternalValidatorSet,
) -> Option<[u8; 32]> {
) -> Option<BlockHash> {
LatestSubstrateBlockToCosign::get(getter, set)
}
pub(crate) fn set_latest_substrate_block_to_cosign(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
) {
LatestSubstrateBlockToCosign::set(txn, set, &substrate_block_hash);
}
pub(crate) fn actively_cosigning(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
) -> Option<[u8; 32]> {
) -> Option<BlockHash> {
ActivelyCosigning::get(txn, set)
}
pub(crate) fn start_cosigning(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
substrate_block_number: u64,
) {
assert!(
@@ -337,14 +335,14 @@ impl TributaryDb {
pub(crate) fn mark_cosigned(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
) {
Cosigned::set(txn, set, substrate_block_hash, &());
}
pub(crate) fn cosigned(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
) -> bool {
Cosigned::get(txn, set, substrate_block_hash).is_some()
}

View File

@@ -9,8 +9,9 @@ use ciphersuite::group::GroupEncoding;
use dkg::Participant;
use serai_primitives::{
address::SeraiAddress,
BlockHash,
validator_sets::{ExternalValidatorSet, Slash},
address::SeraiAddress,
};
use serai_db::*;
@@ -25,7 +26,7 @@ use tributary_sdk::{
Transaction as TributaryTransaction, Block, TributaryReader, P2p,
};
use serai_cosign::CosignIntent;
use serai_cosign_types::CosignIntent;
use serai_coordinator_substrate::NewSetInformation;
use messages::sign::{VariantSignId, SignId};
@@ -79,7 +80,7 @@ impl CosignIntents {
fn take(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
) -> Option<CosignIntent> {
db::CosignIntents::take(txn, set, substrate_block_hash)
}
@@ -113,7 +114,7 @@ impl SubstrateBlockPlans {
pub fn set(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
plans: &Vec<[u8; 32]>,
) {
db::SubstrateBlockPlans::set(txn, set, substrate_block_hash, plans);
@@ -121,7 +122,7 @@ impl SubstrateBlockPlans {
fn take(
txn: &mut impl DbTxn,
set: ExternalValidatorSet,
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
) -> Option<Vec<[u8; 32]>> {
db::SubstrateBlockPlans::take(txn, set, substrate_block_hash)
}

View File

@@ -14,7 +14,7 @@ use schnorr::SchnorrSignature;
use borsh::{BorshSerialize, BorshDeserialize};
use serai_primitives::{addess::SeraiAddress, validator_sets::MAX_KEY_SHARES_PER_SET};
use serai_primitives::{BlockHash, validator_sets::KeyShares, address::SeraiAddress};
use messages::sign::VariantSignId;
@@ -137,7 +137,7 @@ pub enum Transaction {
/// be the one selected to be cosigned.
Cosign {
/// The hash of the Substrate block to cosign
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
},
/// Note an intended-to-be-cosigned Substrate block as cosigned
@@ -175,7 +175,7 @@ pub enum Transaction {
/// cosigning the block in question, it'd be safe to provide this and move on to the next cosign.
Cosigned {
/// The hash of the Substrate block which was cosigned
substrate_block_hash: [u8; 32],
substrate_block_hash: BlockHash,
},
/// Acknowledge a Substrate block
@@ -186,7 +186,7 @@ pub enum Transaction {
/// resulting from its handling.
SubstrateBlock {
/// The hash of the Substrate block
hash: [u8; 32],
hash: BlockHash,
},
/// Acknowledge a Batch
@@ -250,11 +250,11 @@ impl TransactionTrait for Transaction {
signed.to_tributary_signed(0),
),
Transaction::DkgConfirmationPreprocess { attempt, signed, .. } => TransactionKind::Signed(
borsh::to_vec(b"DkgConfirmation".as_slice(), attempt).unwrap(),
borsh::to_vec(&(b"DkgConfirmation".as_slice(), attempt)).unwrap(),
signed.to_tributary_signed(0),
),
Transaction::DkgConfirmationShare { attempt, signed, .. } => TransactionKind::Signed(
borsh::to_vec(b"DkgConfirmation".as_slice(), attempt).unwrap(),
borsh::to_vec(&(b"DkgConfirmation".as_slice(), attempt)).unwrap(),
signed.to_tributary_signed(1),
),
@@ -264,7 +264,7 @@ impl TransactionTrait for Transaction {
Transaction::Batch { .. } => TransactionKind::Provided("Batch"),
Transaction::Sign { id, attempt, round, signed, .. } => TransactionKind::Signed(
borsh::to_vec(b"Sign".as_slice(), id, attempt).unwrap(),
borsh::to_vec(&(b"Sign".as_slice(), id, attempt)).unwrap(),
signed.to_tributary_signed(round.nonce()),
),
@@ -303,14 +303,14 @@ impl TransactionTrait for Transaction {
Transaction::Batch { .. } => {}
Transaction::Sign { data, .. } => {
if data.len() > usize::from(MAX_KEY_SHARES_PER_SET) {
if data.len() > usize::from(KeyShares::MAX_PER_SET) {
Err(TransactionError::InvalidContent)?
}
// TODO: MAX_SIGN_LEN
}
Transaction::SlashReport { slash_points, .. } => {
if slash_points.len() > usize::from(MAX_KEY_SHARES_PER_SET) {
if slash_points.len() > usize::from(KeyShares::MAX_PER_SET) {
Err(TransactionError::InvalidContent)?
}
}

View File

@@ -83,6 +83,12 @@ impl Header {
Header::V1(HeaderV1 { builds_upon, .. }) => *builds_upon,
}
}
/// Get the UNIX time, in milliseconds since the epoch, for when this block was proposed.
pub fn unix_time_in_millis(&self) -> u64 {
match self {
Header::V1(HeaderV1 { unix_time_in_millis, .. }) => *unix_time_in_millis,
}
}
/// The commitment to the transactions within this block.
pub fn transactions_commitment(&self) -> UnbalancedMerkleTree {
match self {

View File

@@ -24,7 +24,9 @@ simple-request = { path = "../../../common/request", version = "0.3" }
hex = { version = "0.4", default-features = false, features = ["alloc"] }
borsh = { version = "1", default-features = false, features = ["std"] }
serai-abi = { path = "../../abi", version = "0.1" }
bitvec = { version = "1", default-features = false, features = ["alloc", "std"] }
serai-abi = { path = "../../abi", version = "0.1", default-features = false, features = ["std"] }
async-lock = "3"

View File

@@ -0,0 +1,52 @@
pub use serai_abi::{
primitives::instructions::SignedBatch,
in_instructions::{Call, Event},
UnsignedCall, Transaction,
};
use crate::{RpcError, TemporalSerai};
/// A `TemporalSerai` scoped to the in instructions module.
#[derive(Clone)]
pub struct InInstructions<'serai>(pub(super) &'serai TemporalSerai<'serai>);
impl<'serai> InInstructions<'serai> {
/// The events from the in instructions module.
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
Ok(
self
.0
.events_borrowed()
.await?
.as_ref()
.expect("`TemporalSerai::events` returned None")
.iter()
.flat_map(IntoIterator::into_iter)
.filter_map(|event| match event {
serai_abi::Event::InInstructions(event) => Some(event.clone()),
_ => None,
})
.collect(),
)
}
/// The `Batch` events from the in instructions module.
pub async fn batch_events(&self) -> Result<Vec<Event>, RpcError> {
Ok(
self
.events()
.await?
.into_iter()
.filter(|event| matches!(event, Event::Batch { .. }))
.collect(),
)
}
/// Create a transaction to execute a batch.
pub fn execute_batch(batch: SignedBatch) -> Transaction {
Transaction::Unsigned {
call: UnsignedCall::try_from(serai_abi::Call::from(Call::execute_batch { batch }))
.expect("`execute_batch` wasn't an unsigned call?"),
}
}
}

View File

@@ -25,6 +25,9 @@ pub use coins::Coins;
mod validator_sets;
pub use validator_sets::ValidatorSets;
mod in_instructions;
pub use in_instructions::InInstructions;
/// An error from the RPC.
#[derive(Debug, Error)]
pub enum RpcError {
@@ -58,8 +61,8 @@ pub struct Serai {
/// from this block will be cached within this. This allows future calls for events to be done
/// cheaply.
#[derive(Clone)]
pub struct TemporalSerai<'a> {
serai: &'a Serai,
pub struct TemporalSerai<'serai> {
serai: &'serai Serai,
block: BlockHash,
events: Arc<RwLock<Option<Vec<Vec<Event>>>>>,
}
@@ -176,7 +179,7 @@ impl Serai {
///
/// This will yield an error if the block chosen isn't finalized. This ensures, given an honest
/// node, that this scope will be available for the lifetime of this object.
pub async fn as_of<'a>(&'a self, block: BlockHash) -> Result<TemporalSerai<'a>, RpcError> {
pub async fn as_of(&self, block: BlockHash) -> Result<TemporalSerai<'_>, RpcError> {
if !self.finalized(block).await? {
Err(RpcError::NotFinalized)?;
}
@@ -184,10 +187,7 @@ impl Serai {
}
/// Scope this RPC client to the state as of the latest finalized block.
pub async fn as_of_latest_finalized_block<'a>(
&'a self,
block: BlockHash,
) -> Result<TemporalSerai<'a>, RpcError> {
pub async fn as_of_latest_finalized_block(&self) -> Result<TemporalSerai<'_>, RpcError> {
let block = self
.block_by_number(self.latest_finalized_block_number().await?)
.await?
@@ -215,7 +215,7 @@ impl Serai {
}
}
impl<'a> TemporalSerai<'a> {
impl<'serai> TemporalSerai<'serai> {
async fn call<ResponseValue: Default + JsonDeserialize>(
&self,
method: &str,
@@ -282,4 +282,9 @@ impl<'a> TemporalSerai<'a> {
pub fn validator_sets(&self) -> ValidatorSets<'_> {
ValidatorSets(self)
}
/// Scope to the in instructions module.
pub fn in_instructions(&self) -> InInstructions<'_> {
InInstructions(self)
}
}

View File

@@ -2,12 +2,14 @@ use borsh::BorshDeserialize;
pub use serai_abi::{
primitives::{
crypto::KeyPair,
crypto::{Signature, KeyPair, EmbeddedEllipticCurveKeys},
network_id::{ExternalNetworkId, NetworkId},
validator_sets::{Session, ExternalValidatorSet, ValidatorSet},
validator_sets::{Session, ExternalValidatorSet, ValidatorSet, SlashReport},
balance::Amount,
address::SeraiAddress,
},
validator_sets::Event,
validator_sets::{Call, Event},
UnsignedCall, Transaction,
};
use crate::{RpcError, TemporalSerai};
@@ -24,9 +26,9 @@ fn rpc_network(network: impl Into<NetworkId>) -> Result<&'static str, RpcError>
/// A `TemporalSerai` scoped to the validator sets module.
#[derive(Clone)]
pub struct ValidatorSets<'a>(pub(super) &'a TemporalSerai<'a>);
pub struct ValidatorSets<'serai>(pub(super) &'serai TemporalSerai<'serai>);
impl<'a> ValidatorSets<'a> {
impl<'serai> ValidatorSets<'serai> {
/// The events from the validator sets module.
pub async fn events(&self) -> Result<Vec<Event>, RpcError> {
Ok(
@@ -108,7 +110,7 @@ impl<'a> ValidatorSets<'a> {
)
}
/// The stake for the current validators for specified network.
/// The stake for the current validators for the specified network.
pub async fn current_stake(&self, network: NetworkId) -> Result<Option<Amount>, RpcError> {
Ok(
self
@@ -142,4 +144,38 @@ impl<'a> ValidatorSets<'a> {
.map(Some)
.map_err(|_| RpcError::InvalidNode("validator set's keys weren't a valid key pair".to_string()))
}
/// Create a transaction to set a validator set's keys.
pub fn set_keys(
network: ExternalNetworkId,
key_pair: KeyPair,
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
signature: Signature,
) -> Transaction {
Transaction::Unsigned {
call: UnsignedCall::try_from(serai_abi::Call::from(Call::set_keys {
network,
key_pair,
signature_participants,
signature,
}))
.expect("`set_keys` wasn't an unsigned call?"),
}
}
/// Create a transaction to report the slashes for a validator set.
pub fn report_slashes(
network: ExternalNetworkId,
slashes: SlashReport,
signature: Signature,
) -> Transaction {
Transaction::Unsigned {
call: UnsignedCall::try_from(serai_abi::Call::from(Call::report_slashes {
network,
slashes,
signature,
}))
.expect("`report_slashes` wasn't an unsigned call?"),
}
}
}

View File

@@ -48,7 +48,10 @@ pub(crate) fn module<
Err(e) => Err(e)?,
};
// Always return the protocol's bootnodes
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id);
let mut all_p2p_addresses = crate::chain_spec::bootnode_multiaddrs(id)
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
// Additionally returns validators found over the DHT
for validator in validators {
let mut returned_addresses = authority_discovery
@@ -66,9 +69,11 @@ pub(crate) fn module<
// It isn't beneficial to use multiple addresses for a single peer here
if !returned_addresses.is_empty() {
all_p2p_addresses.push(
returned_addresses
.remove(usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len())
.into(),
libp2p::Multiaddr::from(
returned_addresses
.remove(usize::try_from(OsRng.next_u64() >> 32).unwrap() % returned_addresses.len()),
)
.to_string(),
);
}
}

View File

@@ -217,6 +217,7 @@ pub struct SlashReport(
);
/// An error when converting from a `Vec`.
#[derive(Debug)]
pub enum FromVecError {
/// The source `Vec` was too long to be converted.
TooLong,