Skip to content

Commit

Permalink
Add transaction::Version data type
Browse files Browse the repository at this point in the history
BIP-68 activated a fair while ago (circa 2019) and since then only
transaction versions 1 and 2 have been considered standard.

Currently in our `Transaction` struct we use an `i32`, this means users
can construct a non-standard transaction if they do not first look up
what the value should be. We can help folk out here by abstracting over
the version number.

Since the version number only governs standardness elect to make the
inner `i32` public (ie., not an invariant). The aim of the type is to
make life easy not restrict what versions are used.

Add transaction::Version data type that simply provides two consts `ONE`
and `TWO`.

Add a `Default` impl on `Version` that returns `Version::TWO`.

In tests that used version 0, instead use `Version::default` because the
test obviously does not care.
  • Loading branch information
tcharding committed Aug 18, 2023
1 parent 1b3a9d3 commit 6cf6f62
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 54 deletions.
6 changes: 3 additions & 3 deletions bitcoin/examples/ecdsa-psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ use bitcoin::locktime::absolute;
use bitcoin::psbt::{self, Input, Psbt, PsbtSighashType};
use bitcoin::secp256k1::{Secp256k1, Signing, Verification};
use bitcoin::{
Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut,
Witness,
transaction, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, Transaction,
TxIn, TxOut, Witness,
};

type Result<T> = std::result::Result<T, Error>;
Expand Down Expand Up @@ -183,7 +183,7 @@ impl WatchOnly {
let change_amount = Amount::from_str(CHANGE_AMOUNT_BTC)?;

let tx = Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: INPUT_UTXO_TXID.parse()?, vout: INPUT_UTXO_VOUT },
Expand Down
10 changes: 5 additions & 5 deletions bitcoin/examples/taproot-psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ use bitcoin::secp256k1::Secp256k1;
use bitcoin::sighash::{self, SighashCache, TapSighash, TapSighashType};
use bitcoin::taproot::{self, LeafVersion, TapLeafHash, TaprootBuilder, TaprootSpendInfo};
use bitcoin::{
absolute, script, Address, Amount, Network, OutPoint, ScriptBuf, Transaction, TxIn, TxOut,
Witness,
absolute, script, transaction, Address, Amount, Network, OutPoint, ScriptBuf, Transaction,
TxIn, TxOut, Witness,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
Expand Down Expand Up @@ -230,7 +230,7 @@ fn generate_bip86_key_spend_tx(

// CREATOR + UPDATER
let tx1 = Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint { txid: input_utxo.txid.parse()?, vout: input_utxo.vout },
Expand Down Expand Up @@ -415,7 +415,7 @@ impl BenefactorWallet {

// CREATOR + UPDATER
let next_tx = Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
Expand Down Expand Up @@ -561,7 +561,7 @@ impl BenefactorWallet {
.expect("failed to verify transaction");

let next_tx = Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time,
input: vec![TxIn {
previous_output: OutPoint { txid: tx.txid(), vout: 0 },
Expand Down
3 changes: 2 additions & 1 deletion bitcoin/src/bip152.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ mod test {

use super::*;
use crate::blockdata::locktime::absolute;
use crate::blockdata::transaction;
use crate::consensus::encode::{deserialize, serialize};
use crate::hash_types::TxMerkleNode;
use crate::{
Expand All @@ -384,7 +385,7 @@ mod test {

fn dummy_tx(nonce: &[u8]) -> Transaction {
Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::from_consensus(2),
input: vec![TxIn {
previous_output: OutPoint::new(Txid::hash(nonce), 0),
Expand Down
7 changes: 4 additions & 3 deletions bitcoin/src/blockdata/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::blockdata::block::{self, Block};
use crate::blockdata::locktime::absolute;
use crate::blockdata::opcodes::all::*;
use crate::blockdata::script;
use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut};
use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut};
use crate::blockdata::witness::Witness;
use crate::internal_macros::impl_bytes_newtype;
use crate::network::Network;
Expand Down Expand Up @@ -64,7 +64,7 @@ pub const COINBASE_MATURITY: u32 = 100;
fn bitcoin_genesis_tx() -> Transaction {
// Base
let mut ret = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![],
Expand Down Expand Up @@ -196,6 +196,7 @@ mod test {

use super::*;
use crate::blockdata::locktime::absolute;
use crate::blockdata::transaction;
use crate::consensus::encode::serialize;
use crate::internal_macros::hex;
use crate::network::Network;
Expand All @@ -204,7 +205,7 @@ mod test {
fn bitcoin_genesis_first_transaction() {
let gen = bitcoin_genesis_tx();

assert_eq!(gen.version, 1);
assert_eq!(gen.version, transaction::Version::ONE);
assert_eq!(gen.input.len(), 1);
assert_eq!(gen.input[0].previous_output.txid, Hash::all_zeros());
assert_eq!(gen.input[0].previous_output.vout, 0xFFFFFFFF);
Expand Down
4 changes: 2 additions & 2 deletions bitcoin/src/blockdata/fee_rate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ impl FeeRate {
/// # Examples
///
/// ```no_run
/// # use bitcoin::{absolute, FeeRate, Transaction};
/// # use bitcoin::{absolute, transaction, FeeRate, Transaction};
/// # // Dummy transaction.
/// # let tx = Transaction { version: 1, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] };
/// # let tx = Transaction { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![] };
///
/// let rate = FeeRate::from_sat_per_vb(1).expect("1 sat/vbyte is valid");
/// let fee = rate.fee_wu(tx.weight());
Expand Down
69 changes: 49 additions & 20 deletions bitcoin/src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ impl TxOut {
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Transaction {
/// The protocol version, is currently expected to be 1 or 2 (BIP 68).
pub version: i32,
pub version: Version,
/// Block height or timestamp. Transaction cannot be included in a block until this height/time.
///
/// ### Relevant BIPs
Expand Down Expand Up @@ -829,6 +829,50 @@ impl Transaction {
}
}

/// The transaction version.
///
/// Currently, as specified by [BIP-68], only version 1 and 2 are considered standard.
///
/// Standardness of the inner `i32` is not an invariant because you are free to create transactions
/// of any version, transactions with non-standard version numbers will not be relayed by the
/// Bitcoin network.
///
/// [BIP-68]: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Version(pub i32);

impl Version {
/// The original Bitcoin transaction version (pre-BIP-68).
pub const ONE: Self = Self(1);

/// The second Bitcoin transaction version (post-BIP-68).
pub const TWO: Self = Self(2);

/// Creates a non-standard transaction version.
pub fn non_standard(version: i32) -> Version { Self(version) }

/// Returns true if this transaction version number is considered standard.
pub fn is_standard(&self) -> bool { *self == Version::ONE || *self == Version::TWO }
}

impl Default for Version {
fn default() -> Version { Version::TWO }
}

impl Encodable for Version {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}

impl Decodable for Version {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Decodable::consensus_decode(r).map(Version)
}
}

impl_consensus_encoding!(TxOut, value, script_pubkey);

impl Encodable for OutPoint {
Expand Down Expand Up @@ -915,7 +959,7 @@ impl Decodable for Transaction {
fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
r: &mut R,
) -> Result<Self, encode::Error> {
let version = i32::consensus_decode_from_finite_reader(r)?;
let version = Version::consensus_decode_from_finite_reader(r)?;
let input = Vec::<TxIn>::consensus_decode_from_finite_reader(r)?;
// segwit
if input.is_empty() {
Expand Down Expand Up @@ -1368,7 +1412,7 @@ mod tests {
let realtx = tx.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!(realtx.version, 1);
assert_eq!(realtx.version, Version::ONE);
assert_eq!(realtx.input.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.
Expand Down Expand Up @@ -1408,7 +1452,7 @@ mod tests {
let realtx = tx.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!(realtx.version, 2);
assert_eq!(realtx.version, Version::TWO);
assert_eq!(realtx.input.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.
Expand Down Expand Up @@ -1471,21 +1515,6 @@ mod tests {
assert_eq!(bytes, json.as_bytes())
}

#[test]
fn test_transaction_version() {
let tx_bytes = hex!("ffffff7f0100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000");
let tx: Result<Transaction, _> = deserialize(&tx_bytes);
assert!(tx.is_ok());
let realtx = tx.unwrap();
assert_eq!(realtx.version, 2147483647);

let tx2_bytes = hex!("000000800100000000000000000000000000000000000000000000000000000000000000000000000000ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000");
let tx2: Result<Transaction, _> = deserialize(&tx2_bytes);
assert!(tx2.is_ok());
let realtx2 = tx2.unwrap();
assert_eq!(realtx2.version, -2147483648);
}

#[test]
fn tx_no_input_deserialization() {
let tx_bytes = hex!(
Expand Down Expand Up @@ -1771,7 +1800,7 @@ mod tests {
];

let empty_transaction_weight = Transaction {
version: 0,
version: Version::default(),
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![],
Expand Down
9 changes: 5 additions & 4 deletions bitcoin/src/crypto/sighash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,10 +1069,10 @@ impl<R: BorrowMut<Transaction>> SighashCache<R> {
///
/// This allows in-line signing such as
/// ```
/// use bitcoin::{absolute, Amount, Transaction, Script};
/// use bitcoin::{absolute, transaction, Amount, Transaction, Script};
/// use bitcoin::sighash::{EcdsaSighashType, SighashCache};
///
/// let mut tx_to_sign = Transaction { version: 2, lock_time: absolute::LockTime::ZERO, input: Vec::new(), output: Vec::new() };
/// let mut tx_to_sign = Transaction { version: transaction::Version::TWO, lock_time: absolute::LockTime::ZERO, input: Vec::new(), output: Vec::new() };
/// let input_count = tx_to_sign.input.len();
///
/// let mut sig_hasher = SighashCache::new(&mut tx_to_sign);
Expand Down Expand Up @@ -1196,6 +1196,7 @@ mod tests {
use super::*;
use crate::address::Address;
use crate::blockdata::locktime::absolute;
use crate::blockdata::transaction;
use crate::consensus::deserialize;
use crate::crypto::key::PublicKey;
use crate::crypto::sighash::{LegacySighash, TapSighash};
Expand All @@ -1211,7 +1212,7 @@ mod tests {

// We need a tx with more inputs than outputs.
let tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn::default(), TxIn::default()],
output: vec![TxOut::NULL],
Expand Down Expand Up @@ -1398,7 +1399,7 @@ mod tests {
#[rustfmt::skip] // Allow long function call `taproot_signature_hash`.
fn test_sighash_errors() {
let dumb_tx = Transaction {
version: 0,
version: transaction::Version::default(),
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn::default()],
output: vec![],
Expand Down
22 changes: 11 additions & 11 deletions bitcoin/src/psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ mod tests {
use crate::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey, KeySource};
use crate::blockdata::locktime::absolute;
use crate::blockdata::script::ScriptBuf;
use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut};
use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut};
use crate::blockdata::witness::Witness;
use crate::internal_macros::hex;
use crate::network::Network::Bitcoin;
Expand All @@ -832,7 +832,7 @@ mod tests {
fn trivial_psbt() {
let psbt = Psbt {
unsigned_tx: Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![],
output: vec![],
Expand Down Expand Up @@ -905,7 +905,7 @@ mod tests {
fn serialize_then_deserialize_global() {
let expected = Psbt {
unsigned_tx: Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::from_consensus(1257139),
input: vec![TxIn {
previous_output: OutPoint {
Expand Down Expand Up @@ -976,7 +976,7 @@ mod tests {

// create some values to use in the PSBT
let tx = Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint {
Expand Down Expand Up @@ -1173,7 +1173,7 @@ mod tests {
fn valid_vector_1() {
let unserialized = Psbt {
unsigned_tx: Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::from_consensus(1257139),
input: vec![
TxIn {
Expand Down Expand Up @@ -1205,7 +1205,7 @@ mod tests {
inputs: vec![
Input {
non_witness_utxo: Some(Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![
TxIn {
Expand Down Expand Up @@ -1505,7 +1505,7 @@ mod tests {
// same vector as valid_vector_1 from BIPs with added
let mut unserialized = Psbt {
unsigned_tx: Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::from_consensus(1257139),
input: vec![
TxIn {
Expand Down Expand Up @@ -1537,7 +1537,7 @@ mod tests {
inputs: vec![
Input {
non_witness_utxo: Some(Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![
TxIn {
Expand Down Expand Up @@ -1674,7 +1674,7 @@ mod tests {

let mut t = Psbt {
unsigned_tx: Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::from_consensus(1257139),
input: vec![
TxIn {
Expand Down Expand Up @@ -1705,7 +1705,7 @@ mod tests {
inputs: vec![
Input {
non_witness_utxo: Some(Transaction {
version: 1,
version: transaction::Version::ONE,
lock_time: absolute::LockTime::ZERO,
input: vec![
TxIn {
Expand Down Expand Up @@ -1783,7 +1783,7 @@ mod tests {
use crate::{WPubkeyHash, WitnessProgram};

let unsigned_tx = Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn::default(), TxIn::default()],
output: vec![TxOut::NULL],
Expand Down
4 changes: 2 additions & 2 deletions bitcoin/tests/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::str::FromStr;

use bitcoin::bip32::{ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDerivationPath, KeySource};
use bitcoin::blockdata::opcodes::OP_0;
use bitcoin::blockdata::script;
use bitcoin::blockdata::{script, transaction};
use bitcoin::consensus::encode::{deserialize, serialize_hex};
use bitcoin::hex::FromHex;
use bitcoin::psbt::{Psbt, PsbtSighashType};
Expand Down Expand Up @@ -163,7 +163,7 @@ fn create_transaction() -> Transaction {
}

Transaction {
version: 2,
version: transaction::Version::TWO,
lock_time: absolute::LockTime::ZERO,
input: vec![
TxIn {
Expand Down

0 comments on commit 6cf6f62

Please sign in to comment.