diff --git a/coordinator/src/tributary/db.rs b/coordinator/src/tributary/db.rs index 5835ff03..acb6e842 100644 --- a/coordinator/src/tributary/db.rs +++ b/coordinator/src/tributary/db.rs @@ -8,20 +8,19 @@ use serai_client::validator_sets::primitives::{ValidatorSet, KeyPair}; pub use serai_db::*; -// Used to determine if an ID is acceptable #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Zone { +pub enum Topic { Dkg, - Batch, - Sign, + Batch([u8; 32]), + Sign([u8; 32]), } -impl Zone { - fn label(&self) -> &'static str { +impl Topic { + fn as_key(&self, genesis: [u8; 32]) -> Vec { match self { - Zone::Dkg => "dkg", - Zone::Batch => "batch", - Zone::Sign => "sign", + Topic::Dkg => [genesis.as_slice(), b"dkg".as_ref()].concat(), + Topic::Batch(id) => [genesis.as_slice(), b"batch".as_ref(), id.as_ref()].concat(), + Topic::Sign(id) => [genesis.as_slice(), b"sign".as_ref(), id.as_ref()].concat(), } } } @@ -29,9 +28,8 @@ impl Zone { // A struct to refer to a piece of data all validators will presumably provide a value for. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct DataSpecification { - pub zone: Zone, + pub topic: Topic, pub label: &'static str, - pub id: [u8; 32], pub attempt: u32, } @@ -39,10 +37,8 @@ impl DataSpecification { fn as_key(&self, genesis: [u8; 32]) -> Vec { // TODO: Use a proper transcript here to avoid conflicts? [ - genesis.as_ref(), - self.zone.label().as_bytes(), + self.topic.as_key(genesis).as_ref(), self.label.as_bytes(), - self.id.as_ref(), self.attempt.to_le_bytes().as_ref(), ] .concat() @@ -60,18 +56,24 @@ impl TributaryDb { D::key(b"coordinator_tributary", dst, key) } - fn block_key(genesis: [u8; 32]) -> Vec { + // Last block scanned + fn last_block_key(genesis: [u8; 32]) -> Vec { Self::tributary_key(b"block", genesis) } pub fn set_last_block(&mut self, genesis: [u8; 32], block: [u8; 32]) { let mut txn = self.0.txn(); - txn.put(Self::block_key(genesis), block); + txn.put(Self::last_block_key(genesis), block); txn.commit(); } pub fn last_block(&self, genesis: [u8; 32]) -> [u8; 32] { - self.0.get(Self::block_key(genesis)).map(|last| last.try_into().unwrap()).unwrap_or(genesis) + self + .0 + .get(Self::last_block_key(genesis)) + .map(|last| last.try_into().unwrap()) + .unwrap_or(genesis) } + // If a validator has been fatally slashed fn fatal_slash_key(genesis: [u8; 32]) -> Vec { Self::tributary_key(b"fatal_slash", genesis) } @@ -79,7 +81,7 @@ impl TributaryDb { let key = Self::fatal_slash_key(genesis); let mut existing = txn.get(&key).unwrap_or(vec![]); - // don't append if we already have it. + // Don't append if we already have it if existing.chunks(32).any(|ex_id| ex_id == id) { return; } @@ -88,6 +90,7 @@ impl TributaryDb { txn.put(key, existing); } + // The plan IDs associated with a Substrate block fn plan_ids_key(genesis: &[u8], block: u64) -> Vec { Self::tributary_key(b"plan_ids", [genesis, block.to_le_bytes().as_ref()].concat()) } @@ -112,6 +115,7 @@ impl TributaryDb { }) } + // The key pair which we're actively working on completing fn currently_completing_key_pair_key(genesis: [u8; 32]) -> Vec { Self::tributary_key(b"currently_completing_key_pair", genesis) } @@ -128,6 +132,7 @@ impl TributaryDb { .map(|bytes| KeyPair::decode(&mut bytes.as_slice()).unwrap()) } + // The key pair confirmed for this Tributary pub fn key_pair_key(set: ValidatorSet) -> Vec { Self::tributary_key(b"key_pair", set.encode()) } @@ -138,33 +143,27 @@ impl TributaryDb { Some(KeyPair::decode(&mut getter.get(Self::key_pair_key(set))?.as_slice()).unwrap()) } - fn recognized_id_key(genesis: [u8; 32], zone: Zone, id: [u8; 32]) -> Vec { - Self::tributary_key( - b"recognized", - [genesis.as_ref(), zone.label().as_bytes(), id.as_ref()].concat(), - ) + // The current attempt to resolve a topic + fn attempt_key(genesis: [u8; 32], topic: Topic) -> Vec { + Self::tributary_key(b"attempt", topic.as_key(genesis)) } - pub fn recognized_id(getter: &G, genesis: [u8; 32], zone: Zone, id: [u8; 32]) -> bool { - getter.get(Self::recognized_id_key(genesis, zone, id)).is_some() + pub fn recognize_topic(txn: &mut D::Transaction<'_>, genesis: [u8; 32], topic: Topic) { + txn.put(Self::attempt_key(genesis, topic), 0u32.to_le_bytes()) } - pub fn recognize_id(txn: &mut D::Transaction<'_>, genesis: [u8; 32], zone: Zone, id: [u8; 32]) { - txn.put(Self::recognized_id_key(genesis, zone, id), []) - } - - fn attempt_key(genesis: [u8; 32], id: [u8; 32]) -> Vec { - let genesis_ref: &[u8] = genesis.as_ref(); - Self::tributary_key(b"attempt", [genesis_ref, id.as_ref()].concat()) - } - pub fn attempt(getter: &G, genesis: [u8; 32], id: [u8; 32]) -> u32 { - u32::from_le_bytes( - getter.get(Self::attempt_key(genesis, id)).unwrap_or(vec![0; 4]).try_into().unwrap(), - ) + pub fn attempt(getter: &G, genesis: [u8; 32], topic: Topic) -> Option { + let attempt_bytes = getter.get(Self::attempt_key(genesis, topic)); + // DKGs start when the chain starts + if attempt_bytes.is_none() && (topic == Topic::Dkg) { + return Some(0); + } + Some(u32::from_le_bytes(attempt_bytes?.try_into().unwrap())) } // Key for the amount of instances received thus far fn data_received_key(genesis: [u8; 32], data_spec: &DataSpecification) -> Vec { Self::tributary_key(b"data_received", data_spec.as_key(genesis)) } + // Key for an instance of data from a specific validator fn data_key( genesis: [u8; 32], data_spec: &DataSpecification, diff --git a/coordinator/src/tributary/handle.rs b/coordinator/src/tributary/handle.rs index ae5d3164..e5291b07 100644 --- a/coordinator/src/tributary/handle.rs +++ b/coordinator/src/tributary/handle.rs @@ -36,7 +36,7 @@ use serai_db::{Get, Db}; use crate::{ processors::Processors, tributary::{ - Transaction, TributarySpec, Zone, DataSpecification, TributaryDb, scanner::RecognizedIdType, + Transaction, TributarySpec, Topic, DataSpecification, TributaryDb, scanner::RecognizedIdType, }, }; @@ -216,7 +216,7 @@ pub fn generated_key_pair( txn, spec, key, - &DataSpecification { zone: Zone::Dkg, label: DKG_CONFIRMATION_NONCES, id: [0; 32], attempt }, + &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt }, spec.n(), ) else { panic!("wasn't a participant in confirming a key pair"); @@ -243,13 +243,10 @@ pub async fn handle_application_tx< let genesis = spec.genesis(); let handle = |txn: &mut _, data_spec: &DataSpecification, bytes: Vec, signed: &Signed| { - if data_spec.zone == Zone::Dkg { - // Since Dkg doesn't have an ID, solely attempts, this should just be [0; 32] - assert_eq!(data_spec.id, [0; 32], "DKG, which shouldn't have IDs, had a non-0 ID"); - } else if !TributaryDb::::recognized_id(txn, genesis, data_spec.zone, data_spec.id) { + let Some(curr_attempt) = TributaryDb::::attempt(txn, genesis, data_spec.topic) else { // TODO: Full slash todo!(); - } + }; // If they've already published a TX for this attempt, slash if let Some(data) = TributaryDb::::data(txn, genesis, data_spec, signed.signer) { @@ -263,7 +260,6 @@ pub async fn handle_application_tx< } // If the attempt is lesser than the blockchain's, slash - let curr_attempt = TributaryDb::::attempt(txn, genesis, data_spec.id); if data_spec.attempt < curr_attempt { // TODO: Slash for being late return None; @@ -283,7 +279,7 @@ pub async fn handle_application_tx< // If we have all the needed commitments/preprocesses/shares, tell the processor // TODO: This needs to be coded by weight, not by validator count - let needed = if data_spec.zone == Zone::Dkg { spec.n() } else { spec.t() }; + let needed = if data_spec.topic == Topic::Dkg { spec.n() } else { spec.t() }; if received == needed { return Some(read_known_to_exist_data::(txn, spec, key, data_spec, needed)); } @@ -294,7 +290,7 @@ pub async fn handle_application_tx< Transaction::DkgCommitments(attempt, bytes, signed) => { match handle( txn, - &DataSpecification { zone: Zone::Dkg, label: "commitments", id: [0; 32], attempt }, + &DataSpecification { topic: Topic::Dkg, label: "commitments", attempt }, bytes, &signed, ) { @@ -343,18 +339,13 @@ pub async fn handle_application_tx< let confirmation_nonces = handle( txn, - &DataSpecification { - zone: Zone::Dkg, - label: DKG_CONFIRMATION_NONCES, - id: [0; 32], - attempt, - }, + &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt }, confirmation_nonces.to_vec(), &signed, ); match handle( txn, - &DataSpecification { zone: Zone::Dkg, label: "shares", id: [0; 32], attempt }, + &DataSpecification { topic: Topic::Dkg, label: "shares", attempt }, bytes, &signed, ) { @@ -379,12 +370,7 @@ pub async fn handle_application_tx< Transaction::DkgConfirmed(attempt, shares, signed) => { match handle( txn, - &DataSpecification { - zone: Zone::Dkg, - label: DKG_CONFIRMATION_SHARES, - id: [0; 32], - attempt, - }, + &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_SHARES, attempt }, shares.to_vec(), &signed, ) { @@ -395,12 +381,7 @@ pub async fn handle_application_tx< txn, spec, key, - &DataSpecification { - zone: Zone::Dkg, - label: DKG_CONFIRMATION_NONCES, - id: [0; 32], - attempt, - }, + &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt }, spec.n(), ) else { panic!("wasn't a participant in DKG confirmation nonces"); @@ -432,7 +413,7 @@ pub async fn handle_application_tx< Transaction::Batch(_, batch) => { // Because this Batch has achieved synchrony, its batch ID should be authorized - TributaryDb::::recognize_id(txn, genesis, Zone::Batch, batch); + TributaryDb::::recognize_topic(txn, genesis, Topic::Batch(batch)); recognized_id(spec.set().network, genesis, RecognizedIdType::Batch, batch).await; } @@ -443,7 +424,7 @@ pub async fn handle_application_tx< ); for id in plan_ids { - TributaryDb::::recognize_id(txn, genesis, Zone::Sign, id); + TributaryDb::::recognize_topic(txn, genesis, Topic::Sign(id)); recognized_id(spec.set().network, genesis, RecognizedIdType::Plan, id).await; } } @@ -452,9 +433,8 @@ pub async fn handle_application_tx< match handle( txn, &DataSpecification { - zone: Zone::Batch, + topic: Topic::Batch(data.plan), label: "preprocess", - id: data.plan, attempt: data.attempt, }, data.data, @@ -479,9 +459,8 @@ pub async fn handle_application_tx< match handle( txn, &DataSpecification { - zone: Zone::Batch, + topic: Topic::Batch(data.plan), label: "share", - id: data.plan, attempt: data.attempt, }, data.data, @@ -511,9 +490,8 @@ pub async fn handle_application_tx< match handle( txn, &DataSpecification { - zone: Zone::Sign, + topic: Topic::Sign(data.plan), label: "preprocess", - id: data.plan, attempt: data.attempt, }, data.data, @@ -545,12 +523,7 @@ pub async fn handle_application_tx< let key_pair = TributaryDb::::key_pair(txn, spec.set()); match handle( txn, - &DataSpecification { - zone: Zone::Sign, - label: "share", - id: data.plan, - attempt: data.attempt, - }, + &DataSpecification { topic: Topic::Sign(data.plan), label: "share", attempt: data.attempt }, data.data, &data.signed, ) {