mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Only allow designated participants to send transactions
This commit is contained in:
@@ -11,13 +11,18 @@ pub struct Blockchain<T: Transaction> {
|
|||||||
tip: [u8; 32],
|
tip: [u8; 32],
|
||||||
provided: ProvidedTransactions<T>,
|
provided: ProvidedTransactions<T>,
|
||||||
// TODO: Mempool
|
// TODO: Mempool
|
||||||
nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
next_nonces: HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Transaction> Blockchain<T> {
|
impl<T: Transaction> Blockchain<T> {
|
||||||
pub fn new(genesis: [u8; 32]) -> Self {
|
pub fn new(genesis: [u8; 32], participants: &[<Ristretto as Ciphersuite>::G]) -> Self {
|
||||||
// TODO: Reload provided/nonces
|
// TODO: Reload provided/nonces
|
||||||
Self { genesis, tip: genesis, provided: ProvidedTransactions::new(), nonces: HashMap::new() }
|
|
||||||
|
let mut next_nonces = HashMap::new();
|
||||||
|
for participant in participants {
|
||||||
|
next_nonces.insert(*participant, 0);
|
||||||
|
}
|
||||||
|
Self { genesis, tip: genesis, provided: ProvidedTransactions::new(), next_nonces }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tip(&self) -> [u8; 32] {
|
pub fn tip(&self) -> [u8; 32] {
|
||||||
@@ -28,8 +33,9 @@ impl<T: Transaction> Blockchain<T> {
|
|||||||
self.provided.provide(tx)
|
self.provided.provide(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_nonce(&self, key: <Ristretto as Ciphersuite>::G) -> u32 {
|
/// Returns the next nonce, or None if they aren't a participant.
|
||||||
self.nonces.get(&key).cloned().unwrap_or(0)
|
pub fn next_nonce(&self, key: <Ristretto as Ciphersuite>::G) -> Option<u32> {
|
||||||
|
self.next_nonces.get(&key).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Embed mempool
|
// TODO: Embed mempool
|
||||||
@@ -45,7 +51,7 @@ impl<T: Transaction> Blockchain<T> {
|
|||||||
for provided in self.provided.transactions.keys() {
|
for provided in self.provided.transactions.keys() {
|
||||||
locally_provided.insert(*provided);
|
locally_provided.insert(*provided);
|
||||||
}
|
}
|
||||||
block.verify(self.genesis, self.tip, locally_provided, self.nonces.clone())
|
block.verify(self.genesis, self.tip, locally_provided, self.next_nonces.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a block, assuming it's valid.
|
/// Add a block, assuming it's valid.
|
||||||
@@ -61,10 +67,12 @@ impl<T: Transaction> Blockchain<T> {
|
|||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {}
|
TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||||
if let Some(prev) = self.nonces.insert(*signer, nonce + 1) {
|
let prev = self
|
||||||
if prev != *nonce {
|
.next_nonces
|
||||||
panic!("block had an invalid nonce");
|
.insert(*signer, nonce + 1)
|
||||||
}
|
.expect("block had signed transaction from non-participant");
|
||||||
|
if prev != *nonce {
|
||||||
|
panic!("block had an invalid nonce");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,18 @@ impl<T: Transaction> Mempool<T> {
|
|||||||
/// Returns true if this is a valid, new transaction.
|
/// Returns true if this is a valid, new transaction.
|
||||||
pub fn add(
|
pub fn add(
|
||||||
&mut self,
|
&mut self,
|
||||||
blockchain_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
blockchain_next_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
tx: T,
|
tx: T,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||||
// If the mempool doesn't have a nonce tracked, grab it from the blockchain
|
// If the mempool doesn't have a nonce tracked, grab it from the blockchain
|
||||||
if !self.next_nonces.contains_key(signer) {
|
if !self.next_nonces.contains_key(signer) {
|
||||||
// TODO: Same commentary here as present in verify_transaction about a whitelist
|
let Some(blockchain_next_nonces) = blockchain_next_nonces.get(signer).cloned() else {
|
||||||
self.next_nonces.insert(*signer, blockchain_nonces.get(signer).cloned().unwrap_or(0));
|
// Not a participant
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.next_nonces.insert(*signer, blockchain_next_nonces);
|
||||||
}
|
}
|
||||||
|
|
||||||
if verify_transaction(&tx, self.genesis, &mut HashSet::new(), &mut self.next_nonces)
|
if verify_transaction(&tx, self.genesis, &mut HashSet::new(), &mut self.next_nonces)
|
||||||
@@ -44,6 +47,9 @@ impl<T: Transaction> Mempool<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns None if the mempool doesn't have a nonce tracked.
|
||||||
|
// The nonce to use when signing should be:
|
||||||
|
// max(blockchain.next_nonce().unwrap(), mempool.next_nonce().unwrap_or(0))
|
||||||
pub fn next_nonce(&self, signer: &<Ristretto as Ciphersuite>::G) -> Option<u32> {
|
pub fn next_nonce(&self, signer: &<Ristretto as Ciphersuite>::G) -> Option<u32> {
|
||||||
self.next_nonces.get(signer).cloned()
|
self.next_nonces.get(signer).cloned()
|
||||||
}
|
}
|
||||||
@@ -51,7 +57,7 @@ impl<T: Transaction> Mempool<T> {
|
|||||||
/// Get transactions to include in a block.
|
/// Get transactions to include in a block.
|
||||||
pub fn block(
|
pub fn block(
|
||||||
&mut self,
|
&mut self,
|
||||||
blockchain_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
blockchain_next_nonces: &HashMap<<Ristretto as Ciphersuite>::G, u32>,
|
||||||
) -> HashMap<[u8; 32], T> {
|
) -> HashMap<[u8; 32], T> {
|
||||||
let mut res = HashMap::new();
|
let mut res = HashMap::new();
|
||||||
for hash in self.txs.keys().cloned().collect::<Vec<_>>() {
|
for hash in self.txs.keys().cloned().collect::<Vec<_>>() {
|
||||||
@@ -59,7 +65,7 @@ impl<T: Transaction> Mempool<T> {
|
|||||||
// Verify this hasn't gone stale
|
// Verify this hasn't gone stale
|
||||||
match tx.kind() {
|
match tx.kind() {
|
||||||
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
TransactionKind::Signed(Signed { signer, nonce, .. }) => {
|
||||||
if blockchain_nonces.get(signer).cloned().unwrap_or(0) > *nonce {
|
if blockchain_next_nonces[signer] > *nonce {
|
||||||
self.txs.remove(&hash);
|
self.txs.remove(&hash);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,12 +92,11 @@ fn duplicate_nonces() {
|
|||||||
insert(NonceTransaction::new(0, 0));
|
insert(NonceTransaction::new(0, 0));
|
||||||
insert(NonceTransaction::new(i, 1));
|
insert(NonceTransaction::new(i, 1));
|
||||||
|
|
||||||
let nonces = HashMap::new();
|
|
||||||
let res = Block::new(LAST, &ProvidedTransactions::new(), mempool).verify(
|
let res = Block::new(LAST, &ProvidedTransactions::new(), mempool).verify(
|
||||||
GENESIS,
|
GENESIS,
|
||||||
LAST,
|
LAST,
|
||||||
HashSet::new(),
|
HashSet::new(),
|
||||||
nonces,
|
HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]),
|
||||||
);
|
);
|
||||||
if i == 1 {
|
if i == 1 {
|
||||||
res.unwrap();
|
res.unwrap();
|
||||||
@@ -125,13 +124,14 @@ fn unsorted_nonces() {
|
|||||||
// Create and verify the block
|
// Create and verify the block
|
||||||
const GENESIS: [u8; 32] = [0xff; 32];
|
const GENESIS: [u8; 32] = [0xff; 32];
|
||||||
const LAST: [u8; 32] = [0x01; 32];
|
const LAST: [u8; 32] = [0x01; 32];
|
||||||
|
let nonces = HashMap::from([(<Ristretto as Ciphersuite>::G::identity(), 0)]);
|
||||||
Block::new(LAST, &ProvidedTransactions::new(), mempool.clone())
|
Block::new(LAST, &ProvidedTransactions::new(), mempool.clone())
|
||||||
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
.verify(GENESIS, LAST, HashSet::new(), nonces.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let skip = NonceTransaction::new(65, 0);
|
let skip = NonceTransaction::new(65, 0);
|
||||||
mempool.insert(skip.hash(), skip);
|
mempool.insert(skip.hash(), skip);
|
||||||
assert!(Block::new(LAST, &ProvidedTransactions::new(), mempool)
|
assert!(Block::new(LAST, &ProvidedTransactions::new(), mempool)
|
||||||
.verify(GENESIS, LAST, HashSet::new(), HashMap::new())
|
.verify(GENESIS, LAST, HashSet::new(), nonces)
|
||||||
.is_err());
|
.is_err());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,25 @@ use crate::{
|
|||||||
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
tests::{ProvidedTransaction, SignedTransaction, random_provided_transaction},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn new_blockchain<T: Transaction>() -> ([u8; 32], Blockchain<T>) {
|
fn new_genesis() -> [u8; 32] {
|
||||||
let mut genesis = [0; 32];
|
let mut genesis = [0; 32];
|
||||||
OsRng.fill_bytes(&mut genesis);
|
OsRng.fill_bytes(&mut genesis);
|
||||||
|
genesis
|
||||||
|
}
|
||||||
|
|
||||||
let blockchain = Blockchain::new(genesis);
|
fn new_blockchain<T: Transaction>(
|
||||||
|
genesis: [u8; 32],
|
||||||
|
participants: &[<Ristretto as Ciphersuite>::G],
|
||||||
|
) -> Blockchain<T> {
|
||||||
|
let blockchain = Blockchain::new(genesis, participants);
|
||||||
assert_eq!(blockchain.tip(), genesis);
|
assert_eq!(blockchain.tip(), genesis);
|
||||||
|
blockchain
|
||||||
(genesis, blockchain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn block_addition() {
|
fn block_addition() {
|
||||||
let (genesis, mut blockchain) = new_blockchain::<SignedTransaction>();
|
let genesis = new_genesis();
|
||||||
|
let mut blockchain = new_blockchain::<SignedTransaction>(genesis, &[]);
|
||||||
let block = blockchain.build_block(HashMap::new());
|
let block = blockchain.build_block(HashMap::new());
|
||||||
assert_eq!(block.header.parent, genesis);
|
assert_eq!(block.header.parent, genesis);
|
||||||
assert_eq!(block.header.transactions, [0; 32]);
|
assert_eq!(block.header.transactions, [0; 32]);
|
||||||
@@ -35,7 +41,8 @@ fn block_addition() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_block() {
|
fn invalid_block() {
|
||||||
let (genesis, blockchain) = new_blockchain::<SignedTransaction>();
|
let genesis = new_genesis();
|
||||||
|
let blockchain = new_blockchain::<SignedTransaction>(genesis, &[]);
|
||||||
|
|
||||||
let block = blockchain.build_block(HashMap::new());
|
let block = blockchain.build_block(HashMap::new());
|
||||||
|
|
||||||
@@ -55,10 +62,36 @@ fn invalid_block() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
|
|
||||||
|
// 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())]),
|
||||||
|
);
|
||||||
|
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||||
|
assert!(blockchain.verify_block(&block).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the rest of the tests with them as a participant
|
||||||
|
let blockchain = new_blockchain(genesis, &[tx.1.signer]);
|
||||||
|
|
||||||
|
// 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())]),
|
||||||
|
);
|
||||||
|
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||||
|
blockchain.verify_block(&block).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Add a valid transaction
|
// Add a valid transaction
|
||||||
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
|
||||||
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx.clone())]));
|
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx.clone())]));
|
||||||
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
assert_eq!(block.header.transactions, merkle(&[tx.hash()]));
|
||||||
blockchain.verify_block(&block).unwrap();
|
blockchain.verify_block(&block).unwrap();
|
||||||
@@ -79,7 +112,6 @@ fn invalid_block() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
// Invalid signature
|
// Invalid signature
|
||||||
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
|
||||||
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx)]));
|
let mut block = blockchain.build_block(HashMap::from([(tx.hash(), tx)]));
|
||||||
blockchain.verify_block(&block).unwrap();
|
blockchain.verify_block(&block).unwrap();
|
||||||
block.transactions[0].1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
block.transactions[0].1.signature.s += <Ristretto as Ciphersuite>::F::ONE;
|
||||||
@@ -93,11 +125,14 @@ fn invalid_block() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signed_transaction() {
|
fn signed_transaction() {
|
||||||
let (genesis, mut blockchain) = new_blockchain::<SignedTransaction>();
|
let genesis = new_genesis();
|
||||||
|
|
||||||
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
let key = Zeroizing::new(<Ristretto as Ciphersuite>::F::random(&mut OsRng));
|
||||||
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
let tx = crate::tests::signed_transaction(&mut OsRng, genesis, &key, 0);
|
||||||
let signer = tx.1.signer;
|
let signer = tx.1.signer;
|
||||||
assert_eq!(blockchain.next_nonce(signer), 0);
|
|
||||||
|
let mut blockchain = new_blockchain::<SignedTransaction>(genesis, &[signer]);
|
||||||
|
assert_eq!(blockchain.next_nonce(signer), Some(0));
|
||||||
|
|
||||||
let test = |blockchain: &mut Blockchain<SignedTransaction>, mempool: HashMap<_, _>| {
|
let test = |blockchain: &mut Blockchain<SignedTransaction>, mempool: HashMap<_, _>| {
|
||||||
let mut hashes = mempool.keys().cloned().collect::<HashSet<_>>();
|
let mut hashes = mempool.keys().cloned().collect::<HashSet<_>>();
|
||||||
@@ -126,7 +161,7 @@ fn signed_transaction() {
|
|||||||
|
|
||||||
// Test with a single nonce
|
// Test with a single nonce
|
||||||
test(&mut blockchain, HashMap::from([(tx.hash(), tx)]));
|
test(&mut blockchain, HashMap::from([(tx.hash(), tx)]));
|
||||||
assert_eq!(blockchain.next_nonce(signer), 1);
|
assert_eq!(blockchain.next_nonce(signer), Some(1));
|
||||||
|
|
||||||
// Test with a flood of nonces
|
// Test with a flood of nonces
|
||||||
let mut mempool = HashMap::new();
|
let mut mempool = HashMap::new();
|
||||||
@@ -140,12 +175,12 @@ fn signed_transaction() {
|
|||||||
mempool.insert(tx.hash(), tx);
|
mempool.insert(tx.hash(), tx);
|
||||||
}
|
}
|
||||||
test(&mut blockchain, mempool);
|
test(&mut blockchain, mempool);
|
||||||
assert_eq!(blockchain.next_nonce(signer), 64);
|
assert_eq!(blockchain.next_nonce(signer), Some(64));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn provided_transaction() {
|
fn provided_transaction() {
|
||||||
let (_, mut blockchain) = new_blockchain::<ProvidedTransaction>();
|
let mut blockchain = new_blockchain::<ProvidedTransaction>(new_genesis(), &[]);
|
||||||
|
|
||||||
let tx = random_provided_transaction(&mut OsRng);
|
let tx = random_provided_transaction(&mut OsRng);
|
||||||
let mut txs = ProvidedTransactions::new();
|
let mut txs = ProvidedTransactions::new();
|
||||||
|
|||||||
@@ -27,17 +27,18 @@ fn mempool_addition() {
|
|||||||
assert_eq!(mempool.next_nonce(&signer), None);
|
assert_eq!(mempool.next_nonce(&signer), None);
|
||||||
|
|
||||||
// Add TX 0
|
// Add TX 0
|
||||||
assert!(mempool.add(&HashMap::new(), first_tx.clone()));
|
let mut blockchain_next_nonces = HashMap::from([(signer, 0)]);
|
||||||
|
assert!(mempool.add(&blockchain_next_nonces, first_tx.clone()));
|
||||||
assert_eq!(mempool.next_nonce(&signer), Some(1));
|
assert_eq!(mempool.next_nonce(&signer), Some(1));
|
||||||
|
|
||||||
// Adding it again should fail
|
// Adding it again should fail
|
||||||
assert!(!mempool.add(&HashMap::new(), first_tx.clone()));
|
assert!(!mempool.add(&blockchain_next_nonces, first_tx.clone()));
|
||||||
|
|
||||||
// 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!(mempool.add(&HashMap::new(), second_tx.clone()));
|
assert!(mempool.add(&blockchain_next_nonces, second_tx.clone()));
|
||||||
assert_eq!(mempool.next_nonce(&signer), Some(2));
|
assert_eq!(mempool.next_nonce(&signer), Some(2));
|
||||||
assert!(!mempool.add(&HashMap::new(), second_tx.clone()));
|
assert!(!mempool.add(&blockchain_next_nonces, second_tx.clone()));
|
||||||
|
|
||||||
// If the mempool doesn't have a nonce for an account, it should successfully use the
|
// If the mempool doesn't have a nonce for an account, it should successfully use the
|
||||||
// blockchain's
|
// blockchain's
|
||||||
@@ -45,18 +46,16 @@ fn mempool_addition() {
|
|||||||
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(&second_signer), None);
|
||||||
let mut blockchain_nonces = HashMap::from([(second_signer, 2)]);
|
blockchain_next_nonces.insert(second_signer, 2);
|
||||||
assert!(mempool.add(&blockchain_nonces, tx.clone()));
|
assert!(mempool.add(&blockchain_next_nonces, tx.clone()));
|
||||||
assert_eq!(mempool.next_nonce(&second_signer), Some(3));
|
assert_eq!(mempool.next_nonce(&second_signer), Some(3));
|
||||||
|
|
||||||
// Getting a block should work
|
// Getting a block should work
|
||||||
let block = mempool.block(&HashMap::new());
|
assert_eq!(mempool.block(&blockchain_next_nonces).len(), 3);
|
||||||
assert_eq!(block, mempool.block(&blockchain_nonces));
|
|
||||||
assert_eq!(block.len(), 3);
|
|
||||||
|
|
||||||
// If the blockchain says an account had its nonce updated, it should cause a prune
|
// If the blockchain says an account had its nonce updated, it should cause a prune
|
||||||
blockchain_nonces.insert(signer, 1);
|
blockchain_next_nonces.insert(signer, 1);
|
||||||
let block = mempool.block(&blockchain_nonces);
|
let block = mempool.block(&blockchain_next_nonces);
|
||||||
assert_eq!(block.len(), 2);
|
assert_eq!(block.len(), 2);
|
||||||
assert!(!block.contains_key(&first_tx.hash()));
|
assert!(!block.contains_key(&first_tx.hash()));
|
||||||
assert_eq!(mempool.txs(), &block);
|
assert_eq!(mempool.txs(), &block);
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ pub trait Transaction: Send + Sync + Clone + Eq + Debug + ReadWrite {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<T: Transaction>(
|
||||||
tx: &T,
|
tx: &T,
|
||||||
genesis: [u8; 32],
|
genesis: [u8; 32],
|
||||||
@@ -108,9 +108,13 @@ pub(crate) fn verify_transaction<T: Transaction>(
|
|||||||
}
|
}
|
||||||
TransactionKind::Unsigned => {}
|
TransactionKind::Unsigned => {}
|
||||||
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
TransactionKind::Signed(Signed { signer, nonce, signature }) => {
|
||||||
// TODO: Use presence as a whitelist, erroring on lack of
|
if let Some(next_nonce) = next_nonces.get(signer) {
|
||||||
if next_nonces.get(signer).cloned().unwrap_or(0) != *nonce {
|
if nonce != next_nonce {
|
||||||
Err(TransactionError::Temporal)?;
|
Err(TransactionError::Temporal)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not a participant
|
||||||
|
Err(TransactionError::Fatal)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use Schnorr half-aggregation and a batch verification here
|
// TODO: Use Schnorr half-aggregation and a batch verification here
|
||||||
|
|||||||
Reference in New Issue
Block a user