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::*;
// 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<u8> {
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<u8> {
// 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<D: Db> TributaryDb<D> {
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)
}
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<u8> {
Self::tributary_key(b"fatal_slash", genesis)
}
@ -79,7 +81,7 @@ impl<D: Db> TributaryDb<D> {
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<D: Db> TributaryDb<D> {
txn.put(key, existing);
}
// The plan IDs associated with a Substrate block
fn plan_ids_key(genesis: &[u8], block: u64) -> Vec<u8> {
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> {
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())
}
// The key pair confirmed for this Tributary
pub fn key_pair_key(set: ValidatorSet) -> Vec<u8> {
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())
}
fn recognized_id_key(genesis: [u8; 32], zone: Zone, id: [u8; 32]) -> Vec<u8> {
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<u8> {
Self::tributary_key(b"attempt", topic.as_key(genesis))
}
pub fn recognized_id<G: Get>(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<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(),
)
pub fn attempt<G: Get>(getter: &G, genesis: [u8; 32], topic: Topic) -> Option<u32> {
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<u8> {
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,

View file

@ -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<D: Db>(
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<u8>, 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::<D>::recognized_id(txn, genesis, data_spec.zone, data_spec.id) {
let Some(curr_attempt) = TributaryDb::<D>::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::<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
let curr_attempt = TributaryDb::<D>::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::<D, _>(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::<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;
}
@ -443,7 +424,7 @@ pub async fn handle_application_tx<
);
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;
}
}
@ -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::<D>::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,
) {