Move transaction encoding tests to primitives/tests/encoding.rs
What changed, and why it matters
This commit simply moves existing transaction encoding and decoding tests from one file to another. It does not change any production code, only reorganizes test code. The only functional tweak is replacing one detailed error comparison with a broader pattern match, because the moved test can no longer see a private inner error type. There is no security issue here.
No security action required. This is a benign test-only refactor. Optionally, reviewers may consider whether the less-specific matches! assertion is acceptable test coverage, but it is not a vulnerability.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change is a pure test refactor: consensus encoding/decoding tests for Transaction, OutPoint, TxIn, TxOut, Block, and related helpers are relocated from primitives/src/transaction.rs (inline mod tests) to a new integration-style test file primitives/tests/encoding.rs. Constants and helper functions are duplicated/moved accordingly. The production Transaction encoder/decoder logic is untouched. The assertion assert_eq!(err, TransactionDecoderError(TransactionDecoderErrorInner::NoWitnesses)) is replaced with assert!(matches!(err, TransactionDecoderError { .. })) because TransactionDecoderErrorInner is not public outside the crate, so the integration test cannot name it. This weakens the test’s specificity slightly but does not alter runtime behavior or security properties.
Changed components
primitives/src/transaction.rsprimitives/tests/encoding.rsInspect captured patch +460 / −444
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 80ce6e9d..30833928 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -1627,9 +1627,6 @@ mod tests {
use hex_unstable::hex;
use super::*;
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- use crate::absolute::LockTime;
#[cfg(feature = "hex")]
use crate::hex_codec::ParsePrimitiveError;
@@ -1639,41 +1636,7 @@ mod tests {
];
const TC_VOUT_BYTES: [u8; 4] = [1, 0, 0, 0];
const TC_SCRIPT_BYTES: [u8; 3] = [1, 2, 3];
- #[cfg(feature = "hex")]
- const TC_SEQ_MAX_BYTES: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
- #[cfg(feature = "hex")]
- const TC_LOCK_TIME_ZERO_BYTES: [u8; 4] = [0, 0, 0, 0];
const TC_ONE_SAT_BYTES: [u8; 8] = [1, 0, 0, 0, 0, 0, 0, 0];
- #[cfg(feature = "hex")]
- const TC_SEGWIT_MARKER_AND_FLAG: [u8; 2] = [0, 1];
- #[cfg(feature = "hex")]
- const TC_WITNESS_ELEM_LEN_AND_DATA: [u8; 4] = [3, 1, 2, 3];
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn transaction_encode_decode_roundtrip() {
- // Create two different inputs to avoid duplicate input rejection
- let tx_in_1 = segwit_tx_in();
- let mut tx_in_2 = segwit_tx_in();
- tx_in_2.previous_output.vout = 2;
-
- let tx = Transaction {
- version: Version::TWO,
- lock_time: absolute::LockTime::ZERO,
- inputs: vec![tx_in_1, tx_in_2],
- outputs: vec![tx_out(), tx_out()],
- };
-
- let encoded = encoding::encode_to_vec(&tx);
-
- let mut decoder = Transaction::decoder();
- let mut slice = encoded.as_slice();
- decoder.push_bytes(&mut slice).unwrap();
- let decoded = decoder.end().unwrap();
-
- assert_eq!(tx, decoded);
- }
#[test]
fn sanity_check() {
@@ -1972,413 +1935,6 @@ mod tests {
#[cfg(any(feature = "hex", feature = "serde"))]
fn tc_script_sig() -> ScriptSigBuf { ScriptSigBuf::from_bytes(TC_SCRIPT_BYTES.to_vec()) }
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn encode_out_point() {
- let out_point = tc_out_point();
- let mut encoder = out_point.encoder();
-
- // The txid
- assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
- assert!(encoder.advance());
-
- // The vout
- assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- fn encode_tx_out() {
- let out = tx_out();
- let mut encoder = out.encoder();
-
- // The amount.
- assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
- assert!(encoder.advance());
-
- // The script pubkey length prefix.
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
-
- // The script pubkey data.
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn encode_tx_in() {
- let txin = segwit_tx_in();
- let mut encoder = txin.encoder();
-
- // The outpoint (same as tested above).
- assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
- assert!(encoder.advance());
-
- // The script sig
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
-
- // The sequence
- assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn encode_segwit_transaction() {
- let tx = Transaction {
- version: Version::TWO,
- lock_time: LockTime::ZERO,
- inputs: vec![segwit_tx_in()],
- outputs: vec![tx_out()],
- };
-
- let mut encoder = tx.encoder();
-
- // The version
- assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
- assert!(encoder.advance());
-
- // The segwit marker and flag
- assert_eq!(encoder.current_chunk(), &TC_SEGWIT_MARKER_AND_FLAG[..]);
- assert!(encoder.advance());
-
- // The input (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
- assert!(encoder.advance());
-
- // The output (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
-
- // The witness
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_WITNESS_ELEM_LEN_AND_DATA[..]);
- assert!(encoder.advance());
-
- // The lock time.
- assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn encode_non_segwit_transaction() {
- let mut tx_in = segwit_tx_in();
- tx_in.witness = Witness::default();
-
- let tx = Transaction {
- version: Version::TWO,
- lock_time: LockTime::ZERO,
- inputs: vec![tx_in],
- outputs: vec![tx_out()],
- };
-
- let mut encoder = tx.encoder();
-
- // The version
- assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
- assert!(encoder.advance());
-
- // Advance past the optional segwit bytes encoder.
- assert!(encoder.advance());
-
- // The input (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
- assert!(encoder.advance());
-
- // The output (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
-
- // Advance past the optional witnesses encoder.
- assert!(encoder.advance());
-
- // The lock time.
- assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- // FIXME: Move all these encoding tests to a single file in `primitives/tests/`.
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn encode_block() {
- use crate::merkle_tree::TxMerkleNode;
- use crate::{Block, BlockHash, BlockHeader, BlockTime, BlockVersion, CompactTarget};
-
- let seconds: u32 = 1_653_195_600; // Arbitrary timestamp: May 22nd, 5am UTC.
-
- let header = BlockHeader {
- version: BlockVersion::TWO,
- prev_blockhash: BlockHash::from_byte_array([0xab; 32]),
- merkle_root: TxMerkleNode::from_byte_array([0xcd; 32]),
- time: BlockTime::from(seconds),
- bits: CompactTarget::from_consensus(0xbeef),
- nonce: 0xcafe,
- };
-
- let tx = Transaction {
- version: Version::TWO,
- lock_time: LockTime::ZERO,
- inputs: vec![segwit_tx_in()],
- outputs: vec![tx_out()],
- };
-
- let block = Block::new_unchecked(header, vec![tx]);
- let mut encoder = block.encoder();
-
- // The block header, 6 encoders, 1 chunk per encoder.
-
- // The block version.
- assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
- assert!(encoder.advance());
- // The previous block's blockhash.
- assert_eq!(
- encoder.current_chunk(),
- &[
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
- 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171
- ][..]
- );
- assert!(encoder.advance());
- // The merkle root hash.
- assert_eq!(
- encoder.current_chunk(),
- &[
- 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
- 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205
- ][..]
- );
- assert!(encoder.advance());
- // The block time.
- assert_eq!(encoder.current_chunk(), &[80, 195, 137, 98][..]);
- assert!(encoder.advance());
- // The target (bits).
- assert_eq!(encoder.current_chunk(), &[239, 190, 0, 0][..]);
- assert!(encoder.advance());
- // The nonce.
- assert_eq!(encoder.current_chunk(), &[254, 202, 0, 0][..]);
- assert!(encoder.advance());
-
- // The transaction list length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
-
- // The transaction (same as tested above).
-
- // The version
- assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
- assert!(encoder.advance());
- // The segwit marker and flag
- assert_eq!(encoder.current_chunk(), &TC_SEGWIT_MARKER_AND_FLAG[..]);
- assert!(encoder.advance());
- // The input (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
- assert!(encoder.advance());
- // The output (same as tested above) but with vec length prefix.
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &[3u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
- assert!(encoder.advance());
- // The witness
- assert_eq!(encoder.current_chunk(), &[1u8][..]);
- assert!(encoder.advance());
- assert_eq!(encoder.current_chunk(), &TC_WITNESS_ELEM_LEN_AND_DATA[..]);
- assert!(encoder.advance());
- // The lock time.
- assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
- assert!(!encoder.advance());
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn decode_segwit_transaction() {
- let tx_bytes = hex!(
- "02000000000101595895ea20179de87052b4046dfe6fd515860505d6511a9004cf12a1f93cac7c01000000\
- 00ffffffff01deb807000000000017a9140f3444e271620c736808aa7b33e370bd87cb5a078702483045022\
- 100fb60dad8df4af2841adc0346638c16d0b8035f5e3f3753b88db122e70c79f9370220756e6633b17fd271\
- 0e626347d28d60b0a2d6cbb41de51740644b9fb3ba7751040121028fa937ca8cba2197a37c007176ed89410\
- 55d3bcb8627d085e94553e62f057dcc00000000"
- );
- let mut decoder = Transaction::decoder();
- let mut slice = tx_bytes.as_slice();
- decoder.push_bytes(&mut slice).unwrap();
- let tx = decoder.end().unwrap();
-
- // Attempt various truncations
- for i in [1, 10, 20, 50, 100, tx_bytes.len() / 2, tx_bytes.len()] {
- let mut decoder = Transaction::decoder();
- let mut slice = &tx_bytes[..tx_bytes.len() - i];
- // push_bytes will not fail because the data is not invalid, just truncated
- decoder.push_bytes(&mut slice).unwrap();
- // ...but end() will fail because we will be in some incomplete state
- decoder.end().unwrap_err();
- }
-
- // All these tests aren't really needed because if they fail, the hash check at the end
- // will also fail. But these will show you where the failure is so I'll leave them in.
- assert_eq!(tx.version, Version::TWO);
- assert_eq!(tx.inputs.len(), 1);
- // In particular this one is easy to get backward -- in bitcoin hashes are encoded
- // as little-endian 256-bit numbers rather than as data strings.
- assert_eq!(
- format!("{:x}", tx.inputs[0].previous_output.txid),
- "7cac3cf9a112cf04901a51d605058615d56ffe6d04b45270e89d1720ea955859".to_string()
- );
- assert_eq!(tx.inputs[0].previous_output.vout, 1);
- assert_eq!(tx.outputs.len(), 1);
- assert_eq!(tx.lock_time, absolute::LockTime::ZERO);
-
- assert_eq!(
- format!("{:x}", tx.compute_txid()),
- "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string()
- );
- assert_eq!(
- format!("{:x}", tx.compute_wtxid()),
- "80b7d8a82d5d5bf92905b06f2014dd699e03837ca172e3a59d51426ebbe3e7f5".to_string()
- );
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn decode_nonsegwit_transaction() {
- let tx_bytes = hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000");
-
- let mut decoder = Transaction::decoder();
- let mut slice = tx_bytes.as_slice();
- decoder.push_bytes(&mut slice).unwrap();
- let tx = decoder.end().unwrap();
-
- // All these tests aren't really needed because if they fail, the hash check at the end
- // will also fail. But these will show you where the failure is so I'll leave them in.
- assert_eq!(tx.version, Version::ONE);
- assert_eq!(tx.inputs.len(), 1);
- // In particular this one is easy to get backward -- in bitcoin hashes are encoded
- // as little-endian 256-bit numbers rather than as data strings.
- assert_eq!(
- format!("{:x}", tx.inputs[0].previous_output.txid),
- "ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string()
- );
- assert_eq!(tx.inputs[0].previous_output.vout, 1);
- assert_eq!(tx.outputs.len(), 1);
- assert_eq!(tx.lock_time, absolute::LockTime::ZERO);
-
- assert_eq!(
- format!("{:x}", tx.compute_txid()),
- "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
- );
- assert_eq!(
- format!("{:x}", tx.compute_wtxid()),
- "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
- );
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- #[cfg(feature = "hex")]
- fn decode_segwit_without_witnesses_errors() {
- // A SegWit-serialized transaction with 1 input but no witnesses for any input.
- let tx_bytes = hex!(
- "02000000\
- 0001\
- 01\
- 0000000000000000000000000000000000000000000000000000000000000000\
- 00000000\
- 00\
- ffffffff\
- 01\
- 0100000000000000\
- 00\
- 00\
- 00000000"
- );
-
- let mut slice = tx_bytes.as_slice();
- let err = Transaction::decoder()
- .push_bytes(&mut slice)
- .expect_err("segwit tx with no witnesses should error");
-
- assert_eq!(err, TransactionDecoderError(TransactionDecoderErrorInner::NoWitnesses));
- }
-
- #[test]
- #[cfg(feature = "alloc")]
- fn decode_zero_inputs() {
- // Test transaction with no inputs (but with one output to satisfy validation).
- let block: u32 = 741_521;
- let original_tx = Transaction {
- version: Version::ONE,
- lock_time: absolute::LockTime::from_height(block).expect("valid height"),
- inputs: vec![],
- outputs: vec![TxOut { amount: Amount::ONE_SAT, script_pubkey: ScriptPubKeyBuf::new() }],
- };
-
- let encoded = encoding::encode_to_vec(&original_tx);
- let decoded_tx = encoding::decode_from_slice(&encoded).unwrap();
-
- assert_eq!(original_tx, decoded_tx);
- }
-
#[test]
#[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
diff --git a/primitives/tests/encoding.rs b/primitives/tests/encoding.rs
new file mode 100644
index 00000000..b02b0365
--- /dev/null
+++ b/primitives/tests/encoding.rs
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Test the consensus encoding implementations for types in `primitives`.
+
+#![cfg(feature = "alloc")]
+#![cfg(feature = "hex")]
+
+use bitcoin_primitives::merkle_tree::TxMerkleNode;
+use bitcoin_primitives::transaction::{
+ OutPoint, Transaction, TransactionDecoderError, TxIn, TxOut, Version,
+};
+use bitcoin_primitives::{
+ absolute, Amount, Block, BlockHash, BlockHeader, BlockTime, BlockVersion, CompactTarget,
+ ScriptPubKeyBuf, ScriptSigBuf, Sequence, Witness,
+};
+use encoding::{Decodable as _, Decoder as _, Encodable as _, Encoder as _};
+use hex_unstable::hex;
+
+const TC_TXID_BYTES: [u8; 32] = [
+ 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9,
+ 8, 7, 6, 5, 4, 3, 2, 1,
+];
+const TC_VOUT_BYTES: [u8; 4] = [1, 0, 0, 0];
+const TC_SCRIPT_BYTES: [u8; 3] = [1, 2, 3];
+const TC_SEQ_MAX_BYTES: [u8; 4] = [0xff, 0xff, 0xff, 0xff];
+const TC_LOCK_TIME_ZERO_BYTES: [u8; 4] = [0, 0, 0, 0];
+const TC_ONE_SAT_BYTES: [u8; 8] = [1, 0, 0, 0, 0, 0, 0, 0];
+const TC_SEGWIT_MARKER_AND_FLAG: [u8; 2] = [0, 1];
+const TC_WITNESS_ELEM_LEN_AND_DATA: [u8; 4] = [3, 1, 2, 3];
+
+fn tc_out_point() -> OutPoint {
+ let s = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20:1";
+ s.parse::<OutPoint>().unwrap()
+}
+
+fn tc_script_pubkey() -> ScriptPubKeyBuf { ScriptPubKeyBuf::from_bytes(TC_SCRIPT_BYTES.to_vec()) }
+
+fn tc_script_sig() -> ScriptSigBuf { ScriptSigBuf::from_bytes(TC_SCRIPT_BYTES.to_vec()) }
+
+fn tx_out() -> TxOut { TxOut { amount: Amount::ONE_SAT, script_pubkey: tc_script_pubkey() } }
+
+fn segwit_tx_in() -> TxIn {
+ let data = [&TC_SCRIPT_BYTES[..]];
+ let witness = Witness::from_iter(data);
+ TxIn {
+ previous_output: tc_out_point(),
+ script_sig: tc_script_sig(),
+ sequence: Sequence::MAX,
+ witness,
+ }
+}
+
+#[test]
+fn transaction_encode_decode_roundtrip() {
+ // Create two different inputs to avoid duplicate input rejection
+ let tx_in_1 = segwit_tx_in();
+ let mut tx_in_2 = segwit_tx_in();
+ tx_in_2.previous_output.vout = 2;
+
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ inputs: vec![tx_in_1, tx_in_2],
+ outputs: vec![tx_out(), tx_out()],
+ };
+
+ let encoded = encoding::encode_to_vec(&tx);
+
+ let mut decoder = Transaction::decoder();
+ let mut slice = encoded.as_slice();
+ decoder.push_bytes(&mut slice).unwrap();
+ let decoded = decoder.end().unwrap();
+
+ assert_eq!(tx, decoded);
+}
+
+#[test]
+fn encode_out_point() {
+ let out_point = tc_out_point();
+ let mut encoder = out_point.encoder();
+
+ // The txid
+ assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The vout
+ assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_tx_out() {
+ let out = tx_out();
+ let mut encoder = out.encoder();
+
+ // The amount.
+ assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The script pubkey length prefix.
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+
+ // The script pubkey data.
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_tx_in() {
+ let txin = segwit_tx_in();
+ let mut encoder = txin.encoder();
+
+ // The outpoint (same as tested above).
+ assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The script sig
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The sequence
+ assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_segwit_transaction() {
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ inputs: vec![segwit_tx_in()],
+ outputs: vec![tx_out()],
+ };
+
+ let mut encoder = tx.encoder();
+
+ // The version
+ assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
+ assert!(encoder.advance());
+
+ // The segwit marker and flag
+ assert_eq!(encoder.current_chunk(), &TC_SEGWIT_MARKER_AND_FLAG[..]);
+ assert!(encoder.advance());
+
+ // The input (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The output (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The witness
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_WITNESS_ELEM_LEN_AND_DATA[..]);
+ assert!(encoder.advance());
+
+ // The lock time.
+ assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_non_segwit_transaction() {
+ let mut tx_in = segwit_tx_in();
+ tx_in.witness = Witness::default();
+
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ inputs: vec![tx_in],
+ outputs: vec![tx_out()],
+ };
+
+ let mut encoder = tx.encoder();
+
+ // The version
+ assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
+ assert!(encoder.advance());
+
+ // Advance past the optional segwit bytes encoder.
+ assert!(encoder.advance());
+
+ // The input (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
+ assert!(encoder.advance());
+
+ // The output (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+
+ // Advance past the optional witnesses encoder.
+ assert!(encoder.advance());
+
+ // The lock time.
+ assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn encode_block() {
+ let seconds: u32 = 1_653_195_600; // Arbitrary timestamp: May 22nd, 5am UTC.
+
+ let header = BlockHeader {
+ version: BlockVersion::TWO,
+ prev_blockhash: BlockHash::from_byte_array([0xab; 32]),
+ merkle_root: TxMerkleNode::from_byte_array([0xcd; 32]),
+ time: BlockTime::from(seconds),
+ bits: CompactTarget::from_consensus(0xbeef),
+ nonce: 0xcafe,
+ };
+
+ let tx = Transaction {
+ version: Version::TWO,
+ lock_time: absolute::LockTime::ZERO,
+ inputs: vec![segwit_tx_in()],
+ outputs: vec![tx_out()],
+ };
+
+ let block = Block::new_unchecked(header, vec![tx]);
+ let mut encoder = block.encoder();
+
+ // The block header, 6 encoders, 1 chunk per encoder.
+
+ // The block version.
+ assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
+ assert!(encoder.advance());
+ // The previous block's blockhash.
+ assert_eq!(
+ encoder.current_chunk(),
+ &[
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171
+ ][..]
+ );
+ assert!(encoder.advance());
+ // The merkle root hash.
+ assert_eq!(
+ encoder.current_chunk(),
+ &[
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205, 205
+ ][..]
+ );
+ assert!(encoder.advance());
+ // The block time.
+ assert_eq!(encoder.current_chunk(), &[80, 195, 137, 98][..]);
+ assert!(encoder.advance());
+ // The target (bits).
+ assert_eq!(encoder.current_chunk(), &[239, 190, 0, 0][..]);
+ assert!(encoder.advance());
+ // The nonce.
+ assert_eq!(encoder.current_chunk(), &[254, 202, 0, 0][..]);
+ assert!(encoder.advance());
+
+ // The transaction list length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+
+ // The transaction (same as tested above).
+
+ // The version
+ assert_eq!(encoder.current_chunk(), &[2u8, 0, 0, 0][..]);
+ assert!(encoder.advance());
+ // The segwit marker and flag
+ assert_eq!(encoder.current_chunk(), &TC_SEGWIT_MARKER_AND_FLAG[..]);
+ assert!(encoder.advance());
+ // The input (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_TXID_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_VOUT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SEQ_MAX_BYTES[..]);
+ assert!(encoder.advance());
+ // The output (same as tested above) but with vec length prefix.
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_ONE_SAT_BYTES[..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &[3u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_SCRIPT_BYTES[..]);
+ assert!(encoder.advance());
+ // The witness
+ assert_eq!(encoder.current_chunk(), &[1u8][..]);
+ assert!(encoder.advance());
+ assert_eq!(encoder.current_chunk(), &TC_WITNESS_ELEM_LEN_AND_DATA[..]);
+ assert!(encoder.advance());
+ // The lock time.
+ assert_eq!(encoder.current_chunk(), &TC_LOCK_TIME_ZERO_BYTES[..]);
+ assert!(!encoder.advance());
+}
+
+#[test]
+fn decode_segwit_transaction() {
+ let tx_bytes = hex!(
+ "02000000000101595895ea20179de87052b4046dfe6fd515860505d6511a9004cf12a1f93cac7c01000000\
+ 00ffffffff01deb807000000000017a9140f3444e271620c736808aa7b33e370bd87cb5a078702483045022\
+ 100fb60dad8df4af2841adc0346638c16d0b8035f5e3f3753b88db122e70c79f9370220756e6633b17fd271\
+ 0e626347d28d60b0a2d6cbb41de51740644b9fb3ba7751040121028fa937ca8cba2197a37c007176ed89410\
+ 55d3bcb8627d085e94553e62f057dcc00000000"
+ );
+ let mut decoder = Transaction::decoder();
+ let mut slice = tx_bytes.as_slice();
+ decoder.push_bytes(&mut slice).unwrap();
+ let tx = decoder.end().unwrap();
+
+ // Attempt various truncations
+ for i in [1, 10, 20, 50, 100, tx_bytes.len() / 2, tx_bytes.len()] {
+ let mut decoder = Transaction::decoder();
+ let mut slice = &tx_bytes[..tx_bytes.len() - i];
+ // push_bytes will not fail because the data is not invalid, just truncated
+ decoder.push_bytes(&mut slice).unwrap();
+ // ...but end() will fail because we will be in some incomplete state
+ decoder.end().unwrap_err();
+ }
+
+ // All these tests aren't really needed because if they fail, the hash check at the end
+ // will also fail. But these will show you where the failure is so I'll leave them in.
+ assert_eq!(tx.version, Version::TWO);
+ assert_eq!(tx.inputs.len(), 1);
+ // In particular this one is easy to get backward -- in bitcoin hashes are encoded
+ // as little-endian 256-bit numbers rather than as data strings.
+ assert_eq!(
+ format!("{:x}", tx.inputs[0].previous_output.txid),
+ "7cac3cf9a112cf04901a51d605058615d56ffe6d04b45270e89d1720ea955859".to_string()
+ );
+ assert_eq!(tx.inputs[0].previous_output.vout, 1);
+ assert_eq!(tx.outputs.len(), 1);
+ assert_eq!(tx.lock_time, absolute::LockTime::ZERO);
+
+ assert_eq!(
+ format!("{:x}", tx.compute_txid()),
+ "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206".to_string()
+ );
+ assert_eq!(
+ format!("{:x}", tx.compute_wtxid()),
+ "80b7d8a82d5d5bf92905b06f2014dd699e03837ca172e3a59d51426ebbe3e7f5".to_string()
+ );
+}
+
+#[test]
+fn decode_nonsegwit_transaction() {
+ let tx_bytes = hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000");
+
+ let mut decoder = Transaction::decoder();
+ let mut slice = tx_bytes.as_slice();
+ decoder.push_bytes(&mut slice).unwrap();
+ let tx = decoder.end().unwrap();
+
+ // All these tests aren't really needed because if they fail, the hash check at the end
+ // will also fail. But these will show you where the failure is so I'll leave them in.
+ assert_eq!(tx.version, Version::ONE);
+ assert_eq!(tx.inputs.len(), 1);
+ // In particular this one is easy to get backward -- in bitcoin hashes are encoded
+ // as little-endian 256-bit numbers rather than as data strings.
+ assert_eq!(
+ format!("{:x}", tx.inputs[0].previous_output.txid),
+ "ce9ea9f6f5e422c6a9dbcddb3b9a14d1c78fab9ab520cb281aa2a74a09575da1".to_string()
+ );
+ assert_eq!(tx.inputs[0].previous_output.vout, 1);
+ assert_eq!(tx.outputs.len(), 1);
+ assert_eq!(tx.lock_time, absolute::LockTime::ZERO);
+
+ assert_eq!(
+ format!("{:x}", tx.compute_txid()),
+ "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
+ );
+ assert_eq!(
+ format!("{:x}", tx.compute_wtxid()),
+ "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
+ );
+}
+
+#[test]
+fn decode_segwit_without_witnesses_errors() {
+ // A SegWit-serialized transaction with 1 input but no witnesses for any input.
+ let tx_bytes = hex!(
+ "02000000\
+ 0001\
+ 01\
+ 0000000000000000000000000000000000000000000000000000000000000000\
+ 00000000\
+ 00\
+ ffffffff\
+ 01\
+ 0100000000000000\
+ 00\
+ 00\
+ 00000000"
+ );
+
+ let mut slice = tx_bytes.as_slice();
+ let err = Transaction::decoder()
+ .push_bytes(&mut slice)
+ .expect_err("segwit tx with no witnesses should error");
+
+ assert!(matches!(err, TransactionDecoderError { .. }));
+}
+
+#[test]
+fn decode_zero_inputs() {
+ // Test transaction with no inputs (but with one output to satisfy validation).
+ let block: u32 = 741_521;
+ let original_tx = Transaction {
+ version: Version::ONE,
+ lock_time: absolute::LockTime::from_height(block).expect("valid height"),
+ inputs: vec![],
+ outputs: vec![TxOut { amount: Amount::ONE_SAT, script_pubkey: ScriptPubKeyBuf::new() }],
+ };
+
+ let encoded = encoding::encode_to_vec(&original_tx);
+ let decoded_tx = encoding::decode_from_slice(&encoded).unwrap();
+
+ assert_eq!(original_tx, decoded_tx);
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.