Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert coordinator/tributary/nonce_decider to use create_db macro #423

Merged
merged 3 commits into from
Nov 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions coordinator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ serai-client = { path = "../substrate/client", default-features = false, feature

hex = { version = "0.4", default-features = false, features = ["std"] }
bincode = { version = "1", default-features = false }
serde = "1"
serde_json = { version = "1", default-features = false, features = ["std"] }

log = { version = "0.4", default-features = false, features = ["std"] }
Expand Down
2 changes: 1 addition & 1 deletion coordinator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,7 @@ async fn handle_processor_message<D: Db, P: P2p>(

let nonce = loop {
let Some(nonce) =
NonceDecider::<D>::nonce(&txn, genesis, &tx).expect("signed TX didn't have nonce")
NonceDecider::nonce(&txn, genesis, &tx).expect("signed TX didn't have nonce")
else {
// This can be None if the following events occur, in order:
// 1) We scanned the relevant transaction(s) in a Tributary block
Expand Down
8 changes: 4 additions & 4 deletions coordinator/src/tributary/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ pub(crate) async fn handle_application_tx<
Transaction::Batch(_, batch) => {
// Because this Batch has achieved synchrony, its batch ID should be authorized
TributaryDb::<D>::recognize_topic(txn, genesis, Topic::Batch(batch));
let nonce = NonceDecider::<D>::handle_batch(txn, genesis, batch);
let nonce = NonceDecider::handle_batch(txn, genesis, batch);
recognized_id(spec.set(), genesis, RecognizedIdType::Batch, batch.to_vec(), nonce).await;
}

Expand All @@ -511,7 +511,7 @@ pub(crate) async fn handle_application_tx<
despite us not providing that transaction",
);

let nonces = NonceDecider::<D>::handle_substrate_block(txn, genesis, &plan_ids);
let nonces = NonceDecider::handle_substrate_block(txn, genesis, &plan_ids);
for (nonce, id) in nonces.into_iter().zip(plan_ids.into_iter()) {
TributaryDb::<D>::recognize_topic(txn, genesis, Topic::Sign(id));
recognized_id(spec.set(), genesis, RecognizedIdType::Plan, id.to_vec(), nonce).await;
Expand All @@ -534,7 +534,7 @@ pub(crate) async fn handle_application_tx<
) {
Accumulation::Ready(DataSet::Participating(mut preprocesses)) => {
unflatten(spec, &mut preprocesses);
NonceDecider::<D>::selected_for_signing_batch(txn, genesis, data.plan);
NonceDecider::selected_for_signing_batch(txn, genesis, data.plan);
let key = TributaryDb::<D>::key_pair(txn, spec.set()).unwrap().0 .0;
processors
.send(
Expand Down Expand Up @@ -602,7 +602,7 @@ pub(crate) async fn handle_application_tx<
) {
Accumulation::Ready(DataSet::Participating(mut preprocesses)) => {
unflatten(spec, &mut preprocesses);
NonceDecider::<D>::selected_for_signing_plan(txn, genesis, data.plan);
NonceDecider::selected_for_signing_plan(txn, genesis, data.plan);
processors
.send(
spec.set().network,
Expand Down
107 changes: 42 additions & 65 deletions coordinator/src/tributary/nonce_decider.rs
Original file line number Diff line number Diff line change
@@ -1,89 +1,70 @@
use core::marker::PhantomData;

use serai_db::{Get, DbTxn, Db};
use serai_db::{Get, DbTxn, create_db};

use crate::tributary::Transaction;

/// Decides the nonce which should be used for a transaction on a Tributary.
///
/// Deterministically builds a list of nonces to use based on the on-chain events and expected
/// transactions in response. Enables rebooting/rebuilding validators with full safety.
pub struct NonceDecider<D: Db>(PhantomData<D>);
use scale::Encode;

const BATCH_CODE: u8 = 0;
const BATCH_SIGNING_CODE: u8 = 1;
const PLAN_CODE: u8 = 2;
const PLAN_SIGNING_CODE: u8 = 3;

impl<D: Db> NonceDecider<D> {
fn next_nonce_key(genesis: [u8; 32]) -> Vec<u8> {
D::key(b"coordinator_tributary_nonce", b"next", genesis)
}
fn allocate_nonce(txn: &mut D::Transaction<'_>, genesis: [u8; 32]) -> u32 {
let key = Self::next_nonce_key(genesis);
let next =
txn.get(&key).map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())).unwrap_or(3);
txn.put(key, (next + 1).to_le_bytes());
next
create_db!(
NonceDeciderDb {
NextNonceDb: (genesis: [u8; 32]) -> u32,
ItemNonceDb: (genesis: [u8; 32], code: u8, id: &[u8]) -> u32
}
);

fn item_nonce_key(genesis: [u8; 32], code: u8, id: &[u8]) -> Vec<u8> {
D::key(
b"coordinator_tributary_nonce",
b"item",
[genesis.as_slice(), [code].as_ref(), id].concat(),
)
}
fn set_nonce(txn: &mut D::Transaction<'_>, genesis: [u8; 32], code: u8, id: &[u8], nonce: u32) {
txn.put(Self::item_nonce_key(genesis, code, id), nonce.to_le_bytes())
}
fn db_nonce<G: Get>(getter: &G, genesis: [u8; 32], code: u8, id: &[u8]) -> Option<u32> {
getter
.get(Self::item_nonce_key(genesis, code, id))
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
impl NextNonceDb {
pub fn allocate_nonce(txn: &mut impl DbTxn, genesis: [u8; 32]) -> u32 {
let next = Self::get(txn, genesis).unwrap_or(3);
Self::set(txn, genesis, &(next + 1));
next
}
}

pub fn handle_batch(txn: &mut D::Transaction<'_>, genesis: [u8; 32], batch: [u8; 5]) -> u32 {
let nonce_for = Self::allocate_nonce(txn, genesis);
Self::set_nonce(txn, genesis, BATCH_CODE, &batch, nonce_for);
/// Decides the nonce which should be used for a transaction on a Tributary.
///
/// Deterministically builds a list of nonces to use based on the on-chain events and expected
/// transactions in response. Enables rebooting/rebuilding validators with full safety.
pub struct NonceDecider;
impl NonceDecider {
pub fn handle_batch(txn: &mut impl DbTxn, genesis: [u8; 32], batch: [u8; 5]) -> u32 {
let nonce_for = NextNonceDb::allocate_nonce(txn, genesis);
ItemNonceDb::set(txn, genesis, BATCH_CODE, &batch, &nonce_for);
nonce_for
}
// TODO: The processor won't yield shares for this if the signing protocol aborts. We need to
// detect when we're expecting shares for an aborted protocol and insert a dummy transaction
// there.
pub fn selected_for_signing_batch(
txn: &mut D::Transaction<'_>,
genesis: [u8; 32],
batch: [u8; 5],
) {
let nonce_for = Self::allocate_nonce(txn, genesis);
Self::set_nonce(txn, genesis, BATCH_SIGNING_CODE, &batch, nonce_for);
}

pub fn handle_substrate_block(
txn: &mut D::Transaction<'_>,
txn: &mut impl DbTxn,
genesis: [u8; 32],
plans: &[[u8; 32]],
) -> Vec<u32> {
let mut res = Vec::with_capacity(plans.len());
for plan in plans {
let nonce_for = Self::allocate_nonce(txn, genesis);
Self::set_nonce(txn, genesis, PLAN_CODE, plan, nonce_for);
let nonce_for = NextNonceDb::allocate_nonce(txn, genesis);
ItemNonceDb::set(txn, genesis, PLAN_CODE, plan, &nonce_for);
res.push(nonce_for);
}
res
}

// TODO: The processor won't yield shares for this if the signing protocol aborts. We need to
// detect when we're expecting shares for an aborted protocol and insert a dummy transaction
// there.
pub fn selected_for_signing_batch(txn: &mut impl DbTxn, genesis: [u8; 32], batch: [u8; 5]) {
let nonce_for = NextNonceDb::allocate_nonce(txn, genesis);
ItemNonceDb::set(txn, genesis, BATCH_SIGNING_CODE, &batch, &nonce_for);
}

// TODO: Same TODO as selected_for_signing_batch
pub fn selected_for_signing_plan(
txn: &mut D::Transaction<'_>,
genesis: [u8; 32],
plan: [u8; 32],
) {
let nonce_for = Self::allocate_nonce(txn, genesis);
Self::set_nonce(txn, genesis, PLAN_SIGNING_CODE, &plan, nonce_for);
pub fn selected_for_signing_plan(txn: &mut impl DbTxn, genesis: [u8; 32], plan: [u8; 32]) {
let nonce_for = NextNonceDb::allocate_nonce(txn, genesis);
ItemNonceDb::set(txn, genesis, PLAN_SIGNING_CODE, &plan, &nonce_for);
}

pub fn nonce<G: Get>(getter: &G, genesis: [u8; 32], tx: &Transaction) -> Option<Option<u32>> {
pub fn nonce(getter: &impl Get, genesis: [u8; 32], tx: &Transaction) -> Option<Option<u32>> {
match tx {
Transaction::RemoveParticipant(_) => None,

Expand All @@ -105,28 +86,24 @@ impl<D: Db> NonceDecider<D> {
assert_eq!(*attempt, 0);
Some(Some(2))
}

Transaction::Batch(_, _) => None,
Transaction::SubstrateBlock(_) => None,

Transaction::BatchPreprocess(data) => {
assert_eq!(data.attempt, 0);
Some(Self::db_nonce(getter, genesis, BATCH_CODE, &data.plan))
Some(ItemNonceDb::get(getter, genesis, BATCH_CODE, &data.plan))
}
Transaction::BatchShare(data) => {
assert_eq!(data.attempt, 0);
Some(Self::db_nonce(getter, genesis, BATCH_SIGNING_CODE, &data.plan))
Some(ItemNonceDb::get(getter, genesis, BATCH_SIGNING_CODE, &data.plan))
}

Transaction::SignPreprocess(data) => {
assert_eq!(data.attempt, 0);
Some(Self::db_nonce(getter, genesis, PLAN_CODE, &data.plan))
Some(ItemNonceDb::get(getter, genesis, PLAN_CODE, &data.plan))
}
Transaction::SignShare(data) => {
assert_eq!(data.attempt, 0);
Some(Self::db_nonce(getter, genesis, PLAN_SIGNING_CODE, &data.plan))
Some(ItemNonceDb::get(getter, genesis, PLAN_SIGNING_CODE, &data.plan))
}

Transaction::SignCompleted { .. } => None,
}
}
Expand Down