mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-09 04:19:33 +00:00
Use multiple nonces in the Tributary
This commit is contained in:
parent
c82d1283af
commit
1ca66b846a
11 changed files with 221 additions and 200 deletions
|
@ -7,13 +7,12 @@ use thiserror::Error;
|
||||||
|
|
||||||
use blake2::{Digest, Blake2s256};
|
use blake2::{Digest, Blake2s256};
|
||||||
|
|
||||||
use ciphersuite::{Ciphersuite, Ristretto};
|
|
||||||
|
|
||||||
use tendermint::ext::{Network, Commit};
|
use tendermint::ext::{Network, Commit};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
transaction::{
|
transaction::{
|
||||||
TransactionError, Signed, TransactionKind, Transaction as TransactionTrait, verify_transaction,
|
TransactionError, Signed, TransactionKind, Transaction as TransactionTrait, GAIN,
|
||||||
|
verify_transaction,
|
||||||
},
|
},
|
||||||
BLOCK_SIZE_LIMIT, ReadWrite, merkle, Transaction,
|
BLOCK_SIZE_LIMIT, ReadWrite, merkle, Transaction,
|
||||||
tendermint::tx::verify_tendermint_tx,
|
tendermint::tx::verify_tendermint_tx,
|
||||||
|
@ -122,7 +121,7 @@ impl<T: TransactionTrait> Block<T> {
|
||||||
let mut unsigned = vec![];
|
let mut unsigned = vec![];
|
||||||
for tx in mempool {
|
for tx in mempool {
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Signed(_) => signed.push(tx),
|
TransactionKind::Signed(_, _) => signed.push(tx),
|
||||||
TransactionKind::Unsigned => unsigned.push(tx),
|
TransactionKind::Unsigned => unsigned.push(tx),
|
||||||
TransactionKind::Provided(_) => panic!("provided transaction entered mempool"),
|
TransactionKind::Provided(_) => panic!("provided transaction entered mempool"),
|
||||||
}
|
}
|
||||||
|
@ -135,7 +134,7 @@ impl<T: TransactionTrait> Block<T> {
|
||||||
|
|
||||||
// Check TXs are sorted by nonce.
|
// Check TXs are sorted by nonce.
|
||||||
let nonce = |tx: &Transaction<T>| {
|
let nonce = |tx: &Transaction<T>| {
|
||||||
if let TransactionKind::Signed(Signed { nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
||||||
*nonce
|
*nonce
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
|
@ -169,12 +168,12 @@ impl<T: TransactionTrait> Block<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn verify<N: Network>(
|
pub(crate) fn verify<N: Network, G: GAIN>(
|
||||||
&self,
|
&self,
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
last_block: [u8; 32],
|
last_block: [u8; 32],
|
||||||
mut locally_provided: HashMap<&'static str, VecDeque<T>>,
|
mut locally_provided: HashMap<&'static str, VecDeque<T>>,
|
||||||
mut next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
get_and_increment_nonce: &mut G,
|
||||||
schema: N::SignatureScheme,
|
schema: N::SignatureScheme,
|
||||||
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
|
commit: impl Fn(u32) -> Option<Commit<N::SignatureScheme>>,
|
||||||
unsigned_in_chain: impl Fn([u8; 32]) -> bool,
|
unsigned_in_chain: impl Fn([u8; 32]) -> bool,
|
||||||
|
@ -259,10 +258,12 @@ impl<T: TransactionTrait> Block<T> {
|
||||||
Err(e) => Err(BlockError::TransactionError(e))?,
|
Err(e) => Err(BlockError::TransactionError(e))?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Transaction::Application(tx) => match verify_transaction(tx, genesis, &mut next_nonces) {
|
Transaction::Application(tx) => {
|
||||||
|
match verify_transaction(tx, genesis, get_and_increment_nonce) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) => Err(BlockError::TransactionError(e))?,
|
Err(e) => Err(BlockError::TransactionError(e))?,
|
||||||
},
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::collections::{VecDeque, HashMap};
|
use std::collections::{VecDeque, HashSet};
|
||||||
|
|
||||||
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
|
|
||||||
use serai_db::{DbTxn, Db};
|
use serai_db::{Get, DbTxn, Db};
|
||||||
|
|
||||||
use scale::Decode;
|
use scale::Decode;
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ pub(crate) struct Blockchain<D: Db, T: TransactionTrait> {
|
||||||
|
|
||||||
block_number: u32,
|
block_number: u32,
|
||||||
tip: [u8; 32],
|
tip: [u8; 32],
|
||||||
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
participants: HashSet<<Ristretto as Ciphersuite>::G>,
|
||||||
|
|
||||||
provided: ProvidedTransactions<D, T>,
|
provided: ProvidedTransactions<D, T>,
|
||||||
mempool: Mempool<D, T>,
|
mempool: Mempool<D, T>,
|
||||||
|
@ -53,11 +53,15 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
fn provided_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
|
fn provided_included_key(genesis: &[u8], hash: &[u8; 32]) -> Vec<u8> {
|
||||||
D::key(b"tributary_blockchain", b"provided_included", [genesis, hash].concat())
|
D::key(b"tributary_blockchain", b"provided_included", [genesis, hash].concat())
|
||||||
}
|
}
|
||||||
fn next_nonce_key(&self, signer: &<Ristretto as Ciphersuite>::G) -> Vec<u8> {
|
fn next_nonce_key(
|
||||||
|
genesis: &[u8; 32],
|
||||||
|
signer: &<Ristretto as Ciphersuite>::G,
|
||||||
|
order: &[u8],
|
||||||
|
) -> Vec<u8> {
|
||||||
D::key(
|
D::key(
|
||||||
b"tributary_blockchain",
|
b"tributary_blockchain",
|
||||||
b"next_nonce",
|
b"next_nonce",
|
||||||
[self.genesis.as_ref(), signer.to_bytes().as_ref()].concat(),
|
[genesis.as_ref(), signer.to_bytes().as_ref(), order].concat(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,18 +70,13 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
participants: &[<Ristretto as Ciphersuite>::G],
|
participants: &[<Ristretto as Ciphersuite>::G],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut next_nonces = HashMap::new();
|
|
||||||
for participant in participants {
|
|
||||||
next_nonces.insert(*participant, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = Self {
|
let mut res = Self {
|
||||||
db: Some(db.clone()),
|
db: Some(db.clone()),
|
||||||
genesis,
|
genesis,
|
||||||
|
participants: participants.iter().cloned().collect(),
|
||||||
|
|
||||||
block_number: 0,
|
block_number: 0,
|
||||||
tip: genesis,
|
tip: genesis,
|
||||||
next_nonces,
|
|
||||||
|
|
||||||
provided: ProvidedTransactions::new(db.clone(), genesis),
|
provided: ProvidedTransactions::new(db.clone(), genesis),
|
||||||
mempool: Mempool::new(db, genesis),
|
mempool: Mempool::new(db, genesis),
|
||||||
|
@ -93,12 +92,6 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
res.tip.copy_from_slice(&tip);
|
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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,27 +172,58 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
|
|
||||||
let unsigned_in_chain =
|
let unsigned_in_chain =
|
||||||
|hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some();
|
|hash: [u8; 32]| db.get(Self::unsigned_included_key(&self.genesis, &hash)).is_some();
|
||||||
self.mempool.add::<N>(&self.next_nonces, internal, tx, schema, unsigned_in_chain, commit)
|
self.mempool.add::<N, _>(
|
||||||
|
|signer, order| {
|
||||||
|
if self.participants.contains(&signer) {
|
||||||
|
Some(
|
||||||
|
db.get(Self::next_nonce_key(&self.genesis, &signer, &order))
|
||||||
|
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
|
.unwrap_or(0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
internal,
|
||||||
|
tx,
|
||||||
|
schema,
|
||||||
|
unsigned_in_chain,
|
||||||
|
commit,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn provide_transaction(&mut self, tx: T) -> Result<(), ProvidedError> {
|
pub(crate) fn provide_transaction(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||||
self.provided.provide(tx)
|
self.provided.provide(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next nonce for signing, or None if they aren't a participant.
|
pub(crate) fn next_nonce(
|
||||||
pub(crate) fn next_nonce(&self, key: <Ristretto as Ciphersuite>::G) -> Option<u32> {
|
&self,
|
||||||
Some(self.next_nonces.get(&key).cloned()?.max(self.mempool.next_nonce(&key).unwrap_or(0)))
|
signer: &<Ristretto as Ciphersuite>::G,
|
||||||
|
order: &[u8],
|
||||||
|
) -> Option<u32> {
|
||||||
|
if let Some(next_nonce) = self.mempool.next_nonce_in_mempool(signer, order.to_vec()) {
|
||||||
|
return Some(next_nonce);
|
||||||
|
}
|
||||||
|
if self.participants.contains(signer) {
|
||||||
|
Some(
|
||||||
|
self
|
||||||
|
.db
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.get(Self::next_nonce_key(&self.genesis, signer, order))
|
||||||
|
.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
|
||||||
|
.unwrap_or(0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_block<N: Network>(&mut self, schema: N::SignatureScheme) -> Block<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();
|
|
||||||
|
|
||||||
let block = Block::new(
|
let block = Block::new(
|
||||||
self.tip,
|
self.tip,
|
||||||
self.provided.transactions.values().flatten().cloned().collect(),
|
self.provided.transactions.values().flatten().cloned().collect(),
|
||||||
self.mempool.block(&self.next_nonces, unsigned_in_chain),
|
self.mempool.block(),
|
||||||
);
|
);
|
||||||
// build_block should not return invalid blocks
|
// build_block should not return invalid blocks
|
||||||
self.verify_block::<N>(&block, schema, false).unwrap();
|
self.verify_block::<N>(&block, schema, false).unwrap();
|
||||||
|
@ -222,17 +246,34 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
// commit has to be valid if it is coming from our db
|
// commit has to be valid if it is coming from our db
|
||||||
Some(Commit::<N::SignatureScheme>::decode(&mut commit.as_ref()).unwrap())
|
Some(Commit::<N::SignatureScheme>::decode(&mut commit.as_ref()).unwrap())
|
||||||
};
|
};
|
||||||
block.verify::<N>(
|
|
||||||
|
let mut txn_db = db.clone();
|
||||||
|
let mut txn = txn_db.txn();
|
||||||
|
let res = block.verify::<N, _>(
|
||||||
self.genesis,
|
self.genesis,
|
||||||
self.tip,
|
self.tip,
|
||||||
self.provided.transactions.clone(),
|
self.provided.transactions.clone(),
|
||||||
self.next_nonces.clone(),
|
&mut |signer, order| {
|
||||||
|
if self.participants.contains(signer) {
|
||||||
|
let key = Self::next_nonce_key(&self.genesis, signer, order);
|
||||||
|
let next = txn
|
||||||
|
.get(&key)
|
||||||
|
.map(|next_nonce| u32::from_le_bytes(next_nonce.try_into().unwrap()))
|
||||||
|
.unwrap_or(0);
|
||||||
|
txn.put(key, (next + 1).to_le_bytes());
|
||||||
|
Some(next)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
schema,
|
schema,
|
||||||
&commit,
|
&commit,
|
||||||
unsigned_in_chain,
|
unsigned_in_chain,
|
||||||
provided_in_chain,
|
provided_in_chain,
|
||||||
allow_non_local_provided,
|
allow_non_local_provided,
|
||||||
)
|
);
|
||||||
|
drop(txn);
|
||||||
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a block.
|
/// Add a block.
|
||||||
|
@ -285,18 +326,9 @@ impl<D: Db, T: TransactionTrait> Blockchain<D, T> {
|
||||||
// remove from the mempool
|
// remove from the mempool
|
||||||
self.mempool.remove(&hash);
|
self.mempool.remove(&hash);
|
||||||
}
|
}
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
||||||
let next_nonce = nonce + 1;
|
let next_nonce = nonce + 1;
|
||||||
let prev = self
|
txn.put(Self::next_nonce_key(&self.genesis, signer, &order), next_nonce.to_le_bytes());
|
||||||
.next_nonces
|
|
||||||
.insert(*signer, next_nonce)
|
|
||||||
.expect("block had signed transaction from non-participant");
|
|
||||||
if prev != *nonce {
|
|
||||||
panic!("verified block had an invalid nonce");
|
|
||||||
}
|
|
||||||
|
|
||||||
txn.put(self.next_nonce_key(signer), next_nonce.to_le_bytes());
|
|
||||||
|
|
||||||
self.mempool.remove(&tx.hash());
|
self.mempool.remove(&tx.hash());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,8 +254,12 @@ impl<D: Db, T: TransactionTrait, P: P2p> Tributary<D, T, P> {
|
||||||
self.network.blockchain.write().await.provide_transaction(tx)
|
self.network.blockchain.write().await.provide_transaction(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn next_nonce(&self, signer: <Ristretto as Ciphersuite>::G) -> Option<u32> {
|
pub async fn next_nonce(
|
||||||
self.network.blockchain.read().await.next_nonce(signer)
|
&self,
|
||||||
|
signer: &<Ristretto as Ciphersuite>::G,
|
||||||
|
order: &[u8],
|
||||||
|
) -> Option<u32> {
|
||||||
|
self.network.blockchain.read().await.next_nonce(signer, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns Ok(true) if new, Ok(false) if an already present unsigned, or the error.
|
// Returns Ok(true) if new, Ok(false) if an already present unsigned, or the error.
|
||||||
|
|
|
@ -20,8 +20,9 @@ pub(crate) struct Mempool<D: Db, T: TransactionTrait> {
|
||||||
db: D,
|
db: D,
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
|
|
||||||
|
last_nonce_in_mempool: HashMap<(<Ristretto as Ciphersuite>::G, Vec<u8>), u32>,
|
||||||
txs: HashMap<[u8; 32], Transaction<T>>,
|
txs: HashMap<[u8; 32], Transaction<T>>,
|
||||||
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
txs_per_signer: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
|
@ -58,7 +59,13 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new(db: D, genesis: [u8; 32]) -> Self {
|
pub(crate) fn new(db: D, genesis: [u8; 32]) -> Self {
|
||||||
let mut res = Mempool { db, genesis, txs: HashMap::new(), next_nonces: HashMap::new() };
|
let mut res = Mempool {
|
||||||
|
db,
|
||||||
|
genesis,
|
||||||
|
last_nonce_in_mempool: HashMap::new(),
|
||||||
|
txs: HashMap::new(),
|
||||||
|
txs_per_signer: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
let current_mempool = res.db.get(res.current_mempool_key()).unwrap_or(vec![]);
|
let current_mempool = res.db.get(res.current_mempool_key()).unwrap_or(vec![]);
|
||||||
|
|
||||||
|
@ -73,21 +80,24 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
Transaction::Tendermint(tx) => {
|
Transaction::Tendermint(tx) => {
|
||||||
res.txs.insert(hash, Transaction::Tendermint(tx));
|
res.txs.insert(hash, Transaction::Tendermint(tx));
|
||||||
}
|
}
|
||||||
Transaction::Application(tx) => {
|
Transaction::Application(tx) => match tx.kind() {
|
||||||
match tx.kind() {
|
TransactionKind::Signed(order, Signed { signer, nonce, .. }) => {
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
let amount = *res.txs_per_signer.get(signer).unwrap_or(&0) + 1;
|
||||||
if let Some(prev) = res.next_nonces.insert(*signer, nonce + 1) {
|
res.txs_per_signer.insert(*signer, amount);
|
||||||
// These mempool additions should've been ordered
|
|
||||||
debug_assert!(prev < *nonce);
|
if let Some(prior_nonce) =
|
||||||
|
res.last_nonce_in_mempool.insert((*signer, order.clone()), *nonce)
|
||||||
|
{
|
||||||
|
assert_eq!(prior_nonce, nonce - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.txs.insert(hash, Transaction::Application(tx));
|
res.txs.insert(hash, Transaction::Application(tx));
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {
|
TransactionKind::Unsigned => {
|
||||||
res.txs.insert(hash, Transaction::Application(tx));
|
res.txs.insert(hash, Transaction::Application(tx));
|
||||||
}
|
}
|
||||||
_ => panic!("mempool database had a provided transaction"),
|
_ => panic!("mempool database had a provided transaction"),
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +105,12 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns Ok(true) if new, Ok(false) if an already present unsigned, or the error.
|
// Returns Ok(true) if new, Ok(false) if an already present unsigned, or the error.
|
||||||
pub(crate) fn add<N: Network>(
|
pub(crate) fn add<
|
||||||
|
N: Network,
|
||||||
|
F: FnOnce(<Ristretto as Ciphersuite>::G, Vec<u8>) -> Option<u32>,
|
||||||
|
>(
|
||||||
&mut self,
|
&mut self,
|
||||||
blockchain_next_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
blockchain_next_nonce: F,
|
||||||
internal: bool,
|
internal: bool,
|
||||||
tx: Transaction<T>,
|
tx: Transaction<T>,
|
||||||
schema: N::SignatureScheme,
|
schema: N::SignatureScheme,
|
||||||
|
@ -119,32 +132,31 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
}
|
}
|
||||||
Transaction::Application(app_tx) => {
|
Transaction::Application(app_tx) => {
|
||||||
match app_tx.kind() {
|
match app_tx.kind() {
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(order, Signed { signer, .. }) => {
|
||||||
// Get the nonce from the blockchain
|
// Get the nonce from the blockchain
|
||||||
let Some(blockchain_next_nonce) = blockchain_next_nonces.get(signer).cloned() else {
|
let Some(blockchain_next_nonce) = blockchain_next_nonce(*signer, order.clone()) else {
|
||||||
// Not a participant
|
// Not a participant
|
||||||
Err(TransactionError::InvalidSigner)?
|
Err(TransactionError::InvalidSigner)?
|
||||||
};
|
};
|
||||||
|
let mut next_nonce = blockchain_next_nonce;
|
||||||
|
|
||||||
// If the blockchain's nonce is greater than the mempool's, use it
|
if let Some(mempool_last_nonce) =
|
||||||
// Default to true so if the mempool hasn't tracked this nonce yet, it'll be inserted
|
self.last_nonce_in_mempool.get(&(*signer, order.clone()))
|
||||||
let mut blockchain_is_greater = true;
|
{
|
||||||
if let Some(mempool_next_nonce) = self.next_nonces.get(signer) {
|
assert!(*mempool_last_nonce >= blockchain_next_nonce);
|
||||||
blockchain_is_greater = blockchain_next_nonce > *mempool_next_nonce;
|
next_nonce = *mempool_last_nonce + 1;
|
||||||
}
|
|
||||||
|
|
||||||
if blockchain_is_greater {
|
|
||||||
self.next_nonces.insert(*signer, blockchain_next_nonce);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have too many transactions from this sender, don't add this yet UNLESS we are
|
// If we have too many transactions from this sender, don't add this yet UNLESS we are
|
||||||
// this sender
|
// this sender
|
||||||
if !internal && (nonce >= &(blockchain_next_nonce + ACCOUNT_MEMPOOL_LIMIT)) {
|
let amount_in_pool = *self.txs_per_signer.get(signer).unwrap_or(&0) + 1;
|
||||||
|
if !internal && (amount_in_pool > ACCOUNT_MEMPOOL_LIMIT) {
|
||||||
Err(TransactionError::TooManyInMempool)?;
|
Err(TransactionError::TooManyInMempool)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
verify_transaction(app_tx, self.genesis, &mut self.next_nonces)?;
|
verify_transaction(app_tx, self.genesis, &mut |_, _| Some(next_nonce))?;
|
||||||
debug_assert_eq!(self.next_nonces[signer], nonce + 1);
|
self.last_nonce_in_mempool.insert((*signer, order.clone()), next_nonce);
|
||||||
|
self.txs_per_signer.insert(*signer, amount_in_pool);
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {
|
TransactionKind::Unsigned => {
|
||||||
// check we have the tx in the pool/chain
|
// check we have the tx in the pool/chain
|
||||||
|
@ -165,38 +177,26 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns None if the mempool doesn't have a nonce tracked.
|
// Returns None if the mempool doesn't have a nonce tracked.
|
||||||
pub(crate) fn next_nonce(&self, signer: &<Ristretto as Ciphersuite>::G) -> Option<u32> {
|
pub(crate) fn next_nonce_in_mempool(
|
||||||
self.next_nonces.get(signer).cloned()
|
&self,
|
||||||
|
signer: &<Ristretto as Ciphersuite>::G,
|
||||||
|
order: Vec<u8>,
|
||||||
|
) -> Option<u32> {
|
||||||
|
self.last_nonce_in_mempool.get(&(*signer, order)).cloned().map(|nonce| nonce + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get transactions to include in a block.
|
/// Get transactions to include in a block.
|
||||||
pub(crate) fn block(
|
pub(crate) fn block(&mut self) -> Vec<Transaction<T>> {
|
||||||
&mut self,
|
|
||||||
blockchain_next_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
|
||||||
unsigned_in_chain: impl Fn([u8; 32]) -> bool,
|
|
||||||
) -> Vec<Transaction<T>> {
|
|
||||||
let mut unsigned = vec![];
|
let mut unsigned = vec![];
|
||||||
let mut signed = vec![];
|
let mut signed = vec![];
|
||||||
for hash in self.txs.keys().cloned().collect::<Vec<_>>() {
|
for hash in self.txs.keys().cloned().collect::<Vec<_>>() {
|
||||||
let tx = &self.txs[&hash];
|
let tx = &self.txs[&hash];
|
||||||
|
|
||||||
// Verify this hasn't gone stale
|
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(_, Signed { .. }) => {
|
||||||
if blockchain_next_nonces[signer] > *nonce {
|
|
||||||
self.remove(&hash);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since this TX isn't stale, include it
|
|
||||||
signed.push(tx.clone());
|
signed.push(tx.clone());
|
||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {
|
TransactionKind::Unsigned => {
|
||||||
if unsigned_in_chain(hash) {
|
|
||||||
self.remove(&hash);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned.push(tx.clone());
|
unsigned.push(tx.clone());
|
||||||
}
|
}
|
||||||
_ => panic!("provided transaction entered mempool"),
|
_ => panic!("provided transaction entered mempool"),
|
||||||
|
@ -205,7 +205,7 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
|
|
||||||
// Sort signed by nonce
|
// Sort signed by nonce
|
||||||
let nonce = |tx: &Transaction<T>| {
|
let nonce = |tx: &Transaction<T>| {
|
||||||
if let TransactionKind::Signed(Signed { nonce, .. }) = tx.kind() {
|
if let TransactionKind::Signed(_, Signed { nonce, .. }) = tx.kind() {
|
||||||
*nonce
|
*nonce
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
|
@ -242,7 +242,16 @@ impl<D: Db, T: TransactionTrait> Mempool<D, T> {
|
||||||
}
|
}
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
self.txs.remove(tx);
|
if let Some(tx) = self.txs.remove(tx) {
|
||||||
|
if let TransactionKind::Signed(order, Signed { signer, nonce, .. }) = tx.kind() {
|
||||||
|
let amount = *self.txs_per_signer.get(signer).unwrap() - 1;
|
||||||
|
self.txs_per_signer.insert(*signer, amount);
|
||||||
|
|
||||||
|
if self.last_nonce_in_mempool.get(&(*signer, order.clone())) == Some(nonce) {
|
||||||
|
self.last_nonce_in_mempool.remove(&(*signer, order));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl<D: Db, T: Transaction> ProvidedTransactions<D, T> {
|
||||||
pub(crate) fn provide(&mut self, tx: T) -> Result<(), ProvidedError> {
|
pub(crate) fn provide(&mut self, tx: T) -> Result<(), ProvidedError> {
|
||||||
let TransactionKind::Provided(order) = tx.kind() else { Err(ProvidedError::NotProvided)? };
|
let TransactionKind::Provided(order) = tx.kind() else { Err(ProvidedError::NotProvided)? };
|
||||||
|
|
||||||
match verify_transaction(&tx, self.genesis, &mut HashMap::new()) {
|
match verify_transaction(&tx, self.genesis, &mut |_, _| None) {
|
||||||
Ok(()) => {}
|
Ok(()) => {}
|
||||||
Err(e) => Err(ProvidedError::InvalidProvided(e))?,
|
Err(e) => Err(ProvidedError::InvalidProvided(e))?,
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl ReadWrite for NonceTransaction {
|
||||||
|
|
||||||
impl TransactionTrait for NonceTransaction {
|
impl TransactionTrait for NonceTransaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind<'_> {
|
||||||
TransactionKind::Signed(&self.2)
|
TransactionKind::Signed(vec![], &self.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(&self) -> [u8; 32] {
|
fn hash(&self) -> [u8; 32] {
|
||||||
|
@ -84,11 +84,11 @@ fn empty_block() {
|
||||||
let unsigned_in_chain = |_: [u8; 32]| false;
|
let unsigned_in_chain = |_: [u8; 32]| false;
|
||||||
let provided_in_chain = |_: [u8; 32]| false;
|
let provided_in_chain = |_: [u8; 32]| false;
|
||||||
Block::<NonceTransaction>::new(LAST, vec![], vec![])
|
Block::<NonceTransaction>::new(LAST, vec![], vec![])
|
||||||
.verify::<N>(
|
.verify::<N, _>(
|
||||||
GENESIS,
|
GENESIS,
|
||||||
LAST,
|
LAST,
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
HashMap::new(),
|
&mut |_, _| None,
|
||||||
validators,
|
validators,
|
||||||
commit,
|
commit,
|
||||||
unsigned_in_chain,
|
unsigned_in_chain,
|
||||||
|
@ -119,11 +119,16 @@ fn duplicate_nonces() {
|
||||||
let unsigned_in_chain = |_: [u8; 32]| false;
|
let unsigned_in_chain = |_: [u8; 32]| false;
|
||||||
let provided_in_chain = |_: [u8; 32]| false;
|
let provided_in_chain = |_: [u8; 32]| false;
|
||||||
|
|
||||||
let res = Block::new(LAST, vec![], mempool).verify::<N>(
|
let mut last_nonce = 0;
|
||||||
|
let res = Block::new(LAST, vec![], mempool).verify::<N, _>(
|
||||||
GENESIS,
|
GENESIS,
|
||||||
LAST,
|
LAST,
|
||||||
HashMap::new(),
|
HashMap::new(),
|
||||||
HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]),
|
&mut |_, _| {
|
||||||
|
let res = last_nonce;
|
||||||
|
last_nonce += 1;
|
||||||
|
Some(res)
|
||||||
|
},
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
commit,
|
commit,
|
||||||
unsigned_in_chain,
|
unsigned_in_chain,
|
||||||
|
|
|
@ -156,7 +156,7 @@ fn signed_transaction() {
|
||||||
let signer = tx.1.signer;
|
let signer = tx.1.signer;
|
||||||
|
|
||||||
let (_, mut blockchain) = new_blockchain::<SignedTransaction>(genesis, &[signer]);
|
let (_, mut blockchain) = new_blockchain::<SignedTransaction>(genesis, &[signer]);
|
||||||
assert_eq!(blockchain.next_nonce(signer), Some(0));
|
assert_eq!(blockchain.next_nonce(&signer, &[]), Some(0));
|
||||||
|
|
||||||
let test = |blockchain: &mut Blockchain<MemDb, SignedTransaction>,
|
let test = |blockchain: &mut Blockchain<MemDb, SignedTransaction>,
|
||||||
mempool: Vec<Transaction<SignedTransaction>>| {
|
mempool: Vec<Transaction<SignedTransaction>>| {
|
||||||
|
@ -165,11 +165,11 @@ fn signed_transaction() {
|
||||||
let Transaction::Application(tx) = tx else {
|
let Transaction::Application(tx) = tx else {
|
||||||
panic!("tendermint tx found");
|
panic!("tendermint tx found");
|
||||||
};
|
};
|
||||||
let next_nonce = blockchain.next_nonce(signer).unwrap();
|
let next_nonce = blockchain.next_nonce(&signer, &[]).unwrap();
|
||||||
blockchain
|
blockchain
|
||||||
.add_transaction::<N>(true, Transaction::Application(tx), validators.clone())
|
.add_transaction::<N>(true, Transaction::Application(tx), validators.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(next_nonce + 1, blockchain.next_nonce(signer).unwrap());
|
assert_eq!(next_nonce + 1, blockchain.next_nonce(&signer, &[]).unwrap());
|
||||||
}
|
}
|
||||||
let block = blockchain.build_block::<N>(validators.clone());
|
let block = blockchain.build_block::<N>(validators.clone());
|
||||||
assert_eq!(block, Block::new(blockchain.tip(), vec![], mempool.clone()));
|
assert_eq!(block, Block::new(blockchain.tip(), vec![], mempool.clone()));
|
||||||
|
@ -192,7 +192,7 @@ fn signed_transaction() {
|
||||||
|
|
||||||
// Test with a single nonce
|
// Test with a single nonce
|
||||||
test(&mut blockchain, vec![Transaction::Application(tx)]);
|
test(&mut blockchain, vec![Transaction::Application(tx)]);
|
||||||
assert_eq!(blockchain.next_nonce(signer), Some(1));
|
assert_eq!(blockchain.next_nonce(&signer, &[]), Some(1));
|
||||||
|
|
||||||
// Test with a flood of nonces
|
// Test with a flood of nonces
|
||||||
let mut mempool = vec![];
|
let mut mempool = vec![];
|
||||||
|
@ -202,7 +202,7 @@ fn signed_transaction() {
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
test(&mut blockchain, mempool);
|
test(&mut blockchain, mempool);
|
||||||
assert_eq!(blockchain.next_nonce(signer), Some(64));
|
assert_eq!(blockchain.next_nonce(&signer, &[]), Some(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -36,16 +36,15 @@ async fn mempool_addition() {
|
||||||
|
|
||||||
let first_tx = signed_transaction(&mut OsRng, genesis, &key, 0);
|
let first_tx = signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
let signer = first_tx.1.signer;
|
let signer = first_tx.1.signer;
|
||||||
assert_eq!(mempool.next_nonce(&signer), None);
|
assert_eq!(mempool.next_nonce_in_mempool(&signer, vec![]), None);
|
||||||
|
|
||||||
// validators
|
// validators
|
||||||
let validators = Arc::new(Validators::new(genesis, vec![(signer, 1)]).unwrap());
|
let validators = Arc::new(Validators::new(genesis, vec![(signer, 1)]).unwrap());
|
||||||
|
|
||||||
// Add TX 0
|
// Add TX 0
|
||||||
let mut blockchain_next_nonces = HashMap::from([(signer, 0)]);
|
|
||||||
assert!(mempool
|
assert!(mempool
|
||||||
.add::<N>(
|
.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| Some(0),
|
||||||
true,
|
true,
|
||||||
Transaction::Application(first_tx.clone()),
|
Transaction::Application(first_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -53,15 +52,15 @@ async fn mempool_addition() {
|
||||||
commit,
|
commit,
|
||||||
)
|
)
|
||||||
.unwrap());
|
.unwrap());
|
||||||
assert_eq!(mempool.next_nonce(&signer), Some(1));
|
assert_eq!(mempool.next_nonce_in_mempool(&signer, vec![]), Some(1));
|
||||||
|
|
||||||
// add a tendermint evidence tx
|
// add a tendermint evidence tx
|
||||||
let evidence_tx =
|
let evidence_tx =
|
||||||
random_evidence_tx::<N>(Signer::new(genesis, key.clone()).into(), TendermintBlock(vec![]))
|
random_evidence_tx::<N>(Signer::new(genesis, key.clone()).into(), TendermintBlock(vec![]))
|
||||||
.await;
|
.await;
|
||||||
assert!(mempool
|
assert!(mempool
|
||||||
.add::<N>(
|
.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| None,
|
||||||
true,
|
true,
|
||||||
Transaction::Tendermint(evidence_tx.clone()),
|
Transaction::Tendermint(evidence_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -75,8 +74,8 @@ async fn mempool_addition() {
|
||||||
|
|
||||||
// Adding them again should fail
|
// Adding them again should fail
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.add::<N>(
|
mempool.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| Some(0),
|
||||||
true,
|
true,
|
||||||
Transaction::Application(first_tx.clone()),
|
Transaction::Application(first_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -86,8 +85,8 @@ async fn mempool_addition() {
|
||||||
Err(TransactionError::InvalidNonce)
|
Err(TransactionError::InvalidNonce)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.add::<N>(
|
mempool.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| None,
|
||||||
true,
|
true,
|
||||||
Transaction::Tendermint(evidence_tx.clone()),
|
Transaction::Tendermint(evidence_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -100,8 +99,8 @@ async fn mempool_addition() {
|
||||||
// Do the same with the next nonce
|
// Do the same with the next nonce
|
||||||
let second_tx = signed_transaction(&mut OsRng, genesis, &key, 1);
|
let second_tx = signed_transaction(&mut OsRng, genesis, &key, 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.add::<N>(
|
mempool.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| Some(0),
|
||||||
true,
|
true,
|
||||||
Transaction::Application(second_tx.clone()),
|
Transaction::Application(second_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -110,10 +109,10 @@ async fn mempool_addition() {
|
||||||
),
|
),
|
||||||
Ok(true)
|
Ok(true)
|
||||||
);
|
);
|
||||||
assert_eq!(mempool.next_nonce(&signer), Some(2));
|
assert_eq!(mempool.next_nonce_in_mempool(&signer, vec![]), Some(2));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.add::<N>(
|
mempool.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| Some(0),
|
||||||
true,
|
true,
|
||||||
Transaction::Application(second_tx.clone()),
|
Transaction::Application(second_tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -128,11 +127,10 @@ async fn mempool_addition() {
|
||||||
let second_key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
let second_key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
let tx = signed_transaction(&mut OsRng, genesis, &second_key, 2);
|
let tx = signed_transaction(&mut OsRng, genesis, &second_key, 2);
|
||||||
let second_signer = tx.1.signer;
|
let second_signer = tx.1.signer;
|
||||||
assert_eq!(mempool.next_nonce(&second_signer), None);
|
assert_eq!(mempool.next_nonce_in_mempool(&second_signer, vec![]), None);
|
||||||
blockchain_next_nonces.insert(second_signer, 2);
|
|
||||||
assert!(mempool
|
assert!(mempool
|
||||||
.add::<N>(
|
.add::<N, _>(
|
||||||
&blockchain_next_nonces,
|
&|_, _| Some(2),
|
||||||
true,
|
true,
|
||||||
Transaction::Application(tx.clone()),
|
Transaction::Application(tx.clone()),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -140,24 +138,18 @@ async fn mempool_addition() {
|
||||||
commit
|
commit
|
||||||
)
|
)
|
||||||
.unwrap());
|
.unwrap());
|
||||||
assert_eq!(mempool.next_nonce(&second_signer), Some(3));
|
assert_eq!(mempool.next_nonce_in_mempool(&second_signer, vec![]), Some(3));
|
||||||
|
|
||||||
// Getting a block should work
|
// Getting a block should work
|
||||||
assert_eq!(mempool.block(&blockchain_next_nonces, unsigned_in_chain).len(), 4);
|
assert_eq!(mempool.block().len(), 4);
|
||||||
|
|
||||||
// If the blockchain says an account had its nonce updated, it should cause a prune
|
// Removing should successfully prune
|
||||||
blockchain_next_nonces.insert(signer, 1);
|
|
||||||
let mut block = mempool.block(&blockchain_next_nonces, unsigned_in_chain);
|
|
||||||
assert_eq!(block.len(), 3);
|
|
||||||
assert!(!block.iter().any(|tx| tx.hash() == first_tx.hash()));
|
|
||||||
assert_eq!(mempool.txs(), &block.drain(..).map(|tx| (tx.hash(), tx)).collect::<HashMap<_, _>>());
|
|
||||||
|
|
||||||
// Removing should also successfully prune
|
|
||||||
mempool.remove(&tx.hash());
|
mempool.remove(&tx.hash());
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.txs(),
|
mempool.txs(),
|
||||||
&HashMap::from([
|
&HashMap::from([
|
||||||
|
(first_tx.hash(), Transaction::Application(first_tx)),
|
||||||
(second_tx.hash(), Transaction::Application(second_tx)),
|
(second_tx.hash(), Transaction::Application(second_tx)),
|
||||||
(evidence_tx.hash(), Transaction::Tendermint(evidence_tx))
|
(evidence_tx.hash(), Transaction::Tendermint(evidence_tx))
|
||||||
])
|
])
|
||||||
|
@ -173,13 +165,12 @@ fn too_many_mempool() {
|
||||||
};
|
};
|
||||||
let unsigned_in_chain = |_: [u8; 32]| false;
|
let unsigned_in_chain = |_: [u8; 32]| false;
|
||||||
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
let signer = signed_transaction(&mut OsRng, genesis, &key, 0).1.signer;
|
|
||||||
|
|
||||||
// We should be able to add transactions up to the limit
|
// We should be able to add transactions up to the limit
|
||||||
for i in 0 .. ACCOUNT_MEMPOOL_LIMIT {
|
for i in 0 .. ACCOUNT_MEMPOOL_LIMIT {
|
||||||
assert!(mempool
|
assert!(mempool
|
||||||
.add::<N>(
|
.add::<N, _>(
|
||||||
&HashMap::from([(signer, 0)]),
|
&|_, _| Some(0),
|
||||||
false,
|
false,
|
||||||
Transaction::Application(signed_transaction(&mut OsRng, genesis, &key, i)),
|
Transaction::Application(signed_transaction(&mut OsRng, genesis, &key, i)),
|
||||||
validators.clone(),
|
validators.clone(),
|
||||||
|
@ -190,8 +181,8 @@ fn too_many_mempool() {
|
||||||
}
|
}
|
||||||
// Yet adding more should fail
|
// Yet adding more should fail
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mempool.add::<N>(
|
mempool.add::<N, _>(
|
||||||
&HashMap::from([(signer, 0)]),
|
&|_, _| Some(0),
|
||||||
false,
|
false,
|
||||||
Transaction::Application(signed_transaction(
|
Transaction::Application(signed_transaction(
|
||||||
&mut OsRng,
|
&mut OsRng,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std::{sync::Arc, io, collections::HashMap};
|
use std::{sync::Arc, io};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
use rand::{RngCore, CryptoRng, rngs::OsRng};
|
use rand::{RngCore, CryptoRng, rngs::OsRng};
|
||||||
|
@ -114,7 +114,7 @@ impl ReadWrite for SignedTransaction {
|
||||||
|
|
||||||
impl Transaction for SignedTransaction {
|
impl Transaction for SignedTransaction {
|
||||||
fn kind(&self) -> TransactionKind<'_> {
|
fn kind(&self) -> TransactionKind<'_> {
|
||||||
TransactionKind::Signed(&self.1)
|
TransactionKind::Signed(vec![], &self.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash(&self) -> [u8; 32] {
|
fn hash(&self) -> [u8; 32] {
|
||||||
|
@ -145,9 +145,7 @@ pub fn signed_transaction<R: RngCore + CryptoRng>(
|
||||||
tx.1.signature.R = Ristretto::generator() * sig_nonce.deref();
|
tx.1.signature.R = Ristretto::generator() * sig_nonce.deref();
|
||||||
tx.1.signature = SchnorrSignature::sign(key, sig_nonce, tx.sig_hash(genesis));
|
tx.1.signature = SchnorrSignature::sign(key, sig_nonce, tx.sig_hash(genesis));
|
||||||
|
|
||||||
let mut nonces = HashMap::from([(signer, nonce)]);
|
verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).unwrap();
|
||||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
|
||||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
|
||||||
|
|
||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
use blake2::{Digest, Blake2s256};
|
use blake2::{Digest, Blake2s256};
|
||||||
|
@ -35,29 +33,23 @@ fn signed_transaction() {
|
||||||
// Mutate various properties and verify it no longer works
|
// Mutate various properties and verify it no longer works
|
||||||
|
|
||||||
// Different genesis
|
// Different genesis
|
||||||
assert!(verify_transaction(
|
assert!(verify_transaction(&tx, Blake2s256::digest(genesis).into(), &mut |_, _| Some(
|
||||||
&tx,
|
tx.1.nonce
|
||||||
Blake2s256::digest(genesis).into(),
|
))
|
||||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
|
||||||
)
|
|
||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
// Different data
|
// Different data
|
||||||
{
|
{
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
tx.0 = Blake2s256::digest(tx.0).to_vec();
|
tx.0 = Blake2s256::digest(tx.0).to_vec();
|
||||||
assert!(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different signer
|
// Different signer
|
||||||
{
|
{
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
tx.1.signer += Ristretto::generator();
|
tx.1.signer += Ristretto::generator();
|
||||||
assert!(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different nonce
|
// Different nonce
|
||||||
|
@ -65,41 +57,28 @@ fn signed_transaction() {
|
||||||
#[allow(clippy::redundant_clone)] // False positive?
|
#[allow(clippy::redundant_clone)] // False positive?
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
tx.1.nonce = tx.1.nonce.wrapping_add(1);
|
tx.1.nonce = tx.1.nonce.wrapping_add(1);
|
||||||
assert!(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Different signature
|
// Different signature
|
||||||
{
|
{
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
tx.1.signature.R += Ristretto::generator();
|
tx.1.signature.R += Ristretto::generator();
|
||||||
assert!(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut tx = tx.clone();
|
let mut tx = tx.clone();
|
||||||
tx.1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
tx.1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||||
assert!(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check the original TX was never mutated and is valid
|
// Sanity check the original TX was never mutated and is valid
|
||||||
let mut nonces = HashMap::from([(tx.1.signer, tx.1.nonce)]);
|
verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).unwrap();
|
||||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
|
||||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_nonce() {
|
fn invalid_nonce() {
|
||||||
let (genesis, tx) = random_signed_transaction(&mut OsRng);
|
let (genesis, tx) = random_signed_transaction(&mut OsRng);
|
||||||
|
|
||||||
assert!(verify_transaction(
|
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce.wrapping_add(1)),).is_err());
|
||||||
&tx,
|
|
||||||
genesis,
|
|
||||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]),
|
|
||||||
)
|
|
||||||
.is_err());
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{io, collections::HashMap};
|
use std::io;
|
||||||
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -82,7 +82,7 @@ impl ReadWrite for Signed {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Signed {
|
impl Signed {
|
||||||
fn read_without_nonce<R: io::Read>(reader: &mut R, nonce: u32) -> io::Result<Self> {
|
pub fn read_without_nonce<R: io::Read>(reader: &mut R, nonce: u32) -> io::Result<Self> {
|
||||||
let signer = Ristretto::read_G(reader)?;
|
let signer = Ristretto::read_G(reader)?;
|
||||||
|
|
||||||
let mut signature = SchnorrSignature::<Ristretto>::read(reader)?;
|
let mut signature = SchnorrSignature::<Ristretto>::read(reader)?;
|
||||||
|
@ -97,7 +97,7 @@ impl Signed {
|
||||||
Ok(Signed { signer, nonce, signature })
|
Ok(Signed { signer, nonce, signature })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_without_nonce<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
pub fn write_without_nonce<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
// This is either an invalid signature or a private key leak
|
// This is either an invalid signature or a private key leak
|
||||||
if self.signature.R.is_identity().into() {
|
if self.signature.R.is_identity().into() {
|
||||||
Err(io::Error::other("signature nonce was identity"))?;
|
Err(io::Error::other("signature nonce was identity"))?;
|
||||||
|
@ -132,7 +132,7 @@ pub enum TransactionKind<'a> {
|
||||||
Unsigned,
|
Unsigned,
|
||||||
|
|
||||||
/// A signed transaction.
|
/// A signed transaction.
|
||||||
Signed(&'a Signed),
|
Signed(Vec<u8>, &'a Signed),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should this be renamed TransactionTrait now that a literal Transaction exists?
|
// TODO: Should this be renamed TransactionTrait now that a literal Transaction exists?
|
||||||
|
@ -156,13 +156,14 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
||||||
/// Panics if called on non-signed transactions.
|
/// Panics if called on non-signed transactions.
|
||||||
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
fn sig_hash(&self, genesis: [u8; 32]) -> <Ristretto as Ciphersuite>::F {
|
||||||
match self.kind() {
|
match self.kind() {
|
||||||
TransactionKind::Signed(Signed { signature, .. }) => {
|
TransactionKind::Signed(order, Signed { signature, .. }) => {
|
||||||
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
<Ristretto as Ciphersuite>::F::from_bytes_mod_order_wide(
|
||||||
&Blake2b512::digest(
|
&Blake2b512::digest(
|
||||||
[
|
[
|
||||||
b"Tributary Signed Transaction",
|
b"Tributary Signed Transaction",
|
||||||
genesis.as_ref(),
|
genesis.as_ref(),
|
||||||
&self.hash(),
|
&self.hash(),
|
||||||
|
order.as_ref(),
|
||||||
signature.R.to_bytes().as_ref(),
|
signature.R.to_bytes().as_ref(),
|
||||||
]
|
]
|
||||||
.concat(),
|
.concat(),
|
||||||
|
@ -175,11 +176,14 @@ pub trait Transaction: 'static + Send + Sync + Clone + Eq + Debug + ReadWrite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait GAIN: FnMut(&<Ristretto as Ciphersuite>::G, &[u8]) -> Option<u32> {}
|
||||||
|
impl<F: FnMut(&<Ristretto as Ciphersuite>::G, &[u8]) -> Option<u32>> GAIN for F {}
|
||||||
|
|
||||||
// This will only cause mutations when the transaction is valid
|
// This will only cause mutations when the transaction is valid
|
||||||
pub(crate) fn verify_transaction<T: Transaction>(
|
pub(crate) fn verify_transaction<F: GAIN, T: Transaction>(
|
||||||
tx: &T,
|
tx: &T,
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
next_nonces: &mut HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
get_and_increment_nonce: &mut F,
|
||||||
) -> Result<(), TransactionError> {
|
) -> Result<(), TransactionError> {
|
||||||
if tx.serialize().len() > TRANSACTION_SIZE_LIMIT {
|
if tx.serialize().len() > TRANSACTION_SIZE_LIMIT {
|
||||||
Err(TransactionError::TooLargeTransaction)?;
|
Err(TransactionError::TooLargeTransaction)?;
|
||||||
|
@ -190,9 +194,9 @@ pub(crate) fn verify_transaction<T: Transaction>(
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Provided(_) => {}
|
TransactionKind::Provided(_) => {}
|
||||||
TransactionKind::Unsigned => {}
|
TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
TransactionKind::Signed(order, Signed { signer, nonce, signature }) => {
|
||||||
if let Some(next_nonce) = next_nonces.get(signer) {
|
if let Some(next_nonce) = get_and_increment_nonce(signer, &order) {
|
||||||
if nonce != next_nonce {
|
if *nonce != next_nonce {
|
||||||
Err(TransactionError::InvalidNonce)?;
|
Err(TransactionError::InvalidNonce)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,8 +208,6 @@ pub(crate) fn verify_transaction<T: Transaction>(
|
||||||
if !signature.verify(*signer, tx.sig_hash(genesis)) {
|
if !signature.verify(*signer, tx.sig_hash(genesis)) {
|
||||||
Err(TransactionError::InvalidSignature)?;
|
Err(TransactionError::InvalidSignature)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
next_nonces.insert(*signer, nonce + 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue