mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 12:09:37 +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 serai_db::{DbTxn, Db};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ReadWrite, Signed, TransactionKind, Transaction, verify_transaction, ProvidedTransactions,
|
ReadWrite, Signed, TransactionKind, Transaction, ProvidedError, ProvidedTransactions, BlockError,
|
||||||
BlockError, Block, Mempool,
|
Block, Mempool,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
|
@ -18,7 +18,7 @@ pub(crate) struct Blockchain<D: Db, T: Transaction> {
|
||||||
tip: [u8; 32],
|
tip: [u8; 32],
|
||||||
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
|
|
||||||
provided: ProvidedTransactions<T>,
|
provided: ProvidedTransactions<D, T>,
|
||||||
mempool: Mempool<T>,
|
mempool: Mempool<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
participants: &[<Ristretto as Ciphersuite>::G],
|
participants: &[<Ristretto as Ciphersuite>::G],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// TODO: Reload provided/mempool
|
// TODO: Reload mempool
|
||||||
|
|
||||||
let mut next_nonces = HashMap::new();
|
let mut next_nonces = HashMap::new();
|
||||||
for participant in participants {
|
for participant in participants {
|
||||||
|
@ -58,14 +58,14 @@ impl<D: Db, T: Transaction> Blockchain<D, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut res = Self {
|
let mut res = Self {
|
||||||
db: Some(db),
|
db: Some(db.clone()),
|
||||||
genesis,
|
genesis,
|
||||||
|
|
||||||
block_number: 0,
|
block_number: 0,
|
||||||
tip: genesis,
|
tip: genesis,
|
||||||
next_nonces,
|
next_nonces,
|
||||||
|
|
||||||
provided: ProvidedTransactions::new(),
|
provided: ProvidedTransactions::new(db, genesis),
|
||||||
mempool: Mempool::new(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)
|
self.mempool.add(&self.next_nonces, internal, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn provide_transaction(&mut self, tx: T) -> bool {
|
pub(crate) fn provide_transaction(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||||
// TODO: Should this check be internal to ProvidedTransactions?
|
self.provided.provide(tx)
|
||||||
if verify_transaction(&tx, self.genesis, &mut HashMap::new()).is_err() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.provided.provide(tx);
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next nonce for signing, or None if they aren't a participant.
|
/// 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 {
|
for tx in &block.transactions {
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Provided => {
|
TransactionKind::Provided => {
|
||||||
self.provided.complete(tx.hash());
|
self.provided.complete(&mut txn, tx.hash());
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {}
|
TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||||
|
|
|
@ -29,6 +29,7 @@ pub use transaction::*;
|
||||||
|
|
||||||
mod provided;
|
mod provided;
|
||||||
pub(crate) use provided::*;
|
pub(crate) use provided::*;
|
||||||
|
pub use provided::ProvidedError;
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
pub use block::*;
|
pub use block::*;
|
||||||
|
@ -125,9 +126,7 @@ impl<D: Db, T: Transaction, P: P2p> Tributary<D, T, P> {
|
||||||
Self { network, synced_block, messages }
|
Self { network, synced_block, messages }
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is there a race condition with providing these? Since the same provided TX provided
|
pub fn provide_transaction(&self, tx: T) -> Result<(), ProvidedError> {
|
||||||
// twice counts as two transactions
|
|
||||||
pub fn provide_transaction(&self, tx: T) -> bool {
|
|
||||||
self.network.blockchain.write().unwrap().provide_transaction(tx)
|
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)]
|
#[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>,
|
pub(crate) transactions: VecDeque<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Transaction> Default for ProvidedTransactions<T> {
|
impl<D: Db, T: Transaction> ProvidedTransactions<D, T> {
|
||||||
fn default() -> Self {
|
fn provided_key(&self, hash: &[u8]) -> Vec<u8> {
|
||||||
ProvidedTransactions { transactions: VecDeque::new() }
|
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(db: D, genesis: [u8; 32]) -> Self {
|
||||||
pub(crate) fn new() -> Self {
|
let mut res = ProvidedTransactions { db, genesis, transactions: VecDeque::new() };
|
||||||
ProvidedTransactions::default()
|
|
||||||
|
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.
|
/// Provide a transaction for inclusion in a block.
|
||||||
pub(crate) fn provide(&mut self, tx: T) {
|
pub(crate) fn provide(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||||
// TODO: Make an error out of this
|
if tx.kind() != TransactionKind::Provided {
|
||||||
assert_eq!(tx.kind(), TransactionKind::Provided, "provided a non-provided transaction");
|
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);
|
self.transactions.push_back(tx);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Complete a provided transaction, no longer proposing it nor voting for its validity.
|
/// 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);
|
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 zeroize::Zeroizing;
|
||||||
use rand::{RngCore, rngs::OsRng};
|
use rand::{RngCore, rngs::OsRng};
|
||||||
|
|
||||||
|
@ -5,10 +7,10 @@ use blake2::{Digest, Blake2s256};
|
||||||
|
|
||||||
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||||
|
|
||||||
use serai_db::MemDb;
|
use serai_db::{DbTxn, Db, MemDb};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
merkle, Transaction, ProvidedTransactions, Block, Blockchain,
|
merkle, Transaction, ProvidedError, ProvidedTransactions, Block, Blockchain,
|
||||||
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -173,21 +175,33 @@ fn signed_transaction() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provided_transaction() {
|
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);
|
let tx = random_provided_transaction(&mut OsRng);
|
||||||
|
|
||||||
// This should be provideable
|
// This should be provideable
|
||||||
let mut txs = ProvidedTransactions::new();
|
let mut db = MemDb::new();
|
||||||
txs.provide(tx.clone());
|
let mut txs = ProvidedTransactions::<_, ProvidedTransaction>::new(db.clone(), genesis);
|
||||||
txs.complete(tx.hash());
|
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
|
// Non-provided transactions should fail verification
|
||||||
let block = Block::new(blockchain.tip(), vec![tx.clone()], vec![]);
|
let block = Block::new(blockchain.tip(), vec![tx.clone()], vec![]);
|
||||||
assert!(blockchain.verify_block(&block).is_err());
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
|
||||||
// Provided transactions should pass verification
|
// Provided transactions should pass verification
|
||||||
blockchain.provide_transaction(tx.clone());
|
blockchain.provide_transaction(tx.clone()).unwrap();
|
||||||
blockchain.verify_block(&block).unwrap();
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
|
||||||
// add_block should work for verified blocks
|
// add_block should work for verified blocks
|
||||||
|
|
Loading…
Reference in a new issue