Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] Client fails on block 91722: Block hash mismatch for BIP-30 #271

Merged
merged 4 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 6 additions & 133 deletions packages/consensus/src/validation/block.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
use crate::types::utxo_set::{UtxoSet, UtxoSetTrait};
use crate::types::transaction::{OutPoint, Transaction};
use crate::codec::{Encode, TransactionCodec};
use utils::{
hash::{Digest, DigestTrait}, merkle_tree::merkle_root, double_sha256::double_sha256_byte_array,
};
use crate::validation::coinbase::is_coinbase_txid_duplicated;
use utils::{hash::Digest, merkle_tree::merkle_root, double_sha256::double_sha256_byte_array,};
use super::transaction::validate_transaction;
use core::num::traits::zero::Zero;

Expand Down Expand Up @@ -69,6 +68,10 @@ pub fn compute_and_validate_tx_data(
txids.append(txid);

if (is_coinbase) {
// skip duplicated txid (it's not possible to spend these coinbase outputs)
if (is_coinbase_txid_duplicated(txid, block_height)) {
continue;
}
let mut vout = 0;
for output in *tx
.outputs {
Expand Down Expand Up @@ -111,133 +114,3 @@ pub fn compute_and_validate_tx_data(

Result::Ok((total_fee, merkle_root(txids.span()), wtxid_root))
}

/// Validates that the block hash at certain heights matches the expected hash according to BIP-30.
///
/// This function ensures that for the two exceptional blocks affected by BIP-30 (heights 91722 and
/// 91812), the block hash matches the known hash for those heights. This prevents accepting
/// incorrect blocks at these critical heights, which could lead to security issues.
///
/// BIP-30 - Reject duplicate (https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki)
pub fn validate_bip30_block_hash(block_height: u32, block_hash: @Digest,) -> Result<(), ByteArray> {
if block_height == 91722 {
let expected_hash = DigestTrait::new(
[
0x8ed04d57,
0xf2f3cd6c,
0xa6e55569,
0xdc1654e1,
0xf219847f,
0x66e726dc,
0xa2710200,
0x00000000,
]
);
if *block_hash != expected_hash {
return Result::Err("Block hash mismatch for BIP-30 exception at height 91722");
}
Result::Ok(())
} else if block_height == 91812 {
let expected_hash = DigestTrait::new(
[
0x2f6f306f,
0xd683deb8,
0x5d9314ef,
0xfdcf36af,
0x66d9e3ac,
0xaceb79d4,
0xaed00a00,
0x00000000,
]
);
if *block_hash != expected_hash {
return Result::Err("Block hash mismatch for BIP-30 exception at height 91812");
}
Result::Ok(())
} else {
Result::Ok(())
}
}


#[cfg(test)]
mod tests {
use super::{validate_bip30_block_hash};
use utils::hash::{DigestTrait};

#[test]
fn test_bip30_block_hash() {
// Expected hash for block at height 91722
let expected_hash = DigestTrait::new(
[
0x8ed04d57,
0xf2f3cd6c,
0xa6e55569,
0xdc1654e1,
0xf219847f,
0x66e726dc,
0xa2710200,
0x00000000,
]
);
let result = validate_bip30_block_hash(91722, @expected_hash);
assert_eq!(result, Result::Ok(()));
}

#[test]
fn test_bip30_block_hash_wrong_hash() {
// Expected hash for block at height 91722
let expected_hash = DigestTrait::new(
[
0x8ed04d56,
0xf2f3cd6c,
0xa6e55569,
0xdc1654e1,
0xf219847f,
0x66e726dc,
0xa2710200,
0x00000000,
]
);
let result = validate_bip30_block_hash(91722, @expected_hash);
assert_eq!(result, Result::Err("Block hash mismatch for BIP-30 exception at height 91722"));
}

#[test]
fn test_bip30_block_hash_height_91812() {
// Expected hash for block at height 91812
let expected_hash = DigestTrait::new(
[
0x2f6f306f,
0xd683deb8,
0x5d9314ef,
0xfdcf36af,
0x66d9e3ac,
0xaceb79d4,
0xaed00a00,
0x00000000,
]
);
let result = validate_bip30_block_hash(91812, @expected_hash);
assert_eq!(result, Result::Ok(()));
}

#[test]
fn test_bip30_block_hash_wrong_hash_91812() {
// Expected hash for block at height 91812
let expected_hash = DigestTrait::new(
[
0x9ed04d56,
0xf2f3cd6c,
0xa6e55569,
0xdc1654e1,
0xf219847f,
0x66e726dc,
0xa2710200,
0x00000000,
]
);
let result = validate_bip30_block_hash(91812, @expected_hash);
assert_eq!(result, Result::Err("Block hash mismatch for BIP-30 exception at height 91812"));
}
}
58 changes: 32 additions & 26 deletions packages/consensus/src/validation/coinbase.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ const BIP_34_BLOCK_HEIGHT: u32 = 227_836;
const BIP_141_BLOCK_HEIGHT: u32 = 481_824;
const WTNS_PK_SCRIPT_LEN: u32 = 38;
const WTNS_PK_SCRIPT_PREFIX: felt252 = 116705705699821; // 0x6a24aa21a9ed
const FIRST_DUP_TXID: u256 = 0xe3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468;
const SECOND_DUP_TXID: u256 = 0xd5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599;


/// Validates coinbase transaction.
pub fn validate_coinbase(
Expand Down Expand Up @@ -172,14 +175,21 @@ fn validate_coinbase_outputs(
Result::Ok(())
}


/// Determines if the transaction outputs of a block at a given height are unspendable due to
/// BIP-30.
/// (BIP-30) Skip coinbase tx for duplicated txids
/// Only the first tx is valid, the duplicated tx is ignored
///
/// This function checks if the block height corresponds to one of the two exceptional blocks
/// where transactions with duplicate TXIDs were allowed.
pub fn is_bip30_unspendable(block_height: u32) -> bool {
block_height == 91722 || block_height == 91812
/// First txid e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468
/// at blocks 91722 and 91880
/// Second txid d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599
/// at blocks 91812 and 91842
pub fn is_coinbase_txid_duplicated(txid: Digest, block_height: u32) -> bool {
// TODO: allow duplicate transactions in case the previous instance of the transaction had no
// spendable outputs left.
if ((txid.into() == FIRST_DUP_TXID && block_height == 91880)
|| (txid.into() == SECOND_DUP_TXID && block_height == 91842)) {
return true;
}
false
}

#[cfg(test)]
Expand All @@ -188,10 +198,24 @@ mod tests {
use super::{
compute_block_reward, validate_coinbase, validate_coinbase_input,
validate_coinbase_sig_script, validate_coinbase_witness, validate_coinbase_outputs,
calculate_wtxid_commitment, is_bip30_unspendable
calculate_wtxid_commitment, is_coinbase_txid_duplicated, FIRST_DUP_TXID, SECOND_DUP_TXID,
};
use utils::{hex::{from_hex, hex_to_hash_rev}, hash::Digest};

#[test]
fn test_bip30_first_txid_dup() {
let txid: Digest = FIRST_DUP_TXID.into();
assert!(!is_coinbase_txid_duplicated(txid, 91722));
assert!(is_coinbase_txid_duplicated(txid, 91880));
}

#[test]
fn test_bip30_second_txid_dup() {
let txid: Digest = SECOND_DUP_TXID.into();
assert!(!is_coinbase_txid_duplicated(txid, 91812));
assert!(is_coinbase_txid_duplicated(txid, 91842));
}

// Ref implementation here:
// https://github.com/bitcoin/bitcoin/blob/0f68a05c084bef3e53e3f549c403bc90b1db319c/src/test/validation_tests.cpp#L24
#[test]
Expand Down Expand Up @@ -664,22 +688,4 @@ mod tests {

validate_coinbase(@tx, total_fees, block_height, wtxid_root_hash).unwrap();
}

#[test]
fn test_is_bip30_unspendable() {
let block_height = 91722;
let result = is_bip30_unspendable(block_height);

assert_eq!(result, true);

let block_height = 91812;
let result = is_bip30_unspendable(block_height);

assert_eq!(result, true);

let block_height = 9;
let result = is_bip30_unspendable(block_height);

assert_eq!(result, false);
}
}
2 changes: 2 additions & 0 deletions scripts/data/regenerate_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ light_test_cases=(
32255 # First target adjustment (32256)
57042 # Block containing pizza tx (57043)
72575 # Difficulty adjustment
91721 # Duplicate coinbase txid (91722)
116927 # Difficulty adjustment
150012 # Small Block (150013)
209999 # First halving block (210000)
Expand All @@ -45,6 +46,7 @@ full_test_cases=(
32255 # First target adjustment (32256)
57042 # Block containing pizza tx (57043)
72575 # Difficulty adjustment
91721 # Duplicate coinbase txid (91722)
116927 # Difficulty adjustment
150012 # Small Block (150013)
209999 # First halving block (210000)
Expand Down
Loading