mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 20:59:23 +00:00
Use multiple nonces in the Tributary
This commit is contained in:
@@ -61,7 +61,7 @@ impl ReadWrite for NonceTransaction {
|
||||
|
||||
impl TransactionTrait for NonceTransaction {
|
||||
fn kind(&self) -> TransactionKind<'_> {
|
||||
TransactionKind::Signed(&self.2)
|
||||
TransactionKind::Signed(vec![], &self.2)
|
||||
}
|
||||
|
||||
fn hash(&self) -> [u8; 32] {
|
||||
@@ -84,11 +84,11 @@ fn empty_block() {
|
||||
let unsigned_in_chain = |_: [u8; 32]| false;
|
||||
let provided_in_chain = |_: [u8; 32]| false;
|
||||
Block::<NonceTransaction>::new(LAST, vec![], vec![])
|
||||
.verify::<N>(
|
||||
.verify::<N, _>(
|
||||
GENESIS,
|
||||
LAST,
|
||||
HashMap::new(),
|
||||
HashMap::new(),
|
||||
&mut |_, _| None,
|
||||
validators,
|
||||
commit,
|
||||
unsigned_in_chain,
|
||||
@@ -119,11 +119,16 @@ fn duplicate_nonces() {
|
||||
let unsigned_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,
|
||||
LAST,
|
||||
HashMap::new(),
|
||||
HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]),
|
||||
&mut |_, _| {
|
||||
let res = last_nonce;
|
||||
last_nonce += 1;
|
||||
Some(res)
|
||||
},
|
||||
validators.clone(),
|
||||
commit,
|
||||
unsigned_in_chain,
|
||||
|
||||
@@ -156,7 +156,7 @@ fn signed_transaction() {
|
||||
let signer = tx.1.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>,
|
||||
mempool: Vec<Transaction<SignedTransaction>>| {
|
||||
@@ -165,11 +165,11 @@ fn signed_transaction() {
|
||||
let Transaction::Application(tx) = tx else {
|
||||
panic!("tendermint tx found");
|
||||
};
|
||||
let next_nonce = blockchain.next_nonce(signer).unwrap();
|
||||
let next_nonce = blockchain.next_nonce(&signer, &[]).unwrap();
|
||||
blockchain
|
||||
.add_transaction::<N>(true, Transaction::Application(tx), validators.clone())
|
||||
.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());
|
||||
assert_eq!(block, Block::new(blockchain.tip(), vec![], mempool.clone()));
|
||||
@@ -192,7 +192,7 @@ fn signed_transaction() {
|
||||
|
||||
// Test with a single nonce
|
||||
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
|
||||
let mut mempool = vec![];
|
||||
@@ -202,7 +202,7 @@ fn signed_transaction() {
|
||||
)));
|
||||
}
|
||||
test(&mut blockchain, mempool);
|
||||
assert_eq!(blockchain.next_nonce(signer), Some(64));
|
||||
assert_eq!(blockchain.next_nonce(&signer, &[]), Some(64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -36,16 +36,15 @@ async fn mempool_addition() {
|
||||
|
||||
let first_tx = signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||
let signer = first_tx.1.signer;
|
||||
assert_eq!(mempool.next_nonce(&signer), None);
|
||||
assert_eq!(mempool.next_nonce_in_mempool(&signer, vec![]), None);
|
||||
|
||||
// validators
|
||||
let validators = Arc::new(Validators::new(genesis, vec![(signer, 1)]).unwrap());
|
||||
|
||||
// Add TX 0
|
||||
let mut blockchain_next_nonces = HashMap::from([(signer, 0)]);
|
||||
assert!(mempool
|
||||
.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
true,
|
||||
Transaction::Application(first_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -53,15 +52,15 @@ async fn mempool_addition() {
|
||||
commit,
|
||||
)
|
||||
.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
|
||||
let evidence_tx =
|
||||
random_evidence_tx::<N>(Signer::new(genesis, key.clone()).into(), TendermintBlock(vec![]))
|
||||
.await;
|
||||
assert!(mempool
|
||||
.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
.add::<N, _>(
|
||||
&|_, _| None,
|
||||
true,
|
||||
Transaction::Tendermint(evidence_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -75,8 +74,8 @@ async fn mempool_addition() {
|
||||
|
||||
// Adding them again should fail
|
||||
assert_eq!(
|
||||
mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
mempool.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
true,
|
||||
Transaction::Application(first_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -86,8 +85,8 @@ async fn mempool_addition() {
|
||||
Err(TransactionError::InvalidNonce)
|
||||
);
|
||||
assert_eq!(
|
||||
mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
mempool.add::<N, _>(
|
||||
&|_, _| None,
|
||||
true,
|
||||
Transaction::Tendermint(evidence_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -100,8 +99,8 @@ async fn mempool_addition() {
|
||||
// Do the same with the next nonce
|
||||
let second_tx = signed_transaction(&mut OsRng, genesis, &key, 1);
|
||||
assert_eq!(
|
||||
mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
mempool.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
true,
|
||||
Transaction::Application(second_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -110,10 +109,10 @@ async fn mempool_addition() {
|
||||
),
|
||||
Ok(true)
|
||||
);
|
||||
assert_eq!(mempool.next_nonce(&signer), Some(2));
|
||||
assert_eq!(mempool.next_nonce_in_mempool(&signer, vec![]), Some(2));
|
||||
assert_eq!(
|
||||
mempool.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
mempool.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
true,
|
||||
Transaction::Application(second_tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -128,11 +127,10 @@ async fn mempool_addition() {
|
||||
let second_key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||
let tx = signed_transaction(&mut OsRng, genesis, &second_key, 2);
|
||||
let second_signer = tx.1.signer;
|
||||
assert_eq!(mempool.next_nonce(&second_signer), None);
|
||||
blockchain_next_nonces.insert(second_signer, 2);
|
||||
assert_eq!(mempool.next_nonce_in_mempool(&second_signer, vec![]), None);
|
||||
assert!(mempool
|
||||
.add::<N>(
|
||||
&blockchain_next_nonces,
|
||||
.add::<N, _>(
|
||||
&|_, _| Some(2),
|
||||
true,
|
||||
Transaction::Application(tx.clone()),
|
||||
validators.clone(),
|
||||
@@ -140,24 +138,18 @@ async fn mempool_addition() {
|
||||
commit
|
||||
)
|
||||
.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
|
||||
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
|
||||
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
|
||||
// Removing should successfully prune
|
||||
mempool.remove(&tx.hash());
|
||||
|
||||
assert_eq!(
|
||||
mempool.txs(),
|
||||
&HashMap::from([
|
||||
(first_tx.hash(), Transaction::Application(first_tx)),
|
||||
(second_tx.hash(), Transaction::Application(second_tx)),
|
||||
(evidence_tx.hash(), Transaction::Tendermint(evidence_tx))
|
||||
])
|
||||
@@ -173,13 +165,12 @@ fn too_many_mempool() {
|
||||
};
|
||||
let unsigned_in_chain = |_: [u8; 32]| false;
|
||||
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
|
||||
for i in 0 .. ACCOUNT_MEMPOOL_LIMIT {
|
||||
assert!(mempool
|
||||
.add::<N>(
|
||||
&HashMap::from([(signer, 0)]),
|
||||
.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
false,
|
||||
Transaction::Application(signed_transaction(&mut OsRng, genesis, &key, i)),
|
||||
validators.clone(),
|
||||
@@ -190,8 +181,8 @@ fn too_many_mempool() {
|
||||
}
|
||||
// Yet adding more should fail
|
||||
assert_eq!(
|
||||
mempool.add::<N>(
|
||||
&HashMap::from([(signer, 0)]),
|
||||
mempool.add::<N, _>(
|
||||
&|_, _| Some(0),
|
||||
false,
|
||||
Transaction::Application(signed_transaction(
|
||||
&mut OsRng,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use core::ops::Deref;
|
||||
use std::{sync::Arc, io, collections::HashMap};
|
||||
use std::{sync::Arc, io};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand::{RngCore, CryptoRng, rngs::OsRng};
|
||||
@@ -114,7 +114,7 @@ impl ReadWrite for SignedTransaction {
|
||||
|
||||
impl Transaction for SignedTransaction {
|
||||
fn kind(&self) -> TransactionKind<'_> {
|
||||
TransactionKind::Signed(&self.1)
|
||||
TransactionKind::Signed(vec![], &self.1)
|
||||
}
|
||||
|
||||
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 = SchnorrSignature::sign(key, sig_nonce, tx.sig_hash(genesis));
|
||||
|
||||
let mut nonces = HashMap::from([(signer, nonce)]);
|
||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
||||
verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).unwrap();
|
||||
|
||||
tx
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use blake2::{Digest, Blake2s256};
|
||||
@@ -35,29 +33,23 @@ fn signed_transaction() {
|
||||
// Mutate various properties and verify it no longer works
|
||||
|
||||
// Different genesis
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
Blake2s256::digest(genesis).into(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
assert!(verify_transaction(&tx, Blake2s256::digest(genesis).into(), &mut |_, _| Some(
|
||||
tx.1.nonce
|
||||
))
|
||||
.is_err());
|
||||
|
||||
// Different data
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.0 = Blake2s256::digest(tx.0).to_vec();
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||
}
|
||||
|
||||
// Different signer
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.1.signer += Ristretto::generator();
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||
}
|
||||
|
||||
// Different nonce
|
||||
@@ -65,41 +57,28 @@ fn signed_transaction() {
|
||||
#[allow(clippy::redundant_clone)] // False positive?
|
||||
let mut tx = tx.clone();
|
||||
tx.1.nonce = tx.1.nonce.wrapping_add(1);
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||
}
|
||||
|
||||
// Different signature
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.1.signature.R += Ristretto::generator();
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||
}
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).is_err());
|
||||
}
|
||||
|
||||
// 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 nonces).unwrap();
|
||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
||||
verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce)).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_nonce() {
|
||||
let (genesis, tx) = random_signed_transaction(&mut OsRng);
|
||||
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
genesis,
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(verify_transaction(&tx, genesis, &mut |_, _| Some(tx.1.nonce.wrapping_add(1)),).is_err());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user