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

Db macro for coordinator/tributary #450

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions coordinator/tributary/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
130 changes: 48 additions & 82 deletions coordinator/tributary/src/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, BlockQuantityDb, LocalQuantityDb, BlockError,
Block, Mempool, Transaction,
transaction::{Signed, TransactionKind, TransactionError, Transaction as TransactionTrait},
};

Expand All @@ -28,39 +29,21 @@ pub(crate) struct Blockchain<D: Db, T: TransactionTrait> {
pub(crate) next_block_notifications: VecDeque<tokio::sync::oneshot::Sender<()>>,
}

impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
fn tip_key(genesis: [u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"tip", genesis)
}
fn block_number_key(&self) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_number", self.genesis)
}
fn block_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block", [genesis, hash].concat())
}
fn block_hash_key(genesis: &[u8], block_number: u32) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_hash", [genesis, &block_number.to_le_bytes()].concat())
}
fn commit_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"commit", [genesis, hash].concat())
}
fn block_after_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_after", [genesis, hash].concat())
}
fn unsigned_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"unsigned_included", [genesis, hash].concat())
}
fn provided_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"provided_included", [genesis, hash].concat())
}
fn next_nonce_key(&self, signer: &<Ristretto as Ciphersuite>::G) -> Vec<u8> {
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]) -> Vec<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return a u32?

BlockDb: (genesis: [u8; 32], hash: &[u8; 32]) -> Vec<u8>,
BlockHashDb: (genesis: [u8; 32], block_number: u32) -> [u8; 32],
CommitDb: (genesis: [u8; 32], block: &[u8; 32]) -> Vec<u8>,
BlockAfterDb: (genesis: [u8; 32], hash: [u8; 32]) -> [u8; 32],
UnsignedIncludedDb: (genesis: [u8; 32], hash: [u8; 32]) -> Vec<u8>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this return ()?

ProvidedIncludedDb: (genesis: [u8; 32], hash: [u8; 32]) -> [u8; 0],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(), not [u8; 0]

NextNonceDb: (genesis: [u8; 32], hash: [u8; 32]) -> Vec<u8>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NextNonce is by signer: [u8; 32], not hash: [u8; 32]. I also believe this Vec<u8> should be a u32

}
);

impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
pub(crate) fn new(
db: D,
genesis: [u8; 32],
Expand All @@ -84,17 +67,17 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {

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.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)) {
if let Some(next_nonce) = NextNonceDb::get(self_db, genesis, participant.to_bytes()) {
res.next_nonces.insert(*participant, u32::from_le_bytes(next_nonce.try_into().unwrap()));
}
}
Expand All @@ -111,32 +94,20 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
}

pub(crate) fn block_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<Block<T>> {
db.get(Self::block_key(&genesis, block))
BlockDb::get(db, genesis, block)
.map(|bytes| Block::<T>::read::<&[u8]>(&mut bytes.as_ref()).unwrap())
}

pub(crate) fn commit_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
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).unwrap(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was self.block_hash(block)?. Now it's BlockHashDb::get(...).unwrap(). That means this function will now panic if called with a block number which hasn't been used, not return None.

)
}

pub(crate) fn locally_provided_txs_in_block(
Expand All @@ -145,19 +116,14 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
block: &[u8; 32],
order: &str,
) -> bool {
let local_key = ProvidedTransactions::<D, T>::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::<D, T>::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 = LocalQuantityDb::get(db, genesis, order_bytes).unwrap_or_default();
let block = BlockQuantityDb::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<N: Network>(
Expand All @@ -170,15 +136,15 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
let genesis = self.genesis;

let commit = |block: u32| -> Option<Commit<N::SignatureScheme>> {
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::<N::SignatureScheme>::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::<N>(&self.next_nonces, internal, tx, schema, unsigned_in_chain, commit)
}

Expand All @@ -194,7 +160,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
pub(crate) fn build_block<N: Network>(&mut self, schema: N::SignatureScheme) -> Block<T> {
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,
Expand All @@ -214,9 +180,9 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
) -> 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<Commit<N::SignatureScheme>> {
let commit = self.commit_by_block_number(block)?;
// commit has to be valid if it is coming from our db
Expand Down Expand Up @@ -259,29 +225,30 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
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.to_le_bytes());

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, &[] as &[u8; 0]);
}
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, &[] as &[u8; 0]);
// remove from the mempool
self.mempool.remove(&hash);
}
Expand All @@ -294,8 +261,7 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
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.to_le_bytes());

self.mempool.remove(&tx.hash());
}
Expand Down
4 changes: 2 additions & 2 deletions coordinator/tributary/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,13 +397,13 @@ impl<D: Db, T: TransactionTrait> TributaryReader<D, T> {
Blockchain::<D, T>::block_from_db(&self.0, self.1, hash)
}
pub fn commit(&self, hash: &[u8; 32]) -> Option<Vec<u8>> {
Blockchain::<D, T>::commit_from_db(&self.0, self.1, hash)
CommitDb::get(&self.0, self.1, hash)
}
pub fn parsed_commit(&self, hash: &[u8; 32]) -> Option<Commit<Validators>> {
self.commit(hash).map(|commit| Commit::<Validators>::decode(&mut commit.as_ref()).unwrap())
}
pub fn block_after(&self, hash: &[u8; 32]) -> Option<[u8; 32]> {
Blockchain::<D, T>::block_after(&self.0, self.1, hash)
BlockAfterDb::get(&self.0, self.1, *hash)
}
pub fn time_of_block(&self, hash: &[u8; 32]) -> Option<u64> {
self
Expand Down
Loading