diff --git a/Cargo.lock b/Cargo.lock index 228b378f4..bb7c5e062 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9671,6 +9671,7 @@ name = "tributary-chain" version = "0.1.0" dependencies = [ "async-trait", + "bincode", "blake2", "ciphersuite", "flexible-transcript", @@ -9682,6 +9683,7 @@ dependencies = [ "rand_chacha", "schnorr-signatures", "serai-db", + "serde", "subtle", "tendermint-machine", "thiserror", diff --git a/coordinator/tributary/Cargo.toml b/coordinator/tributary/Cargo.toml index ff6dc1e31..afeb0c21a 100644 --- a/coordinator/tributary/Cargo.toml +++ b/coordinator/tributary/Cargo.toml @@ -29,6 +29,8 @@ log = { version = "0.4", default-features = false, features = ["std"] } serai-db = { path = "../../common/db" } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] } +serde = { version = "1", default-features = false, features = ["derive"] } +bincode = { version = "1", default-features = false } futures = { version = "0.3", default-features = false, features = ["std"] } tendermint = { package = "tendermint-machine", path = "./tendermint" } diff --git a/coordinator/tributary/src/blockchain.rs b/coordinator/tributary/src/blockchain.rs index 3be658e01..fb65d78b1 100644 --- a/coordinator/tributary/src/blockchain.rs +++ b/coordinator/tributary/src/blockchain.rs @@ -2,14 +2,15 @@ use std::collections::{VecDeque, HashMap}; use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto}; -use serai_db::{DbTxn, Db}; +use serai_db::{DbTxn, Db, Get, create_db}; -use scale::Decode; +use scale::{Encode, Decode}; use tendermint::ext::{Network, Commit}; use crate::{ - ReadWrite, ProvidedError, ProvidedTransactions, BlockError, Block, Mempool, Transaction, + ReadWrite, ProvidedError, ProvidedTransactions, BlockProvidedQuantityDb, LocalProvidedQuantityDb, BlockError, + Block, Mempool, Transaction, transaction::{Signed, TransactionKind, TransactionError, Transaction as TransactionTrait}, }; @@ -28,39 +29,21 @@ pub(crate) struct Blockchain { pub(crate) next_block_notifications: VecDeque>, } -impl Blockchain { - fn tip_key(genesis: [u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"tip", genesis) - } - fn block_number_key(&self) -> Vec { - D::key(b"tributary_blockchain", b"block_number", self.genesis) - } - fn block_key(genesis: &[u8], hash: &[u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"block", [genesis, hash].concat()) - } - fn block_hash_key(genesis: &[u8], block_number: u32) -> Vec { - D::key(b"tributary_blockchain", b"block_hash", [genesis, &block_number.to_le_bytes()].concat()) - } - fn commit_key(genesis: &[u8], hash: &[u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"commit", [genesis, hash].concat()) - } - fn block_after_key(genesis: &[u8], hash: &[u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"block_after", [genesis, hash].concat()) - } - fn unsigned_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"unsigned_included", [genesis, hash].concat()) - } - fn provided_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec { - D::key(b"tributary_blockchain", b"provided_included", [genesis, hash].concat()) - } - fn next_nonce_key(&self, signer: &::G) -> Vec { - D::key( - b"tributary_blockchain", - b"next_nonce", - [self.genesis.as_ref(), signer.to_bytes().as_ref()].concat(), - ) +create_db!( + TributaryBlockchainDb { + TipsDb: (genesis: [u8; 32]) -> [u8; 32], + BlockNumberDb: (genesis: [u8; 32]) -> u32, + BlockDb: (genesis: [u8; 32], hash: &[u8; 32]) -> Vec, + BlockHashDb: (genesis: [u8; 32], block_number: u32) -> [u8; 32], + CommitDb: (genesis: [u8; 32], block: &[u8; 32]) -> Vec, + BlockAfterDb: (genesis: [u8; 32], hash: [u8; 32]) -> [u8; 32], + UnsignedIncludedDb: (genesis: [u8; 32], hash: [u8; 32]) -> (), + ProvidedIncludedDb: (genesis: [u8; 32], hash: [u8; 32]) -> (), + NextNonceDb: (genesis: [u8; 32], signer: [u8; 32]) -> u32 } +); +impl Blockchain { pub(crate) fn new( db: D, genesis: [u8; 32], @@ -84,18 +67,18 @@ impl Blockchain { next_block_notifications: VecDeque::new(), }; - + let self_db = res.db.as_ref().unwrap(); if let Some((block_number, tip)) = { - let db = res.db.as_ref().unwrap(); - db.get(res.block_number_key()).map(|number| (number, db.get(Self::tip_key(genesis)).unwrap())) + BlockNumberDb::get(self_db, genesis) + .map(|number| (number, TipsDb::get(self_db, genesis).unwrap())) } { - res.block_number = u32::from_le_bytes(block_number.try_into().unwrap()); + res.block_number = block_number; res.tip.copy_from_slice(&tip); } for participant in participants { - if let Some(next_nonce) = res.db.as_ref().unwrap().get(res.next_nonce_key(participant)) { - res.next_nonces.insert(*participant, u32::from_le_bytes(next_nonce.try_into().unwrap())); + if let Some(next_nonce) = NextNonceDb::get(self_db, genesis, participant.to_bytes()) { + res.next_nonces.insert(*participant, next_nonce); } } @@ -111,32 +94,20 @@ impl Blockchain { } pub(crate) fn block_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option> { - db.get(Self::block_key(&genesis, block)) + BlockDb::get(db, genesis, block) .map(|bytes| Block::::read::<&[u8]>(&mut bytes.as_ref()).unwrap()) } - pub(crate) fn commit_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option> { - db.get(Self::commit_key(&genesis, block)) - } - - pub(crate) fn block_hash_from_db(db: &D, genesis: [u8; 32], block: u32) -> Option<[u8; 32]> { - db.get(Self::block_hash_key(&genesis, block)).map(|h| h.try_into().unwrap()) - } - pub(crate) fn commit(&self, block: &[u8; 32]) -> Option> { - Self::commit_from_db(self.db.as_ref().unwrap(), self.genesis, block) - } - - pub(crate) fn block_hash(&self, block: u32) -> Option<[u8; 32]> { - Self::block_hash_from_db(self.db.as_ref().unwrap(), self.genesis, block) + CommitDb::get(self.db.as_ref().unwrap(), self.genesis, block) } pub(crate) fn commit_by_block_number(&self, block: u32) -> Option> { - self.commit(&self.block_hash(block)?) - } - - pub(crate) fn block_after(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<[u8; 32]> { - db.get(Self::block_after_key(&genesis, block)).map(|bytes| bytes.try_into().unwrap()) + CommitDb::get( + self.db.as_ref().unwrap(), + self.genesis, + &BlockHashDb::get(self.db.as_ref().unwrap(), self.genesis, block)?, + ) } pub(crate) fn locally_provided_txs_in_block( @@ -145,19 +116,14 @@ impl Blockchain { block: &[u8; 32], order: &str, ) -> bool { - let local_key = ProvidedTransactions::::locally_provided_quantity_key(genesis, order); - let local = - db.get(local_key).map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())).unwrap_or(0); - let block_key = - ProvidedTransactions::::block_provided_quantity_key(genesis, block, order); - let block = - db.get(block_key).map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())).unwrap_or(0); - + let order_bytes = order.as_bytes(); + let local = LocalProvidedQuantityDb::get(db, genesis, order_bytes).unwrap_or_default(); + let block = BlockProvidedQuantityDb::get(db, genesis, block, order_bytes).unwrap_or_default(); local >= block } pub(crate) fn tip_from_db(db: &D, genesis: [u8; 32]) -> [u8; 32] { - db.get(Self::tip_key(genesis)).map(|bytes| bytes.try_into().unwrap()).unwrap_or(genesis) + TipsDb::get(db, genesis).unwrap_or(genesis) } pub(crate) fn add_transaction( @@ -170,15 +136,15 @@ impl Blockchain { let genesis = self.genesis; let commit = |block: u32| -> Option> { - let hash = Self::block_hash_from_db(db, genesis, block)?; + let hash = BlockHashDb::get(db, genesis, block)?; // we must have a commit per valid hash - let commit = Self::commit_from_db(db, genesis, &hash).unwrap(); + let commit = CommitDb::get(db, genesis, &hash).unwrap(); // commit has to be valid if it is coming from our db Some(Commit::::decode(&mut commit.as_ref()).unwrap()) }; let unsigned_in_chain = - |hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some(); + |hash: [u8; 32]| UnsignedIncludedDb::get(db, self.genesis, hash).is_some(); self.mempool.add::(&self.next_nonces, internal, tx, schema, unsigned_in_chain, commit) } @@ -194,7 +160,7 @@ impl Blockchain { pub(crate) fn build_block(&mut self, schema: N::SignatureScheme) -> Block { let db = self.db.as_ref().unwrap(); let unsigned_in_chain = - |hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some(); + |hash: [u8; 32]| UnsignedIncludedDb::get(db, self.genesis, hash).is_some(); let block = Block::new( self.tip, @@ -214,9 +180,9 @@ impl Blockchain { ) -> Result<(), BlockError> { let db = self.db.as_ref().unwrap(); let unsigned_in_chain = - |hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some(); + |hash: [u8; 32]| UnsignedIncludedDb::get(db, self.genesis, hash).is_some(); let provided_in_chain = - |hash: [u8; 32]| db.get(Self::provided_included_key(&self.genesis, &hash)).is_some(); + |hash: [u8; 32]| ProvidedIncludedDb::get(db, self.genesis, hash).is_some(); let commit = |block: u32| -> Option> { let commit = self.commit_by_block_number(block)?; // commit has to be valid if it is coming from our db @@ -259,29 +225,30 @@ impl Blockchain { let mut txn = db.txn(); self.tip = block.hash(); - txn.put(Self::tip_key(self.genesis), self.tip); + TipsDb::set(&mut txn, self.genesis, &self.tip); self.block_number += 1; - txn.put(self.block_number_key(), self.block_number.to_le_bytes()); + BlockNumberDb::set(&mut txn, self.genesis, &self.block_number); + + BlockHashDb::set(&mut txn, self.genesis, self.block_number, &self.tip); - txn.put(Self::block_hash_key(&self.genesis, self.block_number), self.tip); + BlockDb::set(&mut txn, self.genesis, &self.tip, &block.serialize()); - txn.put(Self::block_key(&self.genesis, &self.tip), block.serialize()); - txn.put(Self::commit_key(&self.genesis, &self.tip), commit); + CommitDb::set(&mut txn, self.genesis, &self.tip, &commit); - txn.put(Self::block_after_key(&self.genesis, &block.parent()), block.hash()); + BlockAfterDb::set(&mut txn, self.genesis, block.parent(), &block.hash()); for tx in &block.transactions { match tx.kind() { TransactionKind::Provided(order) => { let hash = tx.hash(); self.provided.complete(&mut txn, order, self.tip, hash); - txn.put(Self::provided_included_key(&self.genesis, &hash), []); + ProvidedIncludedDb::set(&mut txn, self.genesis, hash, &()); } TransactionKind::Unsigned => { let hash = tx.hash(); // Save as included on chain - txn.put(Self::unsigned_included_key(&self.genesis, &hash), []); + UnsignedIncludedDb::set(&mut txn, self.genesis, hash, &()); // remove from the mempool self.mempool.remove(&hash); } @@ -294,8 +261,7 @@ impl Blockchain { if prev != *nonce { panic!("verified block had an invalid nonce"); } - - txn.put(self.next_nonce_key(signer), next_nonce.to_le_bytes()); + NextNonceDb::set(&mut txn, self.genesis, signer.to_bytes(), &next_nonce); self.mempool.remove(&tx.hash()); } diff --git a/coordinator/tributary/src/lib.rs b/coordinator/tributary/src/lib.rs index 752178c31..bc22a9e51 100644 --- a/coordinator/tributary/src/lib.rs +++ b/coordinator/tributary/src/lib.rs @@ -397,13 +397,13 @@ impl TributaryReader { Blockchain::::block_from_db(&self.0, self.1, hash) } pub fn commit(&self, hash: &[u8; 32]) -> Option> { - Blockchain::::commit_from_db(&self.0, self.1, hash) + CommitDb::get(&self.0, self.1, hash) } pub fn parsed_commit(&self, hash: &[u8; 32]) -> Option> { self.commit(hash).map(|commit| Commit::::decode(&mut commit.as_ref()).unwrap()) } pub fn block_after(&self, hash: &[u8; 32]) -> Option<[u8; 32]> { - Blockchain::::block_after(&self.0, self.1, hash) + BlockAfterDb::get(&self.0, self.1, *hash) } pub fn time_of_block(&self, hash: &[u8; 32]) -> Option { self diff --git a/coordinator/tributary/src/provided.rs b/coordinator/tributary/src/provided.rs index e4e8193c3..fb201dadf 100644 --- a/coordinator/tributary/src/provided.rs +++ b/coordinator/tributary/src/provided.rs @@ -2,8 +2,8 @@ use std::collections::{VecDeque, HashMap}; use thiserror::Error; -use serai_db::{Get, DbTxn, Db}; - +use serai_db::{Get, DbTxn, Db, create_db}; +use scale::Encode; use crate::transaction::{TransactionKind, TransactionError, Transaction, verify_transaction}; #[derive(Clone, PartialEq, Eq, Debug, Error)] @@ -30,43 +30,27 @@ pub struct ProvidedTransactions { pub(crate) transactions: HashMap<&'static str, VecDeque>, } -impl ProvidedTransactions { - fn transaction_key(&self, hash: &[u8]) -> Vec { - D::key(b"tributary_provided", b"transaction", [self.genesis.as_ref(), hash].concat()) - } - fn current_provided_key(&self) -> Vec { - D::key(b"tributary_provided", b"current", self.genesis) - } - pub(crate) fn locally_provided_quantity_key(genesis: &[u8; 32], order: &str) -> Vec { - D::key(b"tributary_provided", b"local_quantity", [genesis, order.as_bytes()].concat()) - } - pub(crate) fn on_chain_provided_quantity_key(genesis: &[u8; 32], order: &str) -> Vec { - D::key(b"tributary_provided", b"on_chain_quantity", [genesis, order.as_bytes()].concat()) - } - pub(crate) fn block_provided_quantity_key( - genesis: &[u8; 32], - block: &[u8; 32], - order: &str, - ) -> Vec { - D::key(b"tributary_provided", b"block_quantity", [genesis, block, order.as_bytes()].concat()) - } - - pub(crate) fn on_chain_provided_key(genesis: &[u8; 32], order: &str, id: u32) -> Vec { - D::key( - b"tributary_provided", - b"on_chain_tx", - [genesis, order.as_bytes(), &id.to_le_bytes()].concat(), - ) +create_db!( + TributaryProvidedDb { + TransactionDb: (genesis: &[u8], hash: [u8; 32]) -> Vec, + CurrentProvidedDb: (genesis: &[u8]) -> Vec<[u8; 32]>, + LocalProvidedQuantityDb: (genesis: &[u8], order: &[u8]) -> u32, + OnChainProvidedQuantityDb: (genesis: &[u8], order: &[u8]) -> u32, + BlockProvidedQuantityDb: (genesis: &[u8], block: &[u8], order: &[u8]) -> u32, + OnChainProvidedDb: (genesis: &[u8], order: &[u8], id: u32) -> [u8; 32] } +); +impl ProvidedTransactions { pub(crate) fn new(db: D, genesis: [u8; 32]) -> Self { let mut res = ProvidedTransactions { db, genesis, transactions: HashMap::new() }; - - let currently_provided = res.db.get(res.current_provided_key()).unwrap_or(vec![]); + let currently_provided = CurrentProvidedDb::get(&res.db, &genesis).unwrap_or_default(); let mut i = 0; while i < currently_provided.len() { let tx = T::read::<&[u8]>( - &mut res.db.get(res.transaction_key(¤tly_provided[i .. (i + 32)])).unwrap().as_ref(), + &mut TransactionDb::get(&res.db, &res.genesis, currently_provided[i]) + .unwrap() + .as_ref(), ) .unwrap(); @@ -96,50 +80,40 @@ impl ProvidedTransactions { let tx_hash = tx.hash(); // Check it wasn't already provided - let provided_key = self.transaction_key(&tx_hash); - if self.db.get(&provided_key).is_some() { + if TransactionDb::get(&self.db, &self.genesis, tx_hash).is_some() { Err(ProvidedError::AlreadyProvided)?; } // get local and on-chain tx numbers - let local_key = Self::locally_provided_quantity_key(&self.genesis, order); - let mut local_quantity = self - .db - .get(&local_key) - .map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())) - .unwrap_or(0); - let on_chain_key = Self::on_chain_provided_quantity_key(&self.genesis, order); - let on_chain_quantity = self - .db - .get(on_chain_key) - .map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())) - .unwrap_or(0); - - let current_provided_key = self.current_provided_key(); + let order_bytes = order.as_bytes(); + let mut local_quantity = + LocalProvidedQuantityDb::get(&self.db, &self.genesis, order_bytes).unwrap_or_default(); + let on_chain_quantity = + OnChainProvidedQuantityDb::get(&self.db, &self.genesis, order_bytes).unwrap_or_default(); // This would have a race-condition with multiple calls to provide, though this takes &mut self // peventing multiple calls at once let mut txn = self.db.txn(); - txn.put(provided_key, tx.serialize()); + TransactionDb::set(&mut txn, &self.genesis, tx_hash, &tx.serialize()); let this_provided_id = local_quantity; local_quantity += 1; - txn.put(local_key, local_quantity.to_le_bytes()); + LocalProvidedQuantityDb::set(&mut txn, &self.genesis, order_bytes, &local_quantity); if this_provided_id < on_chain_quantity { // Verify against the on-chain version if tx_hash.as_ref() != - txn.get(Self::on_chain_provided_key(&self.genesis, order, this_provided_id)).unwrap() + OnChainProvidedDb::get(&txn, &self.genesis, order_bytes, this_provided_id).unwrap() { Err(ProvidedError::LocalMismatchesOnChain)?; } txn.commit(); } else { #[allow(clippy::unwrap_or_default)] - let mut currently_provided = txn.get(¤t_provided_key).unwrap_or(vec![]); - currently_provided.extend(tx_hash); - txn.put(current_provided_key, currently_provided); + let mut currently_provided = CurrentProvidedDb::get(&txn, &self.genesis).unwrap_or_default(); + currently_provided.push(tx_hash); + CurrentProvidedDb::set(&mut txn, &self.genesis, ¤tly_provided); txn.commit(); if self.transactions.get(order).is_none() { @@ -161,15 +135,13 @@ impl ProvidedTransactions { ) { if let Some(next_tx) = self.transactions.get_mut(order).and_then(|queue| queue.pop_front()) { assert_eq!(next_tx.hash(), tx); - - let current_provided_key = self.current_provided_key(); - let mut currently_provided = txn.get(¤t_provided_key).unwrap(); + let currently_provided = CurrentProvidedDb::get(txn, &self.genesis).unwrap(); // Find this TX's hash let mut i = 0; loop { - if currently_provided[i .. (i + 32)] == tx { - assert_eq!(¤tly_provided.drain(i .. (i + 32)).collect::>(), &tx); + if currently_provided[i] == tx { + assert_eq!(currently_provided[i], tx); break; } @@ -178,24 +150,17 @@ impl ProvidedTransactions { panic!("couldn't find completed TX in currently provided"); } } - - txn.put(current_provided_key, currently_provided); + CurrentProvidedDb::set(txn, &self.genesis, ¤tly_provided); } // bump the on-chain tx number. - let on_chain_key = Self::on_chain_provided_quantity_key(&self.genesis, order); - let block_order_key = Self::block_provided_quantity_key(&self.genesis, &block, order); - let mut on_chain_quantity = self - .db - .get(&on_chain_key) - .map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())) - .unwrap_or(0); - + let order_bytes = order.as_bytes(); + let mut on_chain_quantity = + OnChainProvidedQuantityDb::get(&self.db, &self.genesis, order_bytes).unwrap_or_default(); let this_provided_id = on_chain_quantity; - txn.put(Self::on_chain_provided_key(&self.genesis, order, this_provided_id), tx); - + OnChainProvidedDb::set(txn, &self.genesis, order_bytes, this_provided_id, &tx); on_chain_quantity += 1; - txn.put(on_chain_key, on_chain_quantity.to_le_bytes()); - txn.put(block_order_key, on_chain_quantity.to_le_bytes()); + OnChainProvidedQuantityDb::set(txn, &self.genesis, order_bytes, &on_chain_quantity); + BlockProvidedQuantityDb::set(txn, &self.genesis, &block, order_bytes, &on_chain_quantity); } } diff --git a/coordinator/tributary/src/tests/blockchain.rs b/coordinator/tributary/src/tests/blockchain.rs index 79f304f02..14f239fff 100644 --- a/coordinator/tributary/src/tests/blockchain.rs +++ b/coordinator/tributary/src/tests/blockchain.rs @@ -24,6 +24,7 @@ use crate::{ ProvidedTransaction, SignedTransaction, random_provided_transaction, p2p::DummyP2p, new_genesis, random_evidence_tx, }, + blockchain::BlockAfterDb, }; type N = TendermintNetwork; @@ -52,10 +53,7 @@ fn block_addition() { assert!(blockchain.add_block::(&block, vec![], validators).is_ok()); assert_eq!(blockchain.tip(), block.hash()); assert_eq!(blockchain.block_number(), 1); - assert_eq!( - Blockchain::::block_after(&db, genesis, &block.parent()).unwrap(), - block.hash() - ); + assert_eq!(BlockAfterDb::get(&db, genesis, block.parent()).unwrap(), block.hash()); } #[test]