Define a proper Topic (Zone + ID)

Removes the inefficiency of recognized_topic for attempt returning None if the
topic isn't recognized.
This commit is contained in:
Luke Parker 2023-09-01 01:21:15 -04:00
parent 2ad6b38be9
commit fddc605c65
No known key found for this signature in database
2 changed files with 52 additions and 80 deletions

View file

@ -8,20 +8,19 @@ use serai_client::validator_sets::primitives::{ValidatorSet, KeyPair};
pub use serai_db::*; pub use serai_db::*;
// Used to determine if an ID is acceptable
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Zone { pub enum Topic {
Dkg, Dkg,
Batch, Batch([u8; 32]),
Sign, Sign([u8; 32]),
} }
impl Zone { impl Topic {
fn label(&self) -> &'static str { fn as_key(&self, genesis: [u8; 32]) -> Vec<u8> {
match self { match self {
Zone::Dkg => "dkg", Topic::Dkg => [genesis.as_slice(), b"dkg".as_ref()].concat(),
Zone::Batch => "batch", Topic::Batch(id) => [genesis.as_slice(), b"batch".as_ref(), id.as_ref()].concat(),
Zone::Sign => "sign", 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. // A struct to refer to a piece of data all validators will presumably provide a value for.
#[derive(Clone, Copy, PartialEq, Eq, Debug)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct DataSpecification { pub struct DataSpecification {
pub zone: Zone, pub topic: Topic,
pub label: &'static str, pub label: &'static str,
pub id: [u8; 32],
pub attempt: u32, pub attempt: u32,
} }
@ -39,10 +37,8 @@ impl DataSpecification {
fn as_key(&self, genesis: [u8; 32]) -> Vec<u8> { fn as_key(&self, genesis: [u8; 32]) -> Vec<u8> {
// TODO: Use a proper transcript here to avoid conflicts? // TODO: Use a proper transcript here to avoid conflicts?
[ [
genesis.as_ref(), self.topic.as_key(genesis).as_ref(),
self.zone.label().as_bytes(),
self.label.as_bytes(), self.label.as_bytes(),
self.id.as_ref(),
self.attempt.to_le_bytes().as_ref(), self.attempt.to_le_bytes().as_ref(),
] ]
.concat() .concat()
@ -60,18 +56,24 @@ impl<D: Db> TributaryDb<D> {
D::key(b"coordinator_tributary", dst, key) D::key(b"coordinator_tributary", dst, key)
} }
fn block_key(genesis: [u8; 32]) -> Vec<u8> { // Last block scanned
fn last_block_key(genesis: [u8; 32]) -> Vec<u8> {
Self::tributary_key(b"block", genesis) Self::tributary_key(b"block", genesis)
} }
pub fn set_last_block(&mut self, genesis: [u8; 32], block: [u8; 32]) { pub fn set_last_block(&mut self, genesis: [u8; 32], block: [u8; 32]) {
let mut txn = self.0.txn(); let mut txn = self.0.txn();
txn.put(Self::block_key(genesis), block); txn.put(Self::last_block_key(genesis), block);
txn.commit(); txn.commit();
} }
pub fn last_block(&self, genesis: [u8; 32]) -> [u8; 32] { 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<u8> { fn fatal_slash_key(genesis: [u8; 32]) -> Vec<u8> {
Self::tributary_key(b"fatal_slash", genesis) Self::tributary_key(b"fatal_slash", genesis)
} }
@ -79,7 +81,7 @@ impl<D: Db> TributaryDb<D> {
let key = Self::fatal_slash_key(genesis); let key = Self::fatal_slash_key(genesis);
let mut existing = txn.get(&key).unwrap_or(vec![]); 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) { if existing.chunks(32).any(|ex_id| ex_id == id) {
return; return;
} }
@ -88,6 +90,7 @@ impl<D: Db> TributaryDb<D> {
txn.put(key, existing); txn.put(key, existing);
} }
// The plan IDs associated with a Substrate block
fn plan_ids_key(genesis: &[u8], block: u64) -> Vec<u8> { fn plan_ids_key(genesis: &[u8], block: u64) -> Vec<u8> {
Self::tributary_key(b"plan_ids", [genesis, block.to_le_bytes().as_ref()].concat()) Self::tributary_key(b"plan_ids", [genesis, block.to_le_bytes().as_ref()].concat())
} }
@ -112,6 +115,7 @@ impl<D: Db> TributaryDb<D> {
}) })
} }
// The key pair which we're actively working on completing
fn currently_completing_key_pair_key(genesis: [u8; 32]) -> Vec<u8> { fn currently_completing_key_pair_key(genesis: [u8; 32]) -> Vec<u8> {
Self::tributary_key(b"currently_completing_key_pair", genesis) Self::tributary_key(b"currently_completing_key_pair", genesis)
} }
@ -128,6 +132,7 @@ impl<D: Db> TributaryDb<D> {
.map(|bytes| KeyPair::decode(&mut bytes.as_slice()).unwrap()) .map(|bytes| KeyPair::decode(&mut bytes.as_slice()).unwrap())
} }
// The key pair confirmed for this Tributary
pub fn key_pair_key(set: ValidatorSet) -> Vec<u8> { pub fn key_pair_key(set: ValidatorSet) -> Vec<u8> {
Self::tributary_key(b"key_pair", set.encode()) Self::tributary_key(b"key_pair", set.encode())
} }
@ -138,33 +143,27 @@ impl<D: Db> TributaryDb<D> {
Some(KeyPair::decode(&mut getter.get(Self::key_pair_key(set))?.as_slice()).unwrap()) 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<u8> { // The current attempt to resolve a topic
Self::tributary_key( fn attempt_key(genesis: [u8; 32], topic: Topic) -> Vec<u8> {
b"recognized", Self::tributary_key(b"attempt", topic.as_key(genesis))
[genesis.as_ref(), zone.label().as_bytes(), id.as_ref()].concat(),
)
} }
pub fn recognized_id<G: Get>(getter: &G, genesis: [u8; 32], zone: Zone, id: [u8; 32]) -> bool { pub fn recognize_topic(txn: &mut D::Transaction<'_>, genesis: [u8; 32], topic: Topic) {
getter.get(Self::recognized_id_key(genesis, zone, id)).is_some() 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]) { pub fn attempt<G: Get>(getter: &G, genesis: [u8; 32], topic: Topic) -> Option<u32> {
txn.put(Self::recognized_id_key(genesis, zone, id), []) 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()))
fn attempt_key(genesis: [u8; 32], id: [u8; 32]) -> Vec<u8> {
let genesis_ref: &[u8] = genesis.as_ref();
Self::tributary_key(b"attempt", [genesis_ref, id.as_ref()].concat())
}
pub fn attempt<G: Get>(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(),
)
} }
// Key for the amount of instances received thus far // Key for the amount of instances received thus far
fn data_received_key(genesis: [u8; 32], data_spec: &DataSpecification) -> Vec<u8> { fn data_received_key(genesis: [u8; 32], data_spec: &DataSpecification) -> Vec<u8> {
Self::tributary_key(b"data_received", data_spec.as_key(genesis)) Self::tributary_key(b"data_received", data_spec.as_key(genesis))
} }
// Key for an instance of data from a specific validator
fn data_key( fn data_key(
genesis: [u8; 32], genesis: [u8; 32],
data_spec: &DataSpecification, data_spec: &DataSpecification,

View file

@ -36,7 +36,7 @@ use serai_db::{Get, Db};
use crate::{ use crate::{
processors::Processors, processors::Processors,
tributary::{ tributary::{
Transaction, TributarySpec, Zone, DataSpecification, TributaryDb, scanner::RecognizedIdType, Transaction, TributarySpec, Topic, DataSpecification, TributaryDb, scanner::RecognizedIdType,
}, },
}; };
@ -216,7 +216,7 @@ pub fn generated_key_pair<D: Db>(
txn, txn,
spec, spec,
key, key,
&DataSpecification { zone: Zone::Dkg, label: DKG_CONFIRMATION_NONCES, id: [0; 32], attempt }, &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt },
spec.n(), spec.n(),
) else { ) else {
panic!("wasn't a participant in confirming a key pair"); 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 genesis = spec.genesis();
let handle = |txn: &mut _, data_spec: &DataSpecification, bytes: Vec<u8>, signed: &Signed| { let handle = |txn: &mut _, data_spec: &DataSpecification, bytes: Vec<u8>, signed: &Signed| {
if data_spec.zone == Zone::Dkg { let Some(curr_attempt) = TributaryDb::<D>::attempt(txn, genesis, data_spec.topic) else {
// 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::<D>::recognized_id(txn, genesis, data_spec.zone, data_spec.id) {
// TODO: Full slash // TODO: Full slash
todo!(); todo!();
} };
// If they've already published a TX for this attempt, slash // If they've already published a TX for this attempt, slash
if let Some(data) = TributaryDb::<D>::data(txn, genesis, data_spec, signed.signer) { if let Some(data) = TributaryDb::<D>::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 // If the attempt is lesser than the blockchain's, slash
let curr_attempt = TributaryDb::<D>::attempt(txn, genesis, data_spec.id);
if data_spec.attempt < curr_attempt { if data_spec.attempt < curr_attempt {
// TODO: Slash for being late // TODO: Slash for being late
return None; return None;
@ -283,7 +279,7 @@ pub async fn handle_application_tx<
// If we have all the needed commitments/preprocesses/shares, tell the processor // If we have all the needed commitments/preprocesses/shares, tell the processor
// TODO: This needs to be coded by weight, not by validator count // 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 { if received == needed {
return Some(read_known_to_exist_data::<D, _>(txn, spec, key, data_spec, needed)); return Some(read_known_to_exist_data::<D, _>(txn, spec, key, data_spec, needed));
} }
@ -294,7 +290,7 @@ pub async fn handle_application_tx<
Transaction::DkgCommitments(attempt, bytes, signed) => { Transaction::DkgCommitments(attempt, bytes, signed) => {
match handle( match handle(
txn, txn,
&DataSpecification { zone: Zone::Dkg, label: "commitments", id: [0; 32], attempt }, &DataSpecification { topic: Topic::Dkg, label: "commitments", attempt },
bytes, bytes,
&signed, &signed,
) { ) {
@ -343,18 +339,13 @@ pub async fn handle_application_tx<
let confirmation_nonces = handle( let confirmation_nonces = handle(
txn, txn,
&DataSpecification { &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt },
zone: Zone::Dkg,
label: DKG_CONFIRMATION_NONCES,
id: [0; 32],
attempt,
},
confirmation_nonces.to_vec(), confirmation_nonces.to_vec(),
&signed, &signed,
); );
match handle( match handle(
txn, txn,
&DataSpecification { zone: Zone::Dkg, label: "shares", id: [0; 32], attempt }, &DataSpecification { topic: Topic::Dkg, label: "shares", attempt },
bytes, bytes,
&signed, &signed,
) { ) {
@ -379,12 +370,7 @@ pub async fn handle_application_tx<
Transaction::DkgConfirmed(attempt, shares, signed) => { Transaction::DkgConfirmed(attempt, shares, signed) => {
match handle( match handle(
txn, txn,
&DataSpecification { &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_SHARES, attempt },
zone: Zone::Dkg,
label: DKG_CONFIRMATION_SHARES,
id: [0; 32],
attempt,
},
shares.to_vec(), shares.to_vec(),
&signed, &signed,
) { ) {
@ -395,12 +381,7 @@ pub async fn handle_application_tx<
txn, txn,
spec, spec,
key, key,
&DataSpecification { &DataSpecification { topic: Topic::Dkg, label: DKG_CONFIRMATION_NONCES, attempt },
zone: Zone::Dkg,
label: DKG_CONFIRMATION_NONCES,
id: [0; 32],
attempt,
},
spec.n(), spec.n(),
) else { ) else {
panic!("wasn't a participant in DKG confirmation nonces"); panic!("wasn't a participant in DKG confirmation nonces");
@ -432,7 +413,7 @@ pub async fn handle_application_tx<
Transaction::Batch(_, batch) => { Transaction::Batch(_, batch) => {
// Because this Batch has achieved synchrony, its batch ID should be authorized // Because this Batch has achieved synchrony, its batch ID should be authorized
TributaryDb::<D>::recognize_id(txn, genesis, Zone::Batch, batch); TributaryDb::<D>::recognize_topic(txn, genesis, Topic::Batch(batch));
recognized_id(spec.set().network, genesis, RecognizedIdType::Batch, batch).await; 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 { for id in plan_ids {
TributaryDb::<D>::recognize_id(txn, genesis, Zone::Sign, id); TributaryDb::<D>::recognize_topic(txn, genesis, Topic::Sign(id));
recognized_id(spec.set().network, genesis, RecognizedIdType::Plan, id).await; recognized_id(spec.set().network, genesis, RecognizedIdType::Plan, id).await;
} }
} }
@ -452,9 +433,8 @@ pub async fn handle_application_tx<
match handle( match handle(
txn, txn,
&DataSpecification { &DataSpecification {
zone: Zone::Batch, topic: Topic::Batch(data.plan),
label: "preprocess", label: "preprocess",
id: data.plan,
attempt: data.attempt, attempt: data.attempt,
}, },
data.data, data.data,
@ -479,9 +459,8 @@ pub async fn handle_application_tx<
match handle( match handle(
txn, txn,
&DataSpecification { &DataSpecification {
zone: Zone::Batch, topic: Topic::Batch(data.plan),
label: "share", label: "share",
id: data.plan,
attempt: data.attempt, attempt: data.attempt,
}, },
data.data, data.data,
@ -511,9 +490,8 @@ pub async fn handle_application_tx<
match handle( match handle(
txn, txn,
&DataSpecification { &DataSpecification {
zone: Zone::Sign, topic: Topic::Sign(data.plan),
label: "preprocess", label: "preprocess",
id: data.plan,
attempt: data.attempt, attempt: data.attempt,
}, },
data.data, data.data,
@ -545,12 +523,7 @@ pub async fn handle_application_tx<
let key_pair = TributaryDb::<D>::key_pair(txn, spec.set()); let key_pair = TributaryDb::<D>::key_pair(txn, spec.set());
match handle( match handle(
txn, txn,
&DataSpecification { &DataSpecification { topic: Topic::Sign(data.plan), label: "share", attempt: data.attempt },
zone: Zone::Sign,
label: "share",
id: data.plan,
attempt: data.attempt,
},
data.data, data.data,
&data.signed, &data.signed,
) { ) {