mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 20:59:23 +00:00
Add DoS limits to tributary and require provided transactions be ordered
This commit is contained in:
@@ -1,9 +1,4 @@
|
||||
use std::{
|
||||
io,
|
||||
collections::{HashSet, HashMap},
|
||||
};
|
||||
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use std::{io, collections::HashMap};
|
||||
|
||||
use blake2::{Digest, Blake2s256};
|
||||
|
||||
@@ -13,9 +8,8 @@ use ciphersuite::{
|
||||
};
|
||||
use schnorr::SchnorrSignature;
|
||||
|
||||
use crate::{
|
||||
ReadWrite, TransactionError, Signed, TransactionKind, Transaction, ProvidedTransactions, Block,
|
||||
};
|
||||
use crate::{ReadWrite, TransactionError, Signed, TransactionKind, Transaction, BlockError, Block};
|
||||
|
||||
// A transaction solely defined by its nonce and a distinguisher (to allow creating distinct TXs
|
||||
// sharing a nonce).
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
@@ -74,8 +68,8 @@ impl Transaction for NonceTransaction {
|
||||
fn empty_block() {
|
||||
const GENESIS: [u8; 32] = [0xff; 32];
|
||||
const LAST: [u8; 32] = [0x01; 32];
|
||||
Block::new(LAST, &ProvidedTransactions::<NonceTransaction>::new(), HashMap::new())
|
||||
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
||||
Block::<NonceTransaction>::new(LAST, vec![], vec![])
|
||||
.verify(GENESIS, LAST, &[], HashMap::new())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -87,51 +81,21 @@ fn duplicate_nonces() {
|
||||
// Run once without duplicating a nonce, and once with, so that's confirmed to be the faulty
|
||||
// component
|
||||
for i in [1, 0] {
|
||||
let mut mempool = HashMap::new();
|
||||
let mut insert = |tx: NonceTransaction| mempool.insert(tx.hash(), tx);
|
||||
let mut mempool = vec![];
|
||||
let mut insert = |tx: NonceTransaction| mempool.push(tx);
|
||||
insert(NonceTransaction::new(0, 0));
|
||||
insert(NonceTransaction::new(i, 1));
|
||||
|
||||
let res = Block::new(LAST, &ProvidedTransactions::new(), mempool).verify(
|
||||
let res = Block::new(LAST, vec![], mempool).verify(
|
||||
GENESIS,
|
||||
LAST,
|
||||
HashSet::new(),
|
||||
&[],
|
||||
HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]),
|
||||
);
|
||||
if i == 1 {
|
||||
res.unwrap();
|
||||
} else {
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res, Err(BlockError::TransactionError(TransactionError::InvalidNonce)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsorted_nonces() {
|
||||
let mut mempool = HashMap::new();
|
||||
// Create a large amount of nonces so the retrieval from the HashMapis effectively guaranteed to
|
||||
// be out of order
|
||||
let mut nonces = (0 .. 64).collect::<Vec<_>>();
|
||||
// Insert in a random order
|
||||
while !nonces.is_empty() {
|
||||
let nonce = nonces.swap_remove(
|
||||
usize::try_from(OsRng.next_u64() % u64::try_from(nonces.len()).unwrap()).unwrap(),
|
||||
);
|
||||
let tx = NonceTransaction::new(nonce, 0);
|
||||
mempool.insert(tx.hash(), tx);
|
||||
}
|
||||
|
||||
// Create and verify the block
|
||||
const GENESIS: [u8; 32] = [0xff; 32];
|
||||
const LAST: [u8; 32] = [0x01; 32];
|
||||
let nonces = HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]);
|
||||
Block::new(LAST, &ProvidedTransactions::new(), mempool.clone())
|
||||
.verify(GENESIS, LAST, HashSet::new(), nonces.clone())
|
||||
.unwrap();
|
||||
|
||||
let skip = NonceTransaction::new(65, 0);
|
||||
mempool.insert(skip.hash(), skip);
|
||||
assert!(Block::new(LAST, &ProvidedTransactions::new(), mempool)
|
||||
.verify(GENESIS, LAST, HashSet::new(), nonces)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
|
||||
@@ -8,7 +6,7 @@ use blake2::{Digest, Blake2s256};
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||
|
||||
use crate::{
|
||||
merkle, Signed, TransactionKind, Transaction, ProvidedTransactions, Block, Blockchain,
|
||||
merkle, Transaction, ProvidedTransactions, Block, Blockchain,
|
||||
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
||||
};
|
||||
|
||||
@@ -69,11 +67,7 @@ fn invalid_block() {
|
||||
// Not a participant
|
||||
{
|
||||
// Manually create the block to bypass build_block's checks
|
||||
let block = Block::new(
|
||||
blockchain.tip(),
|
||||
&ProvidedTransactions::new(),
|
||||
HashMap::from([(tx.hash(), tx.clone())]),
|
||||
);
|
||||
let block = Block::new(blockchain.tip(), vec![], vec![tx.clone()]);
|
||||
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||
assert!(blockchain.verify_block(&block).is_err());
|
||||
}
|
||||
@@ -83,11 +77,7 @@ fn invalid_block() {
|
||||
|
||||
// Re-run the not a participant block to make sure it now works
|
||||
{
|
||||
let block = Block::new(
|
||||
blockchain.tip(),
|
||||
&ProvidedTransactions::new(),
|
||||
HashMap::from([(tx.hash(), tx.clone())]),
|
||||
);
|
||||
let block = Block::new(blockchain.tip(), vec![], vec![tx.clone()]);
|
||||
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
}
|
||||
@@ -95,7 +85,7 @@ fn invalid_block() {
|
||||
{
|
||||
// Add a valid transaction
|
||||
let mut blockchain = blockchain.clone();
|
||||
assert!(blockchain.add_transaction(tx.clone()));
|
||||
assert!(blockchain.add_transaction(true, tx.clone()));
|
||||
let mut block = blockchain.build_block();
|
||||
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
@@ -109,15 +99,14 @@ fn invalid_block() {
|
||||
// Invalid nonce
|
||||
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 5);
|
||||
// Manually create the block to bypass build_block's checks
|
||||
let block =
|
||||
Block::new(blockchain.tip(), &ProvidedTransactions::new(), HashMap::from([(tx.hash(), tx)]));
|
||||
let block = Block::new(blockchain.tip(), vec![], vec![tx]);
|
||||
assert!(blockchain.verify_block(&block).is_err());
|
||||
}
|
||||
|
||||
{
|
||||
// Invalid signature
|
||||
let mut blockchain = blockchain;
|
||||
assert!(blockchain.add_transaction(tx));
|
||||
assert!(blockchain.add_transaction(true, tx));
|
||||
let mut block = blockchain.build_block();
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
block.transactions[0].1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||
@@ -140,49 +129,25 @@ fn signed_transaction() {
|
||||
let mut blockchain = new_blockchain::<SignedTransaction>(genesis, &[signer]);
|
||||
assert_eq!(blockchain.next_nonce(signer), Some(0));
|
||||
|
||||
let test = |blockchain: &mut Blockchain<SignedTransaction>,
|
||||
mempool: HashMap<[u8; 32], SignedTransaction>| {
|
||||
let mut hashes = mempool.keys().cloned().collect::<HashSet<_>>();
|
||||
|
||||
// These transactions do need to be added, in-order, to the mempool for the blockchain to
|
||||
// build a block off them
|
||||
{
|
||||
let mut ordered = HashMap::new();
|
||||
for (_, tx) in mempool.clone().drain() {
|
||||
let nonce = if let TransactionKind::Signed(Signed { nonce, .. }) = tx.kind() {
|
||||
*nonce
|
||||
} else {
|
||||
panic!("non-signed TX in test mempool");
|
||||
};
|
||||
ordered.insert(nonce, tx);
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
while !ordered.contains_key(&i) {
|
||||
i += 1;
|
||||
}
|
||||
for i in i .. (i + u32::try_from(ordered.len()).unwrap()) {
|
||||
assert!(blockchain.add_transaction(ordered.remove(&i).unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
let test = |blockchain: &mut Blockchain<SignedTransaction>, mempool: Vec<SignedTransaction>| {
|
||||
let tip = blockchain.tip();
|
||||
for tx in mempool.clone() {
|
||||
let next_nonce = blockchain.next_nonce(signer).unwrap();
|
||||
assert!(blockchain.add_transaction(true, tx));
|
||||
assert_eq!(next_nonce + 1, blockchain.next_nonce(signer).unwrap());
|
||||
}
|
||||
let block = blockchain.build_block();
|
||||
// The Block constructor should sort these these, and build_block should've called Block::new
|
||||
assert_eq!(block, Block::new(blockchain.tip(), &ProvidedTransactions::new(), mempool));
|
||||
assert_eq!(block, Block::new(blockchain.tip(), vec![], mempool.clone()));
|
||||
assert_eq!(blockchain.tip(), tip);
|
||||
assert_eq!(block.header.parent, tip);
|
||||
|
||||
// Make sure all transactions were included
|
||||
let mut ordered_hashes = vec![];
|
||||
assert_eq!(hashes.len(), block.transactions.len());
|
||||
for transaction in &block.transactions {
|
||||
let hash = transaction.hash();
|
||||
assert!(hashes.remove(&hash));
|
||||
ordered_hashes.push(hash);
|
||||
}
|
||||
assert_eq!(block.transactions, mempool);
|
||||
// Make sure the merkle was correct
|
||||
assert_eq!(block.header.transactions, merkle(&ordered_hashes));
|
||||
assert_eq!(
|
||||
block.header.transactions,
|
||||
merkle(&mempool.iter().map(Transaction::hash).collect::<Vec<_>>())
|
||||
);
|
||||
|
||||
// Verify and add the block
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
@@ -191,19 +156,13 @@ fn signed_transaction() {
|
||||
};
|
||||
|
||||
// Test with a single nonce
|
||||
test(&mut blockchain, HashMap::from([(tx.hash(), tx)]));
|
||||
test(&mut blockchain, vec![tx]);
|
||||
assert_eq!(blockchain.next_nonce(signer), Some(1));
|
||||
|
||||
// Test with a flood of nonces
|
||||
let mut mempool = HashMap::new();
|
||||
let mut nonces = (1 .. 64).collect::<Vec<_>>();
|
||||
// Randomize insertion order into HashMap, even though it should already have unordered iteration
|
||||
while !nonces.is_empty() {
|
||||
let nonce = nonces.swap_remove(
|
||||
usize::try_from(OsRng.next_u64() % u64::try_from(nonces.len()).unwrap()).unwrap(),
|
||||
);
|
||||
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, nonce);
|
||||
mempool.insert(tx.hash(), tx);
|
||||
let mut mempool = vec![];
|
||||
for nonce in 1 .. 64 {
|
||||
mempool.push(crate::tests::signed_transaction(&mut OsRng, genesis, &key, nonce));
|
||||
}
|
||||
test(&mut blockchain, mempool);
|
||||
assert_eq!(blockchain.next_nonce(signer), Some(64));
|
||||
@@ -214,20 +173,24 @@ fn provided_transaction() {
|
||||
let mut blockchain = new_blockchain::<ProvidedTransaction>(new_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());
|
||||
|
||||
// Non-provided transactions should fail verification
|
||||
let block = Block::new(blockchain.tip(), &txs, HashMap::new());
|
||||
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);
|
||||
blockchain.provide_transaction(tx.clone());
|
||||
blockchain.verify_block(&block).unwrap();
|
||||
|
||||
// add_block should work for verified blocks
|
||||
assert!(blockchain.add_block(&block).is_ok());
|
||||
|
||||
let block = Block::new(blockchain.tip(), &txs, HashMap::new());
|
||||
let block = Block::new(blockchain.tip(), vec![tx], vec![]);
|
||||
// The provided transaction should no longer considered provided, causing this error
|
||||
assert!(blockchain.verify_block(&block).is_err());
|
||||
// add_block should fail for unverified provided transactions if told to add them
|
||||
|
||||
@@ -6,7 +6,7 @@ use rand::{RngCore, rngs::OsRng};
|
||||
use ciphersuite::{group::ff::Field, Ciphersuite, Ristretto};
|
||||
|
||||
use crate::{
|
||||
Transaction, Mempool,
|
||||
ACCOUNT_MEMPOOL_LIMIT, Transaction, Mempool,
|
||||
tests::{SignedTransaction, signed_transaction},
|
||||
};
|
||||
|
||||
@@ -28,17 +28,17 @@ fn mempool_addition() {
|
||||
|
||||
// Add TX 0
|
||||
let mut blockchain_next_nonces = HashMap::from([(signer, 0)]);
|
||||
assert!(mempool.add(&blockchain_next_nonces, first_tx.clone()));
|
||||
assert!(mempool.add(&blockchain_next_nonces, true, first_tx.clone()));
|
||||
assert_eq!(mempool.next_nonce(&signer), Some(1));
|
||||
|
||||
// Adding it again should fail
|
||||
assert!(!mempool.add(&blockchain_next_nonces, first_tx.clone()));
|
||||
assert!(!mempool.add(&blockchain_next_nonces, true, first_tx.clone()));
|
||||
|
||||
// Do the same with the next nonce
|
||||
let second_tx = signed_transaction(&mut OsRng, genesis, &key, 1);
|
||||
assert!(mempool.add(&blockchain_next_nonces, second_tx.clone()));
|
||||
assert!(mempool.add(&blockchain_next_nonces, true, second_tx.clone()));
|
||||
assert_eq!(mempool.next_nonce(&signer), Some(2));
|
||||
assert!(!mempool.add(&blockchain_next_nonces, second_tx.clone()));
|
||||
assert!(!mempool.add(&blockchain_next_nonces, true, second_tx.clone()));
|
||||
|
||||
// If the mempool doesn't have a nonce for an account, it should successfully use the
|
||||
// blockchain's
|
||||
@@ -47,7 +47,7 @@ fn mempool_addition() {
|
||||
let second_signer = tx.1.signer;
|
||||
assert_eq!(mempool.next_nonce(&second_signer), None);
|
||||
blockchain_next_nonces.insert(second_signer, 2);
|
||||
assert!(mempool.add(&blockchain_next_nonces, tx.clone()));
|
||||
assert!(mempool.add(&blockchain_next_nonces, true, tx.clone()));
|
||||
assert_eq!(mempool.next_nonce(&second_signer), Some(3));
|
||||
|
||||
// Getting a block should work
|
||||
@@ -55,12 +55,35 @@ fn mempool_addition() {
|
||||
|
||||
// If the blockchain says an account had its nonce updated, it should cause a prune
|
||||
blockchain_next_nonces.insert(signer, 1);
|
||||
let block = mempool.block(&blockchain_next_nonces);
|
||||
let mut block = mempool.block(&blockchain_next_nonces);
|
||||
assert_eq!(block.len(), 2);
|
||||
assert!(!block.contains_key(&first_tx.hash()));
|
||||
assert_eq!(mempool.txs(), &block);
|
||||
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());
|
||||
assert_eq!(mempool.txs(), &HashMap::from([(second_tx.hash(), second_tx)]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_mempool() {
|
||||
let (genesis, mut mempool) = new_mempool::<SignedTransaction>();
|
||||
|
||||
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(
|
||||
&HashMap::from([(signer, 0)]),
|
||||
false,
|
||||
signed_transaction(&mut OsRng, genesis, &key, i)
|
||||
));
|
||||
}
|
||||
// Yet adding more should fail
|
||||
assert!(!mempool.add(
|
||||
&HashMap::from([(signer, 0)]),
|
||||
false,
|
||||
signed_transaction(&mut OsRng, genesis, &key, ACCOUNT_MEMPOOL_LIMIT)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::{
|
||||
io,
|
||||
collections::{HashSet, HashMap},
|
||||
};
|
||||
use std::{io, collections::HashMap};
|
||||
|
||||
use zeroize::Zeroizing;
|
||||
use rand::{RngCore, CryptoRng};
|
||||
@@ -19,9 +16,6 @@ use crate::{ReadWrite, Signed, TransactionError, TransactionKind, Transaction, v
|
||||
#[cfg(test)]
|
||||
mod signed;
|
||||
|
||||
#[cfg(test)]
|
||||
mod provided;
|
||||
|
||||
pub fn random_signed<R: RngCore + CryptoRng>(rng: &mut R) -> Signed {
|
||||
Signed {
|
||||
signer: <Ristretto as Ciphersuite>::G::random(&mut *rng),
|
||||
@@ -127,7 +121,7 @@ pub fn signed_transaction<R: RngCore + CryptoRng>(
|
||||
);
|
||||
|
||||
let mut nonces = HashMap::from([(signer, nonce)]);
|
||||
verify_transaction(&tx, genesis, &mut HashSet::new(), &mut nonces).unwrap();
|
||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
||||
|
||||
tx
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::{Transaction, verify_transaction, tests::random_provided_transaction};
|
||||
|
||||
#[test]
|
||||
fn provided_transaction() {
|
||||
let tx = random_provided_transaction(&mut OsRng);
|
||||
|
||||
// Make sure this works when provided
|
||||
let mut provided = HashSet::from([tx.hash()]);
|
||||
verify_transaction(&tx, [0x88; 32], &mut provided, &mut HashMap::new()).unwrap();
|
||||
assert_eq!(provided.len(), 0);
|
||||
|
||||
// Make sure this fails when not provided
|
||||
assert!(verify_transaction(&tx, [0x88; 32], &mut HashSet::new(), &mut HashMap::new()).is_err());
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{HashSet, HashMap};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
@@ -37,7 +37,6 @@ fn signed_transaction() {
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
Blake2s256::digest(genesis).into(),
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
@@ -46,26 +45,18 @@ fn signed_transaction() {
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.0 = Blake2s256::digest(tx.0).to_vec();
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
genesis,
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
// Different signer
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.1.signer += Ristretto::generator();
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
genesis,
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
// Different nonce
|
||||
@@ -73,42 +64,30 @@ 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 HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, tx.1.nonce)]),).is_err()
|
||||
);
|
||||
}
|
||||
|
||||
// Different signature
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.1.signature.R += Ristretto::generator();
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
genesis,
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
verify_transaction(&tx, genesis, &mut HashMap::from([(tx.1.signer, 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 HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce)]),
|
||||
)
|
||||
.is_err());
|
||||
assert!(
|
||||
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
|
||||
let mut nonces = HashMap::from([(tx.1.signer, tx.1.nonce)]);
|
||||
verify_transaction(&tx, genesis, &mut HashSet::new(), &mut nonces).unwrap();
|
||||
verify_transaction(&tx, genesis, &mut nonces).unwrap();
|
||||
assert_eq!(nonces, HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]));
|
||||
}
|
||||
|
||||
@@ -119,7 +98,6 @@ fn invalid_nonce() {
|
||||
assert!(verify_transaction(
|
||||
&tx,
|
||||
genesis,
|
||||
&mut HashSet::new(),
|
||||
&mut HashMap::from([(tx.1.signer, tx.1.nonce.wrapping_add(1))]),
|
||||
)
|
||||
.is_err());
|
||||
|
||||
Reference in New Issue
Block a user