Merge rust-bitcoin/rust-bitcoin#2076: Re-write the weight/size API

c34e3cc7cc Re-write size/weight API (Tobin C. Harding)
73f7fbf520 Add code comments to transaction serialization (Tobin C. Harding)
29f20c1d0b Add segwit serialization constants (Tobin C. Harding)

Pull request description:

  Audit and re-write the weight/size API for `Block` and `Transaction`. First two patches are trivial, patch 3 contains justification and explanation for this work, copied here:

  ```
      Recently we introduced a bug in the weight/size code, while
      investigating I found that our `Transaction`/`Block` weight/size APIs
      were in a total mess because:

      - The docs were stale
      - The concept of weight (weight units) and size (bytes) were mixed up

      I audited all the API functions, read some bips (141, 144) and re-wrote
      the API with the following goals:

      - Use terminology from the bips
      - Use abstractions that mirror the bips where possible
  ```

  Please note, this PR introduces panics if a sciptPubkey overflows the calculation `weight = spk.size() * 4`.

  Fix #2049

ACKs for top commit:
  apoelstra:
    ACK c34e3cc7cc
  sanket1729:
    ACK c34e3cc7cc.

Tree-SHA512: 4944f652e6e362a282a5731140a9438a82d243a4c646b4627d9046a9f9cf13c476881750d432cfbc6b5fe5de1f0c4c9c44ed4569dac4bc11b55a5db28793803c
This commit is contained in:
Andrew Poelstra 2023-09-26 19:44:45 +00:00
commit 0de8ec5b19
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
8 changed files with 242 additions and 203 deletions

View File

@ -509,6 +509,8 @@ impl Amount {
pub const MIN: Amount = Amount::ZERO; pub const MIN: Amount = Amount::ZERO;
/// The maximum value of an amount. /// The maximum value of an amount.
pub const MAX: Amount = Amount(u64::MAX); pub const MAX: Amount = Amount(u64::MAX);
/// The number of bytes that an amount contributes to the size of a transaction.
pub const SIZE: usize = 8; // Serialized length of a u64.
/// Create an [Amount] with satoshi precision and the given number of satoshis. /// Create an [Amount] with satoshi precision and the given number of satoshis.
pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) } pub const fn from_sat(satoshi: u64) -> Amount { Amount(satoshi) }

View File

@ -54,6 +54,10 @@ pub struct Header {
impl_consensus_encoding!(Header, version, prev_blockhash, merkle_root, time, bits, nonce); impl_consensus_encoding!(Header, version, prev_blockhash, merkle_root, time, bits, nonce);
impl Header { impl Header {
/// The number of bytes that the block header contributes to the size of a block.
// Serialized length of fields (version, prev_blockhash, merkle_root, time, bits, nonce)
pub const SIZE: usize = 4 + 32 + 32 + 4 + 4 + 4; // 80
/// Returns the block hash. /// Returns the block hash.
pub fn block_hash(&self) -> BlockHash { pub fn block_hash(&self) -> BlockHash {
let mut engine = BlockHash::engine(); let mut engine = BlockHash::engine();
@ -275,39 +279,45 @@ impl Block {
merkle_tree::calculate_root(hashes).map(|h| h.into()) merkle_tree::calculate_root(hashes).map(|h| h.into())
} }
/// base_size == size of header + size of encoded transaction count.
fn base_size(&self) -> Weight {
Weight::from_wu_usize(80 + VarInt::from(self.txdata.len()).len())
}
/// Returns the size of the block.
///
/// size == size of header + size of encoded transaction count + total size of transactions.
pub fn size(&self) -> usize {
let txs_size: usize = self.txdata.iter().map(Transaction::size).sum();
self.base_size().to_wu() as usize + txs_size
}
/// Returns the stripped size of the block.
#[deprecated(
since = "0.31.0",
note = "Truncates on 32-bit machines, Use Block::stripped_size() instead"
)]
pub fn strippedsize(&self) -> usize { Self::stripped_size(self).to_wu() as usize }
/// Returns the stripped size of the block.
pub fn stripped_size(&self) -> Weight {
let txs_size: Weight = self.txdata.iter().map(Transaction::stripped_size).sum();
self.base_size() + txs_size
}
/// Returns the weight of the block. /// Returns the weight of the block.
///
/// > Block weight is defined as Base size * 3 + Total size.
pub fn weight(&self) -> Weight { pub fn weight(&self) -> Weight {
let base_weight = self.base_size() * Weight::WITNESS_SCALE_FACTOR; // This is the exact definition of a weight unit, as defined by BIP-141 (quote above).
let txs_weight: Weight = self.txdata.iter().map(Transaction::weight).sum(); let wu = self.base_size() * 3 + self.total_size();
base_weight + txs_weight Weight::from_wu_usize(wu)
} }
/// Returns the base block size.
///
/// > Base size is the block size in bytes with the original transaction serialization without
/// > any witness-related data, as seen by a non-upgraded node.
fn base_size(&self) -> usize {
let mut size = Header::SIZE;
size += VarInt::from(self.txdata.len()).size();
size += self.txdata.iter().map(|tx| tx.base_size()).sum::<usize>();
size
}
/// Returns the total block size.
///
/// > Total size is the block size in bytes with transactions serialized as described in BIP144,
/// > including base data and witness data.
pub fn total_size(&self) -> usize {
let mut size = Header::SIZE;
size += VarInt::from(self.txdata.len()).size();
size += self.txdata.iter().map(|tx| tx.total_size()).sum::<usize>();
size
}
/// Returns the stripped size of the block.
#[deprecated(since = "0.31.0", note = "use Block::base_size() instead")]
pub fn strippedsize(&self) -> usize { self.base_size() }
/// Returns the coinbase transaction, if one is present. /// Returns the coinbase transaction, if one is present.
pub fn coinbase(&self) -> Option<&Transaction> { self.txdata.first() } pub fn coinbase(&self) -> Option<&Transaction> { self.txdata.first() }
@ -489,9 +499,8 @@ mod tests {
assert_eq!(real_decode.header.difficulty_float(), 1.0); assert_eq!(real_decode.header.difficulty_float(), 1.0);
// [test] TODO: check the transaction data // [test] TODO: check the transaction data
assert_eq!(real_decode.base_size(), Weight::from_wu(81)); assert_eq!(real_decode.total_size(), some_block.len());
assert_eq!(real_decode.size(), some_block.len()); assert_eq!(real_decode.base_size(), some_block.len());
assert_eq!(real_decode.stripped_size(), Weight::from_wu_usize(some_block.len()));
assert_eq!( assert_eq!(
real_decode.weight(), real_decode.weight(),
Weight::from_non_witness_data_size(some_block.len() as u64) Weight::from_non_witness_data_size(some_block.len() as u64)
@ -532,8 +541,8 @@ mod tests {
assert_eq!(real_decode.header.difficulty_float(), 2456598.4399242126); assert_eq!(real_decode.header.difficulty_float(), 2456598.4399242126);
// [test] TODO: check the transaction data // [test] TODO: check the transaction data
assert_eq!(real_decode.size(), segwit_block.len()); assert_eq!(real_decode.total_size(), segwit_block.len());
assert_eq!(real_decode.stripped_size(), Weight::from_wu(4283)); assert_eq!(real_decode.base_size(), 4283);
assert_eq!(real_decode.weight(), Weight::from_wu(17168)); assert_eq!(real_decode.weight(), Weight::from_wu(17168));
assert!(real_decode.check_witness_commitment()); assert!(real_decode.check_witness_commitment());

View File

@ -100,6 +100,9 @@ impl LockTime {
/// transaction with nLocktime==0 is able to be included immediately in any block. /// transaction with nLocktime==0 is able to be included immediately in any block.
pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO); pub const ZERO: LockTime = LockTime::Blocks(Height::ZERO);
/// The number of bytes that the locktime contributes to the size of a transaction.
pub const SIZE: usize = 4; // Serialized length of a u32.
/// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY. /// Constructs a `LockTime` from an nLockTime value or the argument to OP_CHEKCLOCKTIMEVERIFY.
/// ///
/// # Examples /// # Examples

View File

@ -36,6 +36,11 @@ use crate::sighash::{EcdsaSighashType, TapSighashType};
use crate::string::FromHexStr; use crate::string::FromHexStr;
use crate::{io, Amount, VarInt}; use crate::{io, Amount, VarInt};
/// The marker MUST be a 1-byte zero value: 0x00. (BIP-141)
const SEGWIT_MARKER: u8 = 0x00;
/// The flag MUST be a 1-byte non-zero value. Currently, 0x01 MUST be used. (BIP-141)
const SEGWIT_FLAG: u8 = 0x01;
/// A reference to a transaction output. /// A reference to a transaction output.
/// ///
/// ### Bitcoin Core References /// ### Bitcoin Core References
@ -52,6 +57,9 @@ pub struct OutPoint {
crate::serde_utils::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout); crate::serde_utils::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout);
impl OutPoint { impl OutPoint {
/// The number of bytes that an outpoint contributes to the size of a transaction.
const SIZE: usize = 32 + 4; // The serialized lengths of txid and vout.
/// Creates a new [`OutPoint`]. /// Creates a new [`OutPoint`].
#[inline] #[inline]
pub fn new(txid: Txid, vout: u32) -> OutPoint { OutPoint { txid, vout } } pub fn new(txid: Txid, vout: u32) -> OutPoint { OutPoint { txid, vout } }
@ -200,9 +208,6 @@ pub struct TxIn {
} }
impl TxIn { impl TxIn {
/// The weight of a `TxIn` excluding the `script_sig` and `witness`.
pub const BASE_WEIGHT: Weight = Weight::from_wu(32 + 4 + 4);
/// Returns true if this input enables the [`absolute::LockTime`] (aka `nLockTime`) of its /// Returns true if this input enables the [`absolute::LockTime`] (aka `nLockTime`) of its
/// [`Transaction`]. /// [`Transaction`].
/// ///
@ -224,12 +229,7 @@ impl TxIn {
/// might increase more than `TxIn::legacy_weight`. This happens when the new input added causes /// might increase more than `TxIn::legacy_weight`. This happens when the new input added causes
/// the input length `VarInt` to increase its encoding length. /// the input length `VarInt` to increase its encoding length.
pub fn legacy_weight(&self) -> Weight { pub fn legacy_weight(&self) -> Weight {
let script_sig_size = self.script_sig.len(); Weight::from_non_witness_data_size(self.base_size() as u64)
// Size in vbytes:
// previous_output (36) + script_sig varint len + script_sig push + sequence (4)
Weight::from_non_witness_data_size(
(36 + VarInt::from(script_sig_size).len() + script_sig_size + 4) as u64,
)
} }
/// The weight of the TxIn when it's included in a segwit transaction (i.e., a transaction /// The weight of the TxIn when it's included in a segwit transaction (i.e., a transaction
@ -244,8 +244,26 @@ impl TxIn {
/// - the new input is the first segwit input added - this will add an additional 2WU to the /// - the new input is the first segwit input added - this will add an additional 2WU to the
/// transaction weight to take into account the segwit marker /// transaction weight to take into account the segwit marker
pub fn segwit_weight(&self) -> Weight { pub fn segwit_weight(&self) -> Weight {
self.legacy_weight() + Weight::from_witness_data_size(self.witness.serialized_len() as u64) Weight::from_non_witness_data_size(self.base_size() as u64)
+ Weight::from_witness_data_size(self.witness.size() as u64)
} }
/// Returns the base size of this input.
///
/// Base size excludes the witness data (see [`Self::total_size`]).
pub fn base_size(&self) -> usize {
let mut size = OutPoint::SIZE;
size += VarInt::from(self.script_sig.len()).size();
size += self.script_sig.len();
size + Sequence::SIZE
}
/// Returns the total number of bytes that this input contributes to a transaction.
///
/// Total size includes the witness data (for base size see [`Self::base_size`]).
pub fn total_size(&self) -> usize { self.base_size() + self.witness.size() }
} }
impl Default for TxIn { impl Default for TxIn {
@ -294,6 +312,9 @@ impl Sequence {
/// disables relative lock time. /// disables relative lock time.
pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD); pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD);
/// The number of bytes that a sequence number contributes to the size of a transaction.
const SIZE: usize = 4; // Serialized length of a u32.
/// The lowest sequence number that does not opt-in for replace-by-fee. /// The lowest sequence number that does not opt-in for replace-by-fee.
/// ///
/// A transaction is considered to have opted in to replacement of itself /// A transaction is considered to have opted in to replacement of itself
@ -493,24 +514,32 @@ impl TxOut {
pub const NULL: Self = pub const NULL: Self =
TxOut { value: Amount::from_sat(0xffffffffffffffff), script_pubkey: ScriptBuf::new() }; TxOut { value: Amount::from_sat(0xffffffffffffffff), script_pubkey: ScriptBuf::new() };
/// The weight of the txout in witness units /// The weight of this output.
/// ///
/// Keep in mind that when adding a TxOut to a transaction, the total weight of the transaction /// Keep in mind that when adding a [`TxOut`] to a [`Transaction`] the total weight of the
/// might increase more than `TxOut::weight`. This happens when the new output added causes /// transaction might increase more than `TxOut::weight`. This happens when the new output added
/// the output length `VarInt` to increase its encoding length. /// causes the output length `VarInt` to increase its encoding length.
///
/// # Panics
///
/// If output size * 4 overflows, this should never happen under normal conditions. Use
/// `Weght::from_vb_checked(self.size() as u64)` if you are concerned.
pub fn weight(&self) -> Weight { pub fn weight(&self) -> Weight {
let script_len = self.script_pubkey.len(); // Size is equivalent to virtual size since all bytes of a TxOut are non-witness bytes.
// In vbytes: Weight::from_vb(self.size() as u64).expect("should never happen under normal conditions")
// value (8) + script varint len + script push
Weight::from_non_witness_data_size((8 + VarInt::from(script_len).len() + script_len) as u64)
} }
/// Returns the total number of bytes that this output contributes to a transaction.
///
/// There is no difference between base size vs total size for outputs.
pub fn size(&self) -> usize { size_from_script_pubkey(&self.script_pubkey) }
/// Creates a `TxOut` with given script and the smallest possible `value` that is **not** dust /// Creates a `TxOut` with given script and the smallest possible `value` that is **not** dust
/// per current Core policy. /// per current Core policy.
/// ///
/// The current dust fee rate is 3 sat/vB. /// The current dust fee rate is 3 sat/vB.
pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self { pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
let len = script_pubkey.len() + VarInt(script_pubkey.len() as u64).len() + 8; let len = size_from_script_pubkey(&script_pubkey);
let len = len let len = len
+ if script_pubkey.is_witness_program() { + if script_pubkey.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4 32 + 4 + 1 + (107 / 4) + 4
@ -526,6 +555,12 @@ impl TxOut {
} }
} }
/// Returns the total number of bytes that this script pubkey would contribute to a transaction.
fn size_from_script_pubkey(script_pubkey: &Script) -> usize {
let len = script_pubkey.len();
Amount::SIZE + VarInt::from(len).size() + len
}
/// Bitcoin transaction. /// Bitcoin transaction.
/// ///
/// An authenticated movement of coins. /// An authenticated movement of coins.
@ -665,7 +700,10 @@ impl Transaction {
Wtxid::from_engine(enc) Wtxid::from_engine(enc)
} }
/// Returns the "weight" of this transaction, as defined by BIP141. /// Returns the weight of this transaction, as defined by BIP-141.
///
/// > Transaction weight is defined as Base transaction size * 3 + Total transaction size (ie.
/// > the same method as calculating Block weight from Base size and Total size).
/// ///
/// For transactions with an empty witness, this is simply the consensus-serialized size times /// For transactions with an empty witness, this is simply the consensus-serialized size times
/// four. For transactions with a witness, this is the non-witness consensus-serialized size /// four. For transactions with a witness, this is the non-witness consensus-serialized size
@ -682,19 +720,56 @@ impl Transaction {
/// and can therefore avoid this ambiguity. /// and can therefore avoid this ambiguity.
#[inline] #[inline]
pub fn weight(&self) -> Weight { pub fn weight(&self) -> Weight {
let inputs = self.input.iter().map(|txin| { // This is the exact definition of a weight unit, as defined by BIP-141 (quote above).
InputWeightPrediction::new( let wu = self.base_size() * 3 + self.total_size();
txin.script_sig.len(), Weight::from_wu_usize(wu)
txin.witness.iter().map(|elem| elem.len()),
)
});
let outputs = self.output.iter().map(|txout| txout.script_pubkey.len());
predict_weight(inputs, outputs)
} }
/// Returns the regular byte-wise consensus-serialized size of this transaction. /// Returns the base transaction size.
///
/// > Base transaction size is the size of the transaction serialised with the witness data stripped.
pub fn base_size(&self) -> usize {
let mut size: usize = 4; // Serialized length of a u32 for the version number.
size += VarInt::from(self.input.len()).size();
size += self.input.iter().map(|input| input.base_size()).sum::<usize>();
size += VarInt::from(self.output.len()).size();
size += self.output.iter().map(|input| input.size()).sum::<usize>();
size + absolute::LockTime::SIZE
}
/// Returns the total transaction size.
///
/// > Total transaction size is the transaction size in bytes serialized as described in BIP144,
/// > including base data and witness data.
#[inline] #[inline]
pub fn size(&self) -> usize { self.scaled_size(1).to_wu() as usize } pub fn total_size(&self) -> usize {
let mut size: usize = 4; // Serialized length of a u32 for the version number.
if self.use_segwit_serialization() {
size += 2; // 1 byte for the marker and 1 for the flag.
}
size += VarInt::from(self.input.len()).size();
size += self
.input
.iter()
.map(|input| {
if self.use_segwit_serialization() {
input.total_size()
} else {
input.base_size()
}
})
.sum::<usize>();
size += VarInt::from(self.output.len()).size();
size += self.output.iter().map(|output| output.size()).sum::<usize>();
size + absolute::LockTime::SIZE
}
/// Returns the "virtual size" (vsize) of this transaction. /// Returns the "virtual size" (vsize) of this transaction.
/// ///
@ -703,6 +778,8 @@ impl Transaction {
/// any remotely sane transaction, and a standardness-rule-correct version is available in the /// any remotely sane transaction, and a standardness-rule-correct version is available in the
/// [`policy`] module. /// [`policy`] module.
/// ///
/// > Virtual transaction size is defined as Transaction weight / 4 (rounded up to the next integer).
///
/// [`BIP141`]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki /// [`BIP141`]: https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
/// [`policy`]: ../../policy/index.html /// [`policy`]: ../../policy/index.html
#[inline] #[inline]
@ -712,72 +789,8 @@ impl Transaction {
} }
/// Returns the size of this transaction excluding the witness data. /// Returns the size of this transaction excluding the witness data.
#[deprecated(since = "0.31.0", note = "Use Transaction::stripped_size() instead")] #[deprecated(since = "0.31.0", note = "Use Transaction::base_size() instead")]
pub fn strippedsize(&self) -> usize { Self::stripped_size(self).to_wu() as usize } pub fn strippedsize(&self) -> usize { self.base_size() }
/// Returns the size of this transaction excluding the witness data.
pub fn stripped_size(&self) -> Weight {
let mut input_size: Weight = Weight::ZERO;
for input in &self.input {
input_size += TxIn::BASE_WEIGHT
+ Weight::from_wu_usize(VarInt(input.script_sig.len() as u64).len())
+ Weight::from_wu_usize(input.script_sig.len());
}
let mut output_size = Weight::ZERO;
for output in &self.output {
output_size += Weight::from_wu(8)+ // value
Weight::from_wu_usize(VarInt(output.script_pubkey.len() as u64).len()) +
Weight::from_wu_usize(output.script_pubkey.len());
}
let non_input_size: Weight =
// version:
Weight::from_wu(4)+
// count varints:
Weight::from_wu_usize(VarInt(self.input.len() as u64).len()) +
Weight::from_wu_usize(VarInt(self.output.len() as u64).len()) +
output_size +
// lock_time
Weight::from_wu(4);
non_input_size + input_size
}
/// Internal utility function for size/weight functions.
fn scaled_size(&self, scale_factor: u64) -> Weight {
let mut input_weight: Weight = Weight::ZERO;
let mut inputs_with_witnesses = 0;
for input in &self.input {
let non_scaled_input_weight: Weight = TxIn::BASE_WEIGHT
+ Weight::from_wu_usize(VarInt(input.script_sig.len() as u64).len())
+ Weight::from_wu_usize(input.script_sig.len());
input_weight += non_scaled_input_weight * scale_factor;
if !input.witness.is_empty() {
inputs_with_witnesses += 1;
input_weight += Weight::from_wu_usize(input.witness.serialized_len());
}
}
let mut output_size = Weight::ZERO;
for output in &self.output {
output_size += Weight::from_wu(8)+ // value
Weight::from_wu_usize(VarInt(output.script_pubkey.len() as u64).len()) +
Weight::from_wu_usize(output.script_pubkey.len());
}
let non_input_size =
// version:
Weight::from_wu(4) +
// count varints:
Weight::from_wu_usize(VarInt(self.input.len() as u64).len()) +
Weight::from_wu_usize(VarInt(self.output.len() as u64).len()) +
output_size +
// lock_time
Weight::from_wu(4);
if inputs_with_witnesses == 0 {
non_input_size.checked_mul(scale_factor).unwrap() + input_weight
} else {
non_input_size.checked_mul(scale_factor).unwrap()
+ input_weight
+ Weight::from_wu_usize(self.input.len() - inputs_with_witnesses + 2)
}
}
/// Checks if this is a coinbase transaction. /// Checks if this is a coinbase transaction.
/// ///
@ -950,6 +963,18 @@ impl Transaction {
} }
count count
} }
/// Returns whether or not to serialize transaction as specified in BIP-144.
fn use_segwit_serialization(&self) -> bool {
for input in &self.input {
if !input.witness.is_empty() {
return true;
}
}
// To avoid serialization ambiguity, no inputs means we use BIP141 serialization (see
// `Transaction` docs for full explanation).
self.input.is_empty()
}
} }
/// The transaction version. /// The transaction version.
@ -1048,21 +1073,15 @@ impl Encodable for Transaction {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> { fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = 0; let mut len = 0;
len += self.version.consensus_encode(w)?; len += self.version.consensus_encode(w)?;
// To avoid serialization ambiguity, no inputs means we use BIP141 serialization (see
// `Transaction` docs for full explanation). // Legacy transaction serialization format only includes inputs and outputs.
let mut have_witness = self.input.is_empty(); if !self.use_segwit_serialization() {
for input in &self.input {
if !input.witness.is_empty() {
have_witness = true;
break;
}
}
if !have_witness {
len += self.input.consensus_encode(w)?; len += self.input.consensus_encode(w)?;
len += self.output.consensus_encode(w)?; len += self.output.consensus_encode(w)?;
} else { } else {
len += 0u8.consensus_encode(w)?; // BIP-141 (segwit) transaction serialization also includes marker, flag, and witness data.
len += 1u8.consensus_encode(w)?; len += SEGWIT_MARKER.consensus_encode(w)?;
len += SEGWIT_FLAG.consensus_encode(w)?;
len += self.input.consensus_encode(w)?; len += self.input.consensus_encode(w)?;
len += self.output.consensus_encode(w)?; len += self.output.consensus_encode(w)?;
for input in &self.input { for input in &self.input {
@ -1206,7 +1225,7 @@ where
let (output_count, output_scripts_size) = output_script_lens.into_iter().fold( let (output_count, output_scripts_size) = output_script_lens.into_iter().fold(
(0, 0), (0, 0),
|(output_count, total_scripts_size), script_len| { |(output_count, total_scripts_size), script_len| {
let script_size = script_len + VarInt(script_len as u64).len(); let script_size = script_len + VarInt(script_len as u64).size();
(output_count + 1, total_scripts_size + script_size) (output_count + 1, total_scripts_size + script_size)
}, },
); );
@ -1236,8 +1255,8 @@ const fn predict_weight_internal(
// version: // version:
4 + 4 +
// count varints: // count varints:
VarInt(input_count as u64).len() + VarInt(input_count as u64).size() +
VarInt(output_count as u64).len() + VarInt(output_count as u64).size() +
output_size + output_size +
// lock_time // lock_time
4; 4;
@ -1277,7 +1296,7 @@ pub const fn predict_weight_from_slices(
i = 0; i = 0;
while i < output_script_lens.len() { while i < output_script_lens.len() {
let script_len = output_script_lens[i]; let script_len = output_script_lens[i];
output_scripts_size += script_len + VarInt(script_len as u64).len(); output_scripts_size += script_len + VarInt(script_len as u64).size();
i += 1; i += 1;
} }
@ -1357,11 +1376,11 @@ impl InputWeightPrediction {
let (count, total_size) = let (count, total_size) =
witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| { witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| {
let elem_len = *elem_len.borrow(); let elem_len = *elem_len.borrow();
let elem_size = elem_len + VarInt(elem_len as u64).len(); let elem_size = elem_len + VarInt(elem_len as u64).size();
(count + 1, total_size + elem_size) (count + 1, total_size + elem_size)
}); });
let witness_size = if count > 0 { total_size + VarInt(count as u64).len() } else { 0 }; let witness_size = if count > 0 { total_size + VarInt(count as u64).size() } else { 0 };
let script_size = input_script_len + VarInt(input_script_len as u64).len(); let script_size = input_script_len + VarInt(input_script_len as u64).size();
InputWeightPrediction { script_size, witness_size } InputWeightPrediction { script_size, witness_size }
} }
@ -1377,16 +1396,16 @@ impl InputWeightPrediction {
// for loops not supported in const fn // for loops not supported in const fn
while i < witness_element_lengths.len() { while i < witness_element_lengths.len() {
let elem_len = witness_element_lengths[i]; let elem_len = witness_element_lengths[i];
let elem_size = elem_len + VarInt(elem_len as u64).len(); let elem_size = elem_len + VarInt(elem_len as u64).size();
total_size += elem_size; total_size += elem_size;
i += 1; i += 1;
} }
let witness_size = if !witness_element_lengths.is_empty() { let witness_size = if !witness_element_lengths.is_empty() {
total_size + VarInt(witness_element_lengths.len() as u64).len() total_size + VarInt(witness_element_lengths.len() as u64).size()
} else { } else {
0 0
}; };
let script_size = input_script_len + VarInt(input_script_len as u64).len(); let script_size = input_script_len + VarInt(input_script_len as u64).size();
InputWeightPrediction { script_size, witness_size } InputWeightPrediction { script_size, witness_size }
} }
@ -1552,10 +1571,9 @@ mod tests {
"a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string() "a6eab3c14ab5272a58a5ba91505ba1a4b6d7a3a9fcbd187b6cd99a7b6d548cb7".to_string()
); );
assert_eq!(realtx.weight().to_wu() as usize, tx_bytes.len() * WITNESS_SCALE_FACTOR); assert_eq!(realtx.weight().to_wu() as usize, tx_bytes.len() * WITNESS_SCALE_FACTOR);
assert_eq!(realtx.size(), tx_bytes.len()); assert_eq!(realtx.total_size(), tx_bytes.len());
assert_eq!(realtx.vsize(), tx_bytes.len()); assert_eq!(realtx.vsize(), tx_bytes.len());
assert_eq!(realtx.stripped_size(), Weight::from_wu_usize(tx_bytes.len())); assert_eq!(realtx.base_size(), tx_bytes.len());
assert_eq!(realtx.scaled_size(4), Weight::from_wu(772));
} }
#[test] #[test]
@ -1594,29 +1612,17 @@ mod tests {
); );
const EXPECTED_WEIGHT: Weight = Weight::from_wu(442); const EXPECTED_WEIGHT: Weight = Weight::from_wu(442);
assert_eq!(realtx.weight(), EXPECTED_WEIGHT); assert_eq!(realtx.weight(), EXPECTED_WEIGHT);
assert_eq!(realtx.size(), tx_bytes.len()); assert_eq!(realtx.total_size(), tx_bytes.len());
assert_eq!(realtx.vsize(), 111); assert_eq!(realtx.vsize(), 111);
// Since
// size = stripped_size + witness_size let expected_strippedsize = (442 - realtx.total_size()) / 3;
// weight = WITNESS_SCALE_FACTOR * stripped_size + witness_size assert_eq!(realtx.base_size(), expected_strippedsize);
// then,
// stripped_size = (weight - size) / (WITNESS_SCALE_FACTOR - 1)
let expected_strippedsize: Weight = Weight::from_wu(
(EXPECTED_WEIGHT - Weight::from_wu_usize(tx_bytes.len()))
/ (Weight::from_wu_usize(WITNESS_SCALE_FACTOR - 1)),
);
assert_eq!(realtx.stripped_size(), expected_strippedsize);
// Construct a transaction without the witness data. // Construct a transaction without the witness data.
let mut tx_without_witness = realtx; let mut tx_without_witness = realtx;
tx_without_witness.input.iter_mut().for_each(|input| input.witness.clear()); tx_without_witness.input.iter_mut().for_each(|input| input.witness.clear());
assert_eq!( assert_eq!(tx_without_witness.total_size(), tx_without_witness.total_size());
tx_without_witness.weight(), assert_eq!(tx_without_witness.total_size(), expected_strippedsize);
expected_strippedsize.scale_by_witness_factor().unwrap()
);
assert_eq!(Weight::from_wu_usize(tx_without_witness.size()), expected_strippedsize);
assert_eq!(Weight::from_wu_usize(tx_without_witness.vsize()), expected_strippedsize);
assert_eq!(tx_without_witness.stripped_size(), expected_strippedsize);
assert_eq!(tx_without_witness.scaled_size(1), Weight::from_wu(83));
} }
// We temporarily abuse `Transaction` for testing consensus serde adapter. // We temporarily abuse `Transaction` for testing consensus serde adapter.
@ -1948,15 +1954,18 @@ mod tests {
for (is_segwit, tx, expected_weight) in &txs { for (is_segwit, tx, expected_weight) in &txs {
let txin_weight = if *is_segwit { TxIn::segwit_weight } else { TxIn::legacy_weight }; let txin_weight = if *is_segwit { TxIn::segwit_weight } else { TxIn::legacy_weight };
let tx: Transaction = deserialize(Vec::from_hex(tx).unwrap().as_slice()).unwrap(); let tx: Transaction = deserialize(Vec::from_hex(tx).unwrap().as_slice()).unwrap();
// The empty tx size doesn't include the segwit marker (`0001`), so, in case of segwit txs, assert_eq!(*is_segwit, tx.use_segwit_serialization());
// we have to manually add it ourselves
let segwit_marker_weight = if *is_segwit { Weight::from_wu(2) } else { Weight::ZERO }; let mut calculated_weight = empty_transaction_weight
let calculated_size = empty_transaction_weight
+ segwit_marker_weight
+ tx.input.iter().fold(Weight::ZERO, |sum, i| sum + txin_weight(i)) + tx.input.iter().fold(Weight::ZERO, |sum, i| sum + txin_weight(i))
+ tx.output.iter().fold(Weight::ZERO, |sum, o| sum + o.weight()); + tx.output.iter().fold(Weight::ZERO, |sum, o| sum + o.weight());
assert_eq!(calculated_size, tx.weight());
// The empty tx uses segwit serialization but a legacy tx does not.
if !tx.use_segwit_serialization() {
calculated_weight -= Weight::from_wu(2);
}
assert_eq!(calculated_weight, *expected_weight);
assert_eq!(tx.weight(), *expected_weight); assert_eq!(tx.weight(), *expected_weight);
} }
} }
@ -2128,7 +2137,7 @@ mod benches {
let mut tx: Transaction = deserialize(&raw_tx).unwrap(); let mut tx: Transaction = deserialize(&raw_tx).unwrap();
bh.iter(|| { bh.iter(|| {
black_box(black_box(&mut tx).size()); black_box(black_box(&mut tx).total_size());
}); });
} }

View File

@ -149,7 +149,7 @@ impl Decodable for Witness {
for i in 0..witness_elements { for i in 0..witness_elements {
let element_size_varint = VarInt::consensus_decode(r)?; let element_size_varint = VarInt::consensus_decode(r)?;
let element_size_varint_len = element_size_varint.len(); let element_size_varint_len = element_size_varint.size();
let element_size = element_size_varint.0 as usize; let element_size = element_size_varint.0 as usize;
let required_len = cursor let required_len = cursor
.checked_add(element_size) .checked_add(element_size)
@ -228,7 +228,7 @@ impl Encodable for Witness {
let indices_size = self.witness_elements * 4; let indices_size = self.witness_elements * 4;
let content_len = content_with_indices_len - indices_size; let content_len = content_with_indices_len - indices_size;
w.emit_slice(&self.content[..content_len])?; w.emit_slice(&self.content[..content_len])?;
Ok(content_len + len.len()) Ok(content_len + len.size())
} }
} }
@ -255,7 +255,7 @@ impl Witness {
let index_size = witness_elements * 4; let index_size = witness_elements * 4;
let content_size = slice let content_size = slice
.iter() .iter()
.map(|elem| elem.as_ref().len() + VarInt::from(elem.as_ref().len()).len()) .map(|elem| elem.as_ref().len() + VarInt::from(elem.as_ref().len()).size())
.sum(); .sum();
let mut content = vec![0u8; content_size + index_size]; let mut content = vec![0u8; content_size + index_size];
@ -264,9 +264,9 @@ impl Witness {
encode_cursor(&mut content, content_size, i, cursor); encode_cursor(&mut content, content_size, i, cursor);
let elem_len_varint = VarInt::from(elem.as_ref().len()); let elem_len_varint = VarInt::from(elem.as_ref().len());
elem_len_varint elem_len_varint
.consensus_encode(&mut &mut content[cursor..cursor + elem_len_varint.len()]) .consensus_encode(&mut &mut content[cursor..cursor + elem_len_varint.size()])
.expect("writers on vec don't errors, space granted by content_size"); .expect("writers on vec don't errors, space granted by content_size");
cursor += elem_len_varint.len(); cursor += elem_len_varint.size();
content[cursor..cursor + elem.as_ref().len()].copy_from_slice(elem.as_ref()); content[cursor..cursor + elem.as_ref().len()].copy_from_slice(elem.as_ref());
cursor += elem.as_ref().len(); cursor += elem.as_ref().len();
} }
@ -289,9 +289,22 @@ impl Witness {
pub fn len(&self) -> usize { self.witness_elements } pub fn len(&self) -> usize { self.witness_elements }
/// Returns the bytes required when this Witness is consensus encoded. /// Returns the bytes required when this Witness is consensus encoded.
pub fn serialized_len(&self) -> usize { #[deprecated(since = "0.31.0", note = "use size instead")]
self.iter().map(|el| VarInt::from(el.len()).len() + el.len()).sum::<usize>() pub fn serialized_len(&self) -> usize { self.size() }
+ VarInt::from(self.witness_elements).len()
/// Returns the number of bytes this witness contributes to a transactions total size.
pub fn size(&self) -> usize {
let mut size: usize = 0;
size += VarInt::from(self.witness_elements).size();
size += self
.iter()
.map(|witness_element| {
VarInt::from(witness_element.len()).size() + witness_element.len()
})
.sum::<usize>();
size
} }
/// Clear the witness. /// Clear the witness.
@ -312,7 +325,7 @@ impl Witness {
let previous_content_end = self.indices_start; let previous_content_end = self.indices_start;
let element_len_varint = VarInt::from(new_element.len()); let element_len_varint = VarInt::from(new_element.len());
let current_content_len = self.content.len(); let current_content_len = self.content.len();
let new_item_total_len = element_len_varint.len() + new_element.len(); let new_item_total_len = element_len_varint.size() + new_element.len();
self.content.resize(current_content_len + new_item_total_len + 4, 0); self.content.resize(current_content_len + new_item_total_len + 4, 0);
self.content[previous_content_end..].rotate_right(new_item_total_len); self.content[previous_content_end..].rotate_right(new_item_total_len);
@ -324,7 +337,7 @@ impl Witness {
previous_content_end, previous_content_end,
); );
let end_varint = previous_content_end + element_len_varint.len(); let end_varint = previous_content_end + element_len_varint.size();
element_len_varint element_len_varint
.consensus_encode(&mut &mut self.content[previous_content_end..end_varint]) .consensus_encode(&mut &mut self.content[previous_content_end..end_varint])
.expect("writers on vec don't error, space granted through previous resize"); .expect("writers on vec don't error, space granted through previous resize");
@ -355,7 +368,7 @@ impl Witness {
fn element_at(&self, index: usize) -> Option<&[u8]> { fn element_at(&self, index: usize) -> Option<&[u8]> {
let varint = VarInt::consensus_decode(&mut &self.content[index..]).ok()?; let varint = VarInt::consensus_decode(&mut &self.content[index..]).ok()?;
let start = index + varint.len(); let start = index + varint.size();
Some(&self.content[start..start + varint.0 as usize]) Some(&self.content[start..start + varint.0 as usize])
} }
@ -424,7 +437,7 @@ impl<'a> Iterator for Iter<'a> {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
let index = decode_cursor(self.inner, self.indices_start, self.current_index)?; let index = decode_cursor(self.inner, self.indices_start, self.current_index)?;
let varint = VarInt::consensus_decode(&mut &self.inner[index..]).ok()?; let varint = VarInt::consensus_decode(&mut &self.inner[index..]).ok()?;
let start = index + varint.len(); let start = index + varint.size();
let end = start + varint.0 as usize; let end = start + varint.0 as usize;
let slice = &self.inner[start..end]; let slice = &self.inner[start..end];
self.current_index += 1; self.current_index += 1;

View File

@ -383,13 +383,15 @@ impl_int_encodable!(i64, read_i64, emit_i64);
#[allow(clippy::len_without_is_empty)] // VarInt has on concept of 'is_empty'. #[allow(clippy::len_without_is_empty)] // VarInt has on concept of 'is_empty'.
impl VarInt { impl VarInt {
/// Gets the length of this VarInt when encoded. /// Gets the length of this VarInt when encoded.
///
/// *Important: this method is only `const` in Rust 1.46 or higher!*
///
/// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1),
/// and 9 otherwise.
#[inline] #[inline]
pub const fn len(&self) -> usize { #[deprecated(since = "0.31.0", note = "use size instead")]
pub const fn len(&self) -> usize { self.size() }
/// Returns the number of bytes this varint contributes to a transaction size.
///
/// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), and 9 otherwise.
#[inline]
pub const fn size(&self) -> usize {
match self.0 { match self.0 {
0..=0xFC => 1, 0..=0xFC => 1,
0xFD..=0xFFFF => 3, 0xFD..=0xFFFF => 3,
@ -937,7 +939,7 @@ mod tests {
fn test_varint_len(varint: VarInt, expected: usize) { fn test_varint_len(varint: VarInt, expected: usize) {
let mut encoder = vec![]; let mut encoder = vec![];
assert_eq!(varint.consensus_encode(&mut encoder).unwrap(), expected); assert_eq!(varint.consensus_encode(&mut encoder).unwrap(), expected);
assert_eq!(varint.len(), expected); assert_eq!(varint.size(), expected);
} }
fn test_varint_encode(n: u8, x: &[u8]) -> Result<VarInt, Error> { fn test_varint_encode(n: u8, x: &[u8]) -> Result<VarInt, Error> {

View File

@ -342,7 +342,7 @@ impl Serialize for TapTree {
let capacity = self let capacity = self
.script_leaves() .script_leaves()
.map(|l| { .map(|l| {
l.script().len() + VarInt::from(l.script().len()).len() // script version l.script().len() + VarInt::from(l.script().len()).size() // script version
+ 1 // merkle branch + 1 // merkle branch
+ 1 // leaf version + 1 // leaf version
}) })

View File

@ -16,9 +16,10 @@ fn do_test(data: &[u8]) {
let no_witness_len = bitcoin::consensus::encode::serialize(&tx).len(); let no_witness_len = bitcoin::consensus::encode::serialize(&tx).len();
// For 0-input transactions, `no_witness_len` will be incorrect because // For 0-input transactions, `no_witness_len` will be incorrect because
// we serialize as segwit even after "stripping the witnesses". We need // we serialize as segwit even after "stripping the witnesses". We need
// to drop two bytes (i.e. eight weight) // to drop two bytes (i.e. eight weight). Similarly, calculated_weight is
// incorrect and needs 2 wu removing for the marker/flag bytes.
if tx.input.is_empty() { if tx.input.is_empty() {
assert_eq!(no_witness_len * 3 + len - 8, calculated_weight); assert_eq!(no_witness_len * 3 + len - 8, calculated_weight - 2);
} else { } else {
assert_eq!(no_witness_len * 3 + len, calculated_weight); assert_eq!(no_witness_len * 3 + len, calculated_weight);
} }