consensus: check for duplicate transactions in blocks
Change the Merkle root validation logic to also check that a block does not contain duplicate transactions. This check is redundant with later double-spend checks, but is a useful defense-in-depth.
This commit is contained in:
parent
706f1fff81
commit
4906a191f9
|
@ -173,12 +173,24 @@ pub fn merkle_root_validity(
|
|||
transaction_hashes: &[transaction::Hash],
|
||||
) -> Result<(), BlockError> {
|
||||
let merkle_root = transaction_hashes.iter().cloned().collect();
|
||||
if block.header.merkle_root == merkle_root {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(BlockError::BadMerkleRoot {
|
||||
|
||||
if block.header.merkle_root != merkle_root {
|
||||
return Err(BlockError::BadMerkleRoot {
|
||||
actual: merkle_root,
|
||||
expected: block.header.merkle_root,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Bitcoin's transaction Merkle trees are malleable, allowing blocks with
|
||||
// duplicate transactions to have the same Merkle root as blocks without
|
||||
// duplicate transactions. Duplicate transactions should cause a block to be
|
||||
// rejected, as duplicate transactions imply that the block contains a
|
||||
// double-spend. As a defense-in-depth, however, we also check that there
|
||||
// are no duplicate transaction hashes, by collecting into a HashSet.
|
||||
use std::collections::HashSet;
|
||||
if transaction_hashes.len() != transaction_hashes.iter().collect::<HashSet<_>>().len() {
|
||||
return Err(BlockError::DuplicateTransaction);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -105,6 +105,9 @@ pub enum BlockError {
|
|||
expected: block::merkle::Root,
|
||||
},
|
||||
|
||||
#[error("block contains duplicate transactions")]
|
||||
DuplicateTransaction,
|
||||
|
||||
#[error("block {0:?} is already in the chain at depth {1:?}")]
|
||||
AlreadyInChain(zebra_chain::block::Hash, u32),
|
||||
|
||||
|
|
Loading…
Reference in New Issue