Skip to content

Commit 394fa53

Browse files
committed
Add comprehensive tests for Coinbase type and block validation
Add thorough test coverage for the new Coinbase type and validation logic
1 parent 0edabd4 commit 394fa53

File tree

2 files changed

+156
-0
lines changed

2 files changed

+156
-0
lines changed

bitcoin/src/blockdata/block.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,10 @@ mod tests {
533533
use super::*;
534534
use crate::consensus::encode::{deserialize, serialize};
535535
use crate::pow::test_utils::{u128_to_work, u64_to_work};
536+
use crate::script::ScriptBuf;
537+
use crate::transaction::{OutPoint, Transaction, TxIn, TxOut, Txid};
536538
use crate::{block, CompactTarget, Network, TestnetVersion};
539+
use crate::{Amount, Sequence, Witness};
537540

538541
#[test]
539542
fn static_vector() {
@@ -781,6 +784,140 @@ mod tests {
781784
assert!(segwit_signal.is_signalling_soft_fork(1));
782785
assert!(!segwit_signal.is_signalling_soft_fork(2));
783786
}
787+
788+
#[test]
789+
fn block_validation_no_transactions() {
790+
let header = header();
791+
let transactions = Vec::new(); // Empty transactions
792+
793+
let block = Block::new_unchecked(header, transactions);
794+
match block.validate() {
795+
Err(InvalidBlockError::NoTransactions) => (),
796+
other => panic!("Expected NoTransactions error, got: {:?}", other),
797+
}
798+
}
799+
800+
#[test]
801+
fn block_validation_invalid_coinbase() {
802+
let header = header();
803+
804+
// Create a non-coinbase transaction (has a real previous output, not all zeros)
805+
let non_coinbase_tx = Transaction {
806+
version: primitives::transaction::Version::TWO,
807+
lock_time: crate::absolute::LockTime::ZERO,
808+
input: vec![TxIn {
809+
previous_output: OutPoint {
810+
txid: Txid::from_byte_array([1; 32]), // Not all zeros
811+
vout: 0,
812+
},
813+
script_sig: ScriptBuf::new(),
814+
sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
815+
witness: Witness::new(),
816+
}],
817+
output: vec![TxOut { value: Amount::ONE_BTC, script_pubkey: ScriptBuf::new() }],
818+
};
819+
820+
let transactions = vec![non_coinbase_tx];
821+
let block = Block::new_unchecked(header, transactions);
822+
823+
match block.validate() {
824+
Err(InvalidBlockError::InvalidCoinbase) => (),
825+
other => panic!("Expected InvalidCoinbase error, got: {:?}", other),
826+
}
827+
}
828+
829+
#[test]
830+
fn block_validation_success_with_coinbase() {
831+
use crate::constants;
832+
833+
// Use the genesis block which has a valid coinbase
834+
let genesis = constants::genesis_block(Network::Bitcoin);
835+
836+
let header = *genesis.header();
837+
let transactions = genesis.transactions().to_vec();
838+
839+
let unchecked_block = Block::new_unchecked(header, transactions);
840+
let validated_block = unchecked_block.validate();
841+
842+
assert!(validated_block.is_ok(), "Genesis block should validate successfully");
843+
}
844+
845+
#[test]
846+
fn checked_block_coinbase_method() {
847+
use crate::constants;
848+
849+
let genesis = constants::genesis_block(Network::Bitcoin);
850+
let coinbase = genesis.coinbase();
851+
852+
// Test that coinbase method returns the expected transaction
853+
let expected_txid = genesis.transactions()[0].compute_txid();
854+
assert_eq!(coinbase.compute_txid(), expected_txid);
855+
assert_eq!(coinbase.wtxid(), Wtxid::COINBASE);
856+
857+
// Test that as_inner() returns the correct transaction
858+
assert_eq!(coinbase.as_transaction(), &genesis.transactions()[0]);
859+
}
860+
861+
#[test]
862+
fn block_new_checked_validation() {
863+
use crate::constants;
864+
865+
// Test successful validation with genesis block
866+
let genesis = constants::genesis_block(Network::Bitcoin);
867+
let header = *genesis.header();
868+
let transactions = genesis.transactions().to_vec();
869+
870+
let checked_block = Block::new_checked(header, transactions.clone());
871+
assert!(checked_block.is_ok(), "Genesis block should validate via new_checked");
872+
873+
// Test validation failure with empty transactions
874+
let empty_result = Block::new_checked(header, Vec::new());
875+
match empty_result {
876+
Err(InvalidBlockError::NoTransactions) => (),
877+
other => panic!("Expected NoTransactions error, got: {:?}", other),
878+
}
879+
880+
// Test validation failure with invalid coinbase
881+
let non_coinbase_tx = Transaction {
882+
version: primitives::transaction::Version::TWO,
883+
lock_time: crate::absolute::LockTime::ZERO,
884+
input: vec![TxIn {
885+
previous_output: OutPoint {
886+
txid: Txid::from_byte_array([1; 32]), // Not all zeros
887+
vout: 0,
888+
},
889+
script_sig: ScriptBuf::new(),
890+
sequence: Sequence::ENABLE_LOCKTIME_AND_RBF,
891+
witness: Witness::new(),
892+
}],
893+
output: vec![TxOut { value: Amount::ONE_BTC, script_pubkey: ScriptBuf::new() }],
894+
};
895+
896+
let invalid_coinbase_result = Block::new_checked(header, vec![non_coinbase_tx]);
897+
match invalid_coinbase_result {
898+
Err(InvalidBlockError::InvalidCoinbase) => (),
899+
other => panic!("Expected InvalidCoinbase error, got: {:?}", other),
900+
}
901+
}
902+
903+
#[test]
904+
fn coinbase_bip34_height_with_coinbase_type() {
905+
// testnet block 100,000
906+
const BLOCK_HEX: &str = "0200000035ab154183570282ce9afc0b494c9fc6a3cfea05aa8c1add2ecc56490000000038ba3d78e4500a5a7570dbe61960398add4410d278b21cd9708e6d9743f374d544fc055227f1001c29c1ea3b0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3703a08601000427f1001c046a510100522cfabe6d6d0000000000000000000068692066726f6d20706f6f6c7365727665726aac1eeeed88ffffffff0100f2052a010000001976a914912e2b234f941f30b18afbb4fa46171214bf66c888ac00000000";
907+
let block: Block = deserialize(&hex!(BLOCK_HEX)).unwrap();
908+
let block = block.assume_checked(None);
909+
910+
// Test that BIP34 height extraction works with the Coinbase type
911+
assert_eq!(block.bip34_block_height(), Ok(100_000));
912+
913+
// Test that coinbase method returns a Coinbase type
914+
let coinbase = block.coinbase();
915+
assert!(coinbase.as_transaction().is_coinbase());
916+
917+
// Test that the coinbase transaction ID matches expected
918+
let cb_txid = "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38";
919+
assert_eq!(coinbase.compute_txid().to_string(), cb_txid);
920+
}
784921
}
785922

786923
#[cfg(bench)]

bitcoin/src/blockdata/transaction.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,25 @@ mod tests {
21272127
let pretty_txid = "0x0000000000000000000000000000000000000000000000000000000000000000";
21282128
assert_eq!(pretty_txid, format!("{:#}", &outpoint.txid));
21292129
}
2130+
2131+
#[test]
2132+
fn coinbase_assume_methods() {
2133+
use crate::constants;
2134+
use crate::network::Network;
2135+
2136+
let genesis = constants::genesis_block(Network::Bitcoin);
2137+
let coinbase_tx = &genesis.transactions()[0];
2138+
2139+
// Test that we can create a Coinbase reference using assume_coinbase_ref
2140+
let coinbase_ref = Coinbase::assume_coinbase_ref(coinbase_tx);
2141+
assert_eq!(coinbase_ref.compute_txid(), coinbase_tx.compute_txid());
2142+
assert_eq!(coinbase_ref.wtxid(), Wtxid::COINBASE);
2143+
2144+
// Test that we can create a Coinbase using assume_coinbase
2145+
let coinbase_owned = Coinbase::assume_coinbase(coinbase_tx.clone());
2146+
assert_eq!(coinbase_owned.compute_txid(), coinbase_tx.compute_txid());
2147+
assert_eq!(coinbase_owned.wtxid(), Wtxid::COINBASE);
2148+
}
21302149
}
21312150

21322151
#[cfg(bench)]

0 commit comments

Comments
 (0)