mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Reloaded provided transactions from the disk
Also resolves a race condition by asserting provided transactions must be unique, allowing them to be safely provided multiple times.
This commit is contained in:
parent
63318cb728
commit
695d923593
4 changed files with 109 additions and 38 deletions
|
@ -5,8 +5,8 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
|||
use serai_db::{DbTxn, Db};
|
||||
|
||||
use crate::{
|
||||
ReadWrite, Signed, TransactionKind, Transaction, verify_transaction, ProvidedTransactions,
|
||||
BlockError, Block, Mempool,
|
||||
ReadWrite, Signed, TransactionKind, Transaction, ProvidedError, ProvidedTransactions, BlockError,
|
||||
Block, Mempool,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
|
@ -18,7 +18,7 @@ pub(crate) struct Blockchain<D: Db, T: Transaction> {
|
|||
tip: [u8; 32],
|
||||
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||
|
||||
provided: ProvidedTransactions<T>,
|
||||
provided: ProvidedTransactions<D, T>,
|
||||
mempool: Mempool<T>,
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
|||
genesis: [u8; 32],
|
||||
participants: &[<Ristretto as Ciphersuite>::G],
|
||||
) -> Self {
|
||||
// TODO: Reload provided/mempool
|
||||
// TODO: Reload mempool
|
||||
|
||||
let mut next_nonces = HashMap::new();
|
||||
for participant in participants {
|
||||
|
@ -58,14 +58,14 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
|||
}
|
||||
|
||||
let mut res = Self {
|
||||
db: Some(db),
|
||||
db: Some(db.clone()),
|
||||
genesis,
|
||||
|
||||
block_number: 0,
|
||||
tip: genesis,
|
||||
next_nonces,
|
||||
|
||||
provided: ProvidedTransactions::new(),
|
||||
provided: ProvidedTransactions::new(db, genesis),
|
||||
mempool: Mempool::new(genesis),
|
||||
};
|
||||
|
||||
|
@ -102,13 +102,8 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
|||
self.mempool.add(&self.next_nonces, internal, tx)
|
||||
}
|
||||
|
||||
pub(crate) fn provide_transaction(&mut self, tx: T) -> bool {
|
||||
// TODO: Should this check be internal to ProvidedTransactions?
|
||||
if verify_transaction(&tx, self.genesis, &mut HashMap::new()).is_err() {
|
||||
return false;
|
||||
}
|
||||
self.provided.provide(tx);
|
||||
true
|
||||
pub(crate) fn provide_transaction(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||
self.provided.provide(tx)
|
||||
}
|
||||
|
||||
/// Returns the next nonce for signing, or None if they aren't a participant.
|
||||
|
@ -159,7 +154,7 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
|||
for tx in &block.transactions {
|
||||
match tx.kind() {
|
||||
TransactionKind::Provided => {
|
||||
self.provided.complete(tx.hash());
|
||||
self.provided.complete(&mut txn, tx.hash());
|
||||
}
|
||||
TransactionKind::Unsigned => {}
|
||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||
|
|
|
@ -29,6 +29,7 @@ pub use transaction::*;
|
|||
|
||||
mod provided;
|
||||
pub(crate) use provided::*;
|
||||
pub use provided::ProvidedError;
|
||||
|
||||
mod block;
|
||||
pub use block::*;
|
||||
|
@ -125,9 +126,7 @@ impl<D: Db, T: Transaction, P: P2p> Tributary<D, T, P> {
|
|||
Self { network, synced_block, messages }
|
||||
}
|
||||
|
||||
// TODO: Is there a race condition with providing these? Since the same provided TX provided
|
||||
// twice counts as two transactions
|
||||
pub fn provide_transaction(&self, tx: T) -> bool {
|
||||
pub fn provide_transaction(&self, tx: T) -> Result<(), ProvidedError> {
|
||||
self.network.blockchain.write().unwrap().provide_transaction(tx)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,95 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::collections::{VecDeque, HashMap};
|
||||
|
||||
use crate::{TransactionKind, Transaction};
|
||||
use thiserror::Error;
|
||||
|
||||
use serai_db::{Get, DbTxn, Db};
|
||||
|
||||
use crate::{TransactionKind, TransactionError, Transaction, verify_transaction};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Error)]
|
||||
pub enum ProvidedError {
|
||||
/// The provided transaction's kind wasn't Provided
|
||||
#[error("transaction wasn't a provided transaction")]
|
||||
NotProvided,
|
||||
/// The provided transaction was invalid.
|
||||
#[error("provided transaction was invalid")]
|
||||
InvalidProvided(TransactionError),
|
||||
/// Transaction was already provided
|
||||
#[error("transaction was already provided")]
|
||||
AlreadyProvided,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ProvidedTransactions<T: Transaction> {
|
||||
pub struct ProvidedTransactions<D: Db, T: Transaction> {
|
||||
db: D,
|
||||
genesis: [u8; 32],
|
||||
|
||||
pub(crate) transactions: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T: Transaction> Default for ProvidedTransactions<T> {
|
||||
fn default() -> Self {
|
||||
ProvidedTransactions { transactions: VecDeque::new() }
|
||||
impl<D: Db, T: Transaction> ProvidedTransactions<D, T> {
|
||||
fn provided_key(&self, hash: &[u8]) -> Vec<u8> {
|
||||
D::key(b"tributary", b"provided", [self.genesis.as_ref(), hash].concat())
|
||||
}
|
||||
fn currently_provided_key(&self) -> Vec<u8> {
|
||||
D::key(b"tributary", b"currently_provided", self.genesis)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transaction> ProvidedTransactions<T> {
|
||||
pub(crate) fn new() -> Self {
|
||||
ProvidedTransactions::default()
|
||||
pub(crate) fn new(db: D, genesis: [u8; 32]) -> Self {
|
||||
let mut res = ProvidedTransactions { db, genesis, transactions: VecDeque::new() };
|
||||
|
||||
let currently_provided = res.db.get(res.currently_provided_key()).unwrap_or(vec![]);
|
||||
let mut i = 0;
|
||||
while i < currently_provided.len() {
|
||||
res.transactions.push_back(
|
||||
T::read::<&[u8]>(
|
||||
&mut res.db.get(res.provided_key(¤tly_provided[i .. (i + 32)])).unwrap().as_ref(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
i += 32;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Provide a transaction for inclusion in a block.
|
||||
pub(crate) fn provide(&mut self, tx: T) {
|
||||
// TODO: Make an error out of this
|
||||
assert_eq!(tx.kind(), TransactionKind::Provided, "provided a non-provided transaction");
|
||||
pub(crate) fn provide(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||
if tx.kind() != TransactionKind::Provided {
|
||||
Err(ProvidedError::NotProvided)?;
|
||||
}
|
||||
|
||||
match verify_transaction(&tx, self.genesis, &mut HashMap::new()) {
|
||||
Ok(()) => {}
|
||||
Err(e) => Err(ProvidedError::InvalidProvided(e))?,
|
||||
}
|
||||
|
||||
let tx_hash = tx.hash();
|
||||
let provided_key = self.provided_key(&tx_hash);
|
||||
if self.db.get(&provided_key).is_some() {
|
||||
Err(ProvidedError::AlreadyProvided)?;
|
||||
}
|
||||
|
||||
let currently_provided_key = self.currently_provided_key();
|
||||
let mut currently_provided = self.db.get(¤tly_provided_key).unwrap_or(vec![]);
|
||||
|
||||
let mut txn = self.db.txn();
|
||||
txn.put(provided_key, tx.serialize());
|
||||
currently_provided.extend(tx_hash);
|
||||
txn.put(currently_provided_key, currently_provided);
|
||||
txn.commit();
|
||||
|
||||
self.transactions.push_back(tx);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Complete a provided transaction, no longer proposing it nor voting for its validity.
|
||||
pub(crate) fn complete(&mut self, tx: [u8; 32]) {
|
||||
pub(crate) fn complete(&mut self, txn: &mut D::Transaction<'_>, tx: [u8; 32]) {
|
||||
assert_eq!(self.transactions.pop_front().unwrap().hash(), tx);
|
||||
|
||||
let currently_provided_key = self.currently_provided_key();
|
||||
let mut currently_provided = txn.get(¤tly_provided_key).unwrap();
|
||||
assert_eq!(¤tly_provided.drain(.. 32).collect::<Vec<_>>(), &tx);
|
||||
txn.put(currently_provided_key, currently_provided);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
|
@ -5,10 +7,10 @@ use blake2::{Digest, Blake2s256};
|
|||
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||
|
||||
use serai_db::MemDb;
|
||||
use serai_db::{DbTxn, Db, MemDb};
|
||||
|
||||
use crate::{
|
||||
merkle, Transaction, ProvidedTransactions, Block, Blockchain,
|
||||
merkle, Transaction, ProvidedError, ProvidedTransactions, Block, Blockchain,
|
||||
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
||||
};
|
||||
|
||||
|
@ -173,21 +175,33 @@ fn signed_transaction() {
|
|||
|
||||
#[test]
|
||||
fn provided_transaction() {
|
||||
let mut blockchain = new_blockchain::<ProvidedTransaction>(new_genesis(), &[]);
|
||||
let genesis = new_genesis();
|
||||
let mut blockchain = new_blockchain::<ProvidedTransaction>(genesis, &[]);
|
||||
|
||||
let tx = random_provided_transaction(&mut OsRng);
|
||||
|
||||
// This should be provideable
|
||||
let mut txs = ProvidedTransactions::new();
|
||||
txs.provide(tx.clone());
|
||||
txs.complete(tx.hash());
|
||||
let mut db = MemDb::new();
|
||||
let mut txs = ProvidedTransactions::<_, ProvidedTransaction>::new(db.clone(), genesis);
|
||||
txs.provide(tx.clone()).unwrap();
|
||||
assert_eq!(txs.provide(tx.clone()), Err(ProvidedError::AlreadyProvided));
|
||||
assert_eq!(
|
||||
ProvidedTransactions::<_, ProvidedTransaction>::new(db.clone(), genesis).transactions,
|
||||
VecDeque::from([tx.clone()]),
|
||||
);
|
||||
let mut txn = db.txn();
|
||||
txs.complete(&mut txn, tx.hash());
|
||||
txn.commit();
|
||||
assert!(ProvidedTransactions::<_, ProvidedTransaction>::new(db.clone(), genesis)
|
||||
.transactions
|
||||
.is_empty());
|
||||
|
||||
// Non-provided transactions should fail verification
|
||||
let block = Block::new(blockchain.tip(), vec![tx.clone()], vec![]);
|
||||
assert!(blockchain.verify_block(&block).is_err());
|
||||
|
||||
// Provided transactions should pass verification
|
||||
blockchain.provide_transaction(tx.clone());
|
||||
blockchain.provide_transaction(tx.clone()).unwrap();
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
|
||||
// add_block should work for verified blocks
|
||||
|
|
Loading…
Reference in a new issue