Add a TributaryReader which doesn't require a borrow to operate

Reduces lock contention.

Additionally changes block_key to include the genesis. While not technically
needed, the lack of genesis introduced a side effect where any Tributary on the
the database could return the block of any other Tributary. While that wasn't a
security issue, returning it suggested it was on-chain when it wasn't. This may
have been usable to create issues.
This commit is contained in:
Luke Parker
2023-04-24 06:50:40 -04:00
parent e0820759c0
commit e74b4ab94f
10 changed files with 178 additions and 154 deletions

View File

@@ -29,16 +29,14 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
fn block_number_key(&self) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_number", self.genesis)
}
fn block_key(hash: &[u8; 32]) -> Vec<u8> {
// Since block hashes incorporate their parent, and the first parent is the genesis, this is
// fine not incorporating the hash unless there's a hash collision
D::key(b"tributary_blockchain", b"block", hash)
fn block_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block", [genesis, hash].concat())
}
fn commit_key(hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"commit", hash)
fn commit_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"commit", [genesis, hash].concat())
}
fn block_after_key(hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_after", hash)
fn block_after_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
D::key(b"tributary_blockchain", b"block_after", [genesis, hash].concat())
}
fn next_nonce_key(&self, signer: &<Ristretto as Ciphersuite>::G) -> Vec<u8> {
D::key(
@@ -95,21 +93,21 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
self.block_number
}
pub(crate) fn block_from_db(db: &D, block: &[u8; 32]) -> Option<Block<T>> {
db.get(Self::block_key(block))
pub(crate) fn block_from_db(db: &D, genesis: [u8; 32], block: &[u8; 32]) -> Option<Block<T>> {
db.get(Self::block_key(&genesis, block))
.map(|bytes| Block::<T>::read::<&[u8]>(&mut bytes.as_ref()).unwrap())
}
pub(crate) fn commit_from_db(db: &D, block: &[u8; 32]) -> Option<Vec<u8>> {
db.get(Self::commit_key(block))
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 commit(&self, block: &[u8; 32]) -> Option<Vec<u8>> {
Self::commit_from_db(self.db.as_ref().unwrap(), block)
Self::commit_from_db(self.db.as_ref().unwrap(), self.genesis, block)
}
pub(crate) fn block_after(db: &D, block: &[u8; 32]) -> Option<[u8; 32]> {
db.get(Self::block_after_key(block)).map(|bytes| bytes.try_into().unwrap())
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())
}
pub(crate) fn add_transaction(&mut self, internal: bool, tx: T) -> bool {
@@ -162,10 +160,10 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
self.block_number += 1;
txn.put(self.block_number_key(), self.block_number.to_le_bytes());
txn.put(Self::block_key(&self.tip), block.serialize());
txn.put(Self::commit_key(&self.tip), commit);
txn.put(Self::block_key(&self.genesis, &self.tip), block.serialize());
txn.put(Self::commit_key(&self.genesis, &self.tip), commit);
txn.put(Self::block_after_key(&block.parent()), block.hash());
txn.put(Self::block_after_key(&self.genesis, &block.parent()), block.hash());
for tx in &block.transactions {
match tx.kind() {

View File

@@ -1,4 +1,4 @@
use core::fmt::Debug;
use core::{marker::PhantomData, fmt::Debug};
use std::{sync::Arc, io};
use async_trait::async_trait;
@@ -150,24 +150,8 @@ impl<D: Db, T: Transaction, P: P2p> Tributary<D, T, P> {
self.network.blockchain.read().await.tip()
}
// Since these values are static, they can be safely read from the database without lock
// acquisition
pub fn block(&self, hash: &[u8; 32]) -> Option<Block<T>> {
Blockchain::<D, T>::block_from_db(&self.db, hash)
}
pub fn commit(&self, hash: &[u8; 32]) -> Option<Vec<u8>> {
Blockchain::<D, T>::commit_from_db(&self.db, 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.db, hash)
}
pub fn time_of_block(&self, hash: &[u8; 32]) -> Option<u64> {
self
.commit(hash)
.map(|commit| Commit::<Validators>::decode(&mut commit.as_ref()).unwrap().end_time)
pub fn reader(&self) -> TributaryReader<D, T> {
TributaryReader(self.db.clone(), self.genesis, PhantomData)
}
pub async fn provide_transaction(&self, tx: T) -> Result<(), ProvidedError> {
@@ -266,3 +250,30 @@ impl<D: Db, T: Transaction, P: P2p> Tributary<D, T, P> {
}
}
}
#[derive(Clone)]
pub struct TributaryReader<D: Db, T: Transaction>(D, [u8; 32], PhantomData<T>);
impl<D: Db, T: Transaction> TributaryReader<D, T> {
pub fn genesis(&self) -> [u8; 32] {
self.1
}
// Since these values are static, they can be safely read from the database without lock
// acquisition
pub fn block(&self, hash: &[u8; 32]) -> Option<Block<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)
}
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)
}
pub fn time_of_block(&self, hash: &[u8; 32]) -> Option<u64> {
self
.commit(hash)
.map(|commit| Commit::<Validators>::decode(&mut commit.as_ref()).unwrap().end_time)
}
}

View File

@@ -43,7 +43,7 @@ fn block_addition() {
assert_eq!(blockchain.tip(), block.hash());
assert_eq!(blockchain.block_number(), 1);
assert_eq!(
Blockchain::<MemDb, SignedTransaction>::block_after(&db, &block.parent()).unwrap(),
Blockchain::<MemDb, SignedTransaction>::block_after(&db, genesis, &block.parent()).unwrap(),
block.hash()
);
}