use core::convert::TryFrom;
use core::default::Default;
use core::{cmp, fmt, str};
use hashes::{self, sha256d, Hash};
use internals::write_err;
use super::Weight;
use crate::blockdata::locktime::absolute::{self, Height, Time};
use crate::blockdata::locktime::relative;
use crate::blockdata::script::{Script, ScriptBuf};
use crate::blockdata::witness::Witness;
#[cfg(feature = "bitcoinconsensus")]
pub use crate::consensus::validation::TxVerifyError;
use crate::consensus::{encode, Decodable, Encodable};
use crate::hash_types::{Txid, Wtxid};
use crate::internal_macros::impl_consensus_encoding;
use crate::parse::impl_parse_str_from_int_infallible;
use crate::prelude::*;
use crate::script::Push;
#[cfg(doc)]
use crate::sighash::{EcdsaSighashType, TapSighashType};
use crate::string::FromHexStr;
use crate::{io, Amount, VarInt};
const SEGWIT_MARKER: u8 = 0x00;
const SEGWIT_FLAG: u8 = 0x01;
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct OutPoint {
pub txid: Txid,
pub vout: u32,
}
#[cfg(feature = "serde")]
crate::serde_utils::serde_struct_human_string_impl!(OutPoint, "an OutPoint", txid, vout);
impl OutPoint {
const SIZE: usize = 32 + 4;
#[inline]
pub fn new(txid: Txid, vout: u32) -> OutPoint {
OutPoint { txid, vout }
}
#[inline]
pub fn null() -> OutPoint {
OutPoint { txid: Hash::all_zeros(), vout: u32::MAX }
}
#[inline]
pub fn is_null(&self) -> bool {
*self == OutPoint::null()
}
}
impl Default for OutPoint {
fn default() -> Self {
OutPoint::null()
}
}
impl fmt::Display for OutPoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.txid, self.vout)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseOutPointError {
Txid(hex::HexToArrayError),
Vout(crate::error::ParseIntError),
Format,
TooLong,
VoutNotCanonical,
}
impl fmt::Display for ParseOutPointError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ParseOutPointError::*;
match *self {
Txid(ref e) => write_err!(f, "error parsing TXID"; e),
Vout(ref e) => write_err!(f, "error parsing vout"; e),
Format => write!(f, "OutPoint not in <txid>:<vout> format"),
TooLong => write!(f, "vout should be at most 10 digits"),
VoutNotCanonical => write!(f, "no leading zeroes or + allowed in vout part"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseOutPointError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use ParseOutPointError::*;
match self {
Txid(e) => Some(e),
Vout(e) => Some(e),
Format | TooLong | VoutNotCanonical => None,
}
}
}
fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
if s.len() > 1 {
let first = s.chars().next().unwrap();
if first == '0' || first == '+' {
return Err(ParseOutPointError::VoutNotCanonical);
}
}
crate::parse::int(s).map_err(ParseOutPointError::Vout)
}
impl core::str::FromStr for OutPoint {
type Err = ParseOutPointError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() > 75 {
return Err(ParseOutPointError::TooLong);
}
let find = s.find(':');
if find.is_none() || find != s.rfind(':') {
return Err(ParseOutPointError::Format);
}
let colon = find.unwrap();
if colon == 0 || colon == s.len() - 1 {
return Err(ParseOutPointError::Format);
}
Ok(OutPoint {
txid: s[..colon].parse().map_err(ParseOutPointError::Txid)?,
vout: parse_vout(&s[colon + 1..])?,
})
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct TxIn {
pub previous_output: OutPoint,
pub script_sig: ScriptBuf,
pub sequence: Sequence,
pub witness: Witness,
}
impl TxIn {
pub fn enables_lock_time(&self) -> bool {
self.sequence != Sequence::MAX
}
pub fn legacy_weight(&self) -> Weight {
Weight::from_non_witness_data_size(self.base_size() as u64)
}
pub fn segwit_weight(&self) -> Weight {
Weight::from_non_witness_data_size(self.base_size() as u64)
+ Weight::from_witness_data_size(self.witness.size() as u64)
}
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
}
pub fn total_size(&self) -> usize {
self.base_size() + self.witness.size()
}
}
impl Default for TxIn {
fn default() -> TxIn {
TxIn {
previous_output: OutPoint::default(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::default(),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Sequence(pub u32);
impl Sequence {
pub const MAX: Self = Sequence(0xFFFFFFFF);
pub const ZERO: Self = Sequence(0);
pub const ENABLE_LOCKTIME_NO_RBF: Self = Sequence::MIN_NO_RBF;
pub const ENABLE_RBF_NO_LOCKTIME: Self = Sequence(0xFFFFFFFD);
const SIZE: usize = 4;
const MIN_NO_RBF: Self = Sequence(0xFFFFFFFE);
const LOCK_TIME_DISABLE_FLAG_MASK: u32 = 0x80000000;
const LOCK_TYPE_MASK: u32 = 0x00400000;
#[deprecated(since = "0.31.0", note = "Use Self::MAX instead")]
pub const fn max_value() -> Self {
Self::MAX
}
#[inline]
pub fn enables_absolute_lock_time(&self) -> bool {
*self != Sequence::MAX
}
#[inline]
pub fn is_final(&self) -> bool {
!self.enables_absolute_lock_time()
}
#[inline]
pub fn is_rbf(&self) -> bool {
*self < Sequence::MIN_NO_RBF
}
#[inline]
pub fn is_relative_lock_time(&self) -> bool {
self.0 & Sequence::LOCK_TIME_DISABLE_FLAG_MASK == 0
}
#[inline]
pub fn is_height_locked(&self) -> bool {
self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK == 0)
}
#[inline]
pub fn is_time_locked(&self) -> bool {
self.is_relative_lock_time() & (self.0 & Sequence::LOCK_TYPE_MASK > 0)
}
#[inline]
pub fn from_height(height: u16) -> Self {
Sequence(u32::from(height))
}
#[inline]
pub fn from_512_second_intervals(intervals: u16) -> Self {
Sequence(u32::from(intervals) | Sequence::LOCK_TYPE_MASK)
}
#[inline]
pub fn from_seconds_floor(seconds: u32) -> Result<Self, relative::Error> {
if let Ok(interval) = u16::try_from(seconds / 512) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(relative::Error::IntegerOverflow(seconds))
}
}
#[inline]
pub fn from_seconds_ceil(seconds: u32) -> Result<Self, relative::Error> {
if let Ok(interval) = u16::try_from((seconds + 511) / 512) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(relative::Error::IntegerOverflow(seconds))
}
}
#[inline]
pub fn from_consensus(n: u32) -> Self {
Sequence(n)
}
#[inline]
pub fn to_consensus_u32(self) -> u32 {
self.0
}
#[inline]
pub fn to_relative_lock_time(&self) -> Option<relative::LockTime> {
use crate::locktime::relative::{Height, LockTime, Time};
if !self.is_relative_lock_time() {
return None;
}
let lock_value = self.low_u16();
if self.is_time_locked() {
Some(LockTime::from(Time::from_512_second_intervals(lock_value)))
} else {
Some(LockTime::from(Height::from(lock_value)))
}
}
fn low_u16(&self) -> u16 {
self.0 as u16
}
}
impl FromHexStr for Sequence {
type Error = crate::parse::ParseIntError;
fn from_hex_str_no_prefix<S: AsRef<str> + Into<String>>(s: S) -> Result<Self, Self::Error> {
let sequence = crate::parse::hex_u32(s)?;
Ok(Self::from_consensus(sequence))
}
}
impl Default for Sequence {
fn default() -> Self {
Sequence::MAX
}
}
impl From<Sequence> for u32 {
fn from(sequence: Sequence) -> u32 {
sequence.0
}
}
impl fmt::Display for Sequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl fmt::LowerHex for Sequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&self.0, f)
}
}
impl fmt::UpperHex for Sequence {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::UpperHex::fmt(&self.0, f)
}
}
impl_parse_str_from_int_infallible!(Sequence, u32, from_consensus);
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct TxOut {
pub value: Amount,
pub script_pubkey: ScriptBuf,
}
impl TxOut {
pub const NULL: Self =
TxOut { value: Amount::from_sat(0xffffffffffffffff), script_pubkey: ScriptBuf::new() };
pub fn weight(&self) -> Weight {
Weight::from_vb(self.size() as u64).expect("should never happen under normal conditions")
}
pub fn size(&self) -> usize {
size_from_script_pubkey(&self.script_pubkey)
}
pub fn minimal_non_dust(script_pubkey: ScriptBuf) -> Self {
let len = size_from_script_pubkey(&script_pubkey);
let len = len
+ if script_pubkey.is_witness_program() {
32 + 4 + 1 + (107 / 4) + 4
} else {
32 + 4 + 1 + 107 + 4
};
let dust_amount = (len as u64) * 3;
TxOut {
value: Amount::from_sat(dust_amount + 1), script_pubkey,
}
}
}
fn size_from_script_pubkey(script_pubkey: &Script) -> usize {
let len = script_pubkey.len();
Amount::SIZE + VarInt::from(len).size() + len
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
pub struct Transaction {
pub version: Version,
pub lock_time: absolute::LockTime,
pub input: Vec<TxIn>,
pub output: Vec<TxOut>,
}
impl cmp::PartialOrd for Transaction {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl cmp::Ord for Transaction {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.version
.cmp(&other.version)
.then(self.lock_time.to_consensus_u32().cmp(&other.lock_time.to_consensus_u32()))
.then(self.input.cmp(&other.input))
.then(self.output.cmp(&other.output))
}
}
impl Transaction {
pub const MAX_STANDARD_WEIGHT: Weight = Weight::from_wu(400_000);
pub fn ntxid(&self) -> sha256d::Hash {
let cloned_tx = Transaction {
version: self.version,
lock_time: self.lock_time,
input: self
.input
.iter()
.map(|txin| TxIn {
script_sig: ScriptBuf::new(),
witness: Witness::default(),
..*txin
})
.collect(),
output: self.output.clone(),
};
cloned_tx.txid().into()
}
pub fn txid(&self) -> Txid {
let mut enc = Txid::engine();
self.version.consensus_encode(&mut enc).expect("engines don't error");
self.input.consensus_encode(&mut enc).expect("engines don't error");
self.output.consensus_encode(&mut enc).expect("engines don't error");
self.lock_time.consensus_encode(&mut enc).expect("engines don't error");
Txid::from_engine(enc)
}
pub fn wtxid(&self) -> Wtxid {
let mut enc = Wtxid::engine();
self.consensus_encode(&mut enc).expect("engines don't error");
Wtxid::from_engine(enc)
}
#[inline]
pub fn weight(&self) -> Weight {
let wu = self.base_size() * 3 + self.total_size();
Weight::from_wu_usize(wu)
}
pub fn base_size(&self) -> usize {
let mut size: usize = 4;
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
}
#[inline]
pub fn total_size(&self) -> usize {
let mut size: usize = 4;
if self.use_segwit_serialization() {
size += 2; }
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
}
#[inline]
pub fn vsize(&self) -> usize {
self.weight().to_vbytes_ceil() as usize
}
#[deprecated(since = "0.31.0", note = "Use Transaction::base_size() instead")]
pub fn strippedsize(&self) -> usize {
self.base_size()
}
#[doc(alias = "is_coin_base")] pub fn is_coinbase(&self) -> bool {
self.input.len() == 1 && self.input[0].previous_output.is_null()
}
#[deprecated(since = "0.31.0", note = "use is_coinbase instead")]
pub fn is_coin_base(&self) -> bool {
self.is_coinbase()
}
pub fn is_explicitly_rbf(&self) -> bool {
self.input.iter().any(|input| input.sequence.is_rbf())
}
pub fn is_absolute_timelock_satisfied(&self, height: Height, time: Time) -> bool {
if !self.is_lock_time_enabled() {
return true;
}
self.lock_time.is_satisfied_by(height, time)
}
pub fn is_lock_time_enabled(&self) -> bool {
self.input.iter().any(|i| i.enables_lock_time())
}
pub fn script_pubkey_lens(&self) -> impl Iterator<Item = usize> + '_ {
self.output.iter().map(|txout| txout.script_pubkey.len())
}
pub fn total_sigop_cost<S>(&self, mut spent: S) -> usize
where
S: FnMut(&OutPoint) -> Option<TxOut>,
{
let mut cost = self.count_p2pk_p2pkh_sigops().saturating_mul(4);
cost = cost.saturating_add(self.count_p2sh_sigops(&mut spent).saturating_mul(4));
cost.saturating_add(self.count_witness_sigops(&mut spent))
}
fn count_p2pk_p2pkh_sigops(&self) -> usize {
let mut count: usize = 0;
for input in &self.input {
count = count.saturating_add(input.script_sig.count_sigops_legacy());
}
for output in &self.output {
count = count.saturating_add(output.script_pubkey.count_sigops_legacy());
}
count
}
fn count_p2sh_sigops<S>(&self, spent: &mut S) -> usize
where
S: FnMut(&OutPoint) -> Option<TxOut>,
{
fn count_sigops(prevout: &TxOut, input: &TxIn) -> usize {
let mut count: usize = 0;
if prevout.script_pubkey.is_p2sh() {
if let Some(Push::Data(redeem)) = input.script_sig.last_pushdata() {
count =
count.saturating_add(Script::from_bytes(redeem.as_bytes()).count_sigops());
}
}
count
}
let mut count: usize = 0;
for input in &self.input {
if let Some(prevout) = spent(&input.previous_output) {
count = count.saturating_add(count_sigops(&prevout, input));
}
}
count
}
fn count_witness_sigops<S>(&self, spent: &mut S) -> usize
where
S: FnMut(&OutPoint) -> Option<TxOut>,
{
fn count_sigops_with_witness_program(witness: &Witness, witness_program: &Script) -> usize {
if witness_program.is_p2wpkh() {
1
} else if witness_program.is_p2wsh() {
return witness
.last()
.map(Script::from_bytes)
.map(|s| s.count_sigops())
.unwrap_or(0);
} else {
0
}
}
fn count_sigops(prevout: TxOut, input: &TxIn) -> usize {
let script_sig = &input.script_sig;
let witness = &input.witness;
let witness_program = if prevout.script_pubkey.is_witness_program() {
&prevout.script_pubkey
} else if prevout.script_pubkey.is_p2sh() && script_sig.is_push_only() {
if let Some(Push::Data(push_bytes)) = script_sig.last_pushdata() {
Script::from_bytes(push_bytes.as_bytes())
} else {
return 0;
}
} else {
return 0;
};
count_sigops_with_witness_program(witness, witness_program)
}
let mut count: usize = 0;
for input in &self.input {
if let Some(prevout) = spent(&input.previous_output) {
count = count.saturating_add(count_sigops(prevout, input));
}
}
count
}
fn use_segwit_serialization(&self) -> bool {
for input in &self.input {
if !input.witness.is_empty() {
return true;
}
}
self.input.is_empty()
}
}
#[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 {
pub const ONE: Self = Self(1);
pub const TWO: Self = Self(2);
pub fn non_standard(version: i32) -> Version {
Self(version)
}
pub fn is_standard(&self) -> bool {
*self == Version::ONE || *self == 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 {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let len = self.txid.consensus_encode(w)?;
Ok(len + self.vout.consensus_encode(w)?)
}
}
impl Decodable for OutPoint {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Ok(OutPoint {
txid: Decodable::consensus_decode(r)?,
vout: Decodable::consensus_decode(r)?,
})
}
}
impl Encodable for TxIn {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = 0;
len += self.previous_output.consensus_encode(w)?;
len += self.script_sig.consensus_encode(w)?;
len += self.sequence.consensus_encode(w)?;
Ok(len)
}
}
impl Decodable for TxIn {
#[inline]
fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
r: &mut R,
) -> Result<Self, encode::Error> {
Ok(TxIn {
previous_output: Decodable::consensus_decode_from_finite_reader(r)?,
script_sig: Decodable::consensus_decode_from_finite_reader(r)?,
sequence: Decodable::consensus_decode_from_finite_reader(r)?,
witness: Witness::default(),
})
}
}
impl Encodable for Sequence {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for Sequence {
fn consensus_decode<R: io::Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Decodable::consensus_decode(r).map(Sequence)
}
}
impl Encodable for Transaction {
fn consensus_encode<W: io::Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
let mut len = 0;
len += self.version.consensus_encode(w)?;
if !self.use_segwit_serialization() {
len += self.input.consensus_encode(w)?;
len += self.output.consensus_encode(w)?;
} else {
len += SEGWIT_MARKER.consensus_encode(w)?;
len += SEGWIT_FLAG.consensus_encode(w)?;
len += self.input.consensus_encode(w)?;
len += self.output.consensus_encode(w)?;
for input in &self.input {
len += input.witness.consensus_encode(w)?;
}
}
len += self.lock_time.consensus_encode(w)?;
Ok(len)
}
}
impl Decodable for Transaction {
fn consensus_decode_from_finite_reader<R: io::Read + ?Sized>(
r: &mut R,
) -> Result<Self, encode::Error> {
let version = Version::consensus_decode_from_finite_reader(r)?;
let input = Vec::<TxIn>::consensus_decode_from_finite_reader(r)?;
if input.is_empty() {
let segwit_flag = u8::consensus_decode_from_finite_reader(r)?;
match segwit_flag {
1 => {
let mut input = Vec::<TxIn>::consensus_decode_from_finite_reader(r)?;
let output = Vec::<TxOut>::consensus_decode_from_finite_reader(r)?;
for txin in input.iter_mut() {
txin.witness = Decodable::consensus_decode_from_finite_reader(r)?;
}
if !input.is_empty() && input.iter().all(|input| input.witness.is_empty()) {
Err(encode::Error::ParseFailed("witness flag set but no witnesses present"))
} else {
Ok(Transaction {
version,
input,
output,
lock_time: Decodable::consensus_decode_from_finite_reader(r)?,
})
}
}
x => Err(encode::Error::UnsupportedSegwitFlag(x)),
}
} else {
Ok(Transaction {
version,
input,
output: Decodable::consensus_decode_from_finite_reader(r)?,
lock_time: Decodable::consensus_decode_from_finite_reader(r)?,
})
}
}
}
impl From<Transaction> for Txid {
fn from(tx: Transaction) -> Txid {
tx.txid()
}
}
impl From<&Transaction> for Txid {
fn from(tx: &Transaction) -> Txid {
tx.txid()
}
}
impl From<Transaction> for Wtxid {
fn from(tx: Transaction) -> Wtxid {
tx.wtxid()
}
}
impl From<&Transaction> for Wtxid {
fn from(tx: &Transaction) -> Wtxid {
tx.wtxid()
}
}
pub fn predict_weight<I, O>(inputs: I, output_script_lens: O) -> Weight
where
I: IntoIterator<Item = InputWeightPrediction>,
O: IntoIterator<Item = usize>,
{
let (input_count, partial_input_weight, inputs_with_witnesses) = inputs.into_iter().fold(
(0, 0, 0),
|(count, partial_input_weight, inputs_with_witnesses), prediction| {
(
count + 1,
partial_input_weight + prediction.script_size * 4 + prediction.witness_size,
inputs_with_witnesses + (prediction.witness_size > 0) as usize,
)
},
);
let (output_count, output_scripts_size) = output_script_lens.into_iter().fold(
(0, 0),
|(output_count, total_scripts_size), script_len| {
let script_size = script_len + VarInt(script_len as u64).size();
(output_count + 1, total_scripts_size + script_size)
},
);
predict_weight_internal(
input_count,
partial_input_weight,
inputs_with_witnesses,
output_count,
output_scripts_size,
)
}
const fn predict_weight_internal(
input_count: usize,
partial_input_weight: usize,
inputs_with_witnesses: usize,
output_count: usize,
output_scripts_size: usize,
) -> Weight {
let input_weight = partial_input_weight + input_count * 4 * (32 + 4 + 4);
let output_size = 8 * output_count + output_scripts_size;
let non_input_size =
4 +
VarInt(input_count as u64).size() +
VarInt(output_count as u64).size() +
output_size +
4;
let weight = if inputs_with_witnesses == 0 {
non_input_size * 4 + input_weight
} else {
non_input_size * 4 + input_weight + input_count - inputs_with_witnesses + 2
};
Weight::from_wu(weight as u64)
}
pub const fn predict_weight_from_slices(
inputs: &[InputWeightPrediction],
output_script_lens: &[usize],
) -> Weight {
let mut partial_input_weight = 0;
let mut inputs_with_witnesses = 0;
let mut i = 0;
while i < inputs.len() {
let prediction = inputs[i];
partial_input_weight += prediction.script_size * 4 + prediction.witness_size;
inputs_with_witnesses += (prediction.witness_size > 0) as usize;
i += 1;
}
let mut output_scripts_size = 0;
i = 0;
while i < output_script_lens.len() {
let script_len = output_script_lens[i];
output_scripts_size += script_len + VarInt(script_len as u64).size();
i += 1;
}
predict_weight_internal(
inputs.len(),
partial_input_weight,
inputs_with_witnesses,
output_script_lens.len(),
output_scripts_size,
)
}
#[derive(Copy, Clone, Debug)]
pub struct InputWeightPrediction {
script_size: usize,
witness_size: usize,
}
impl InputWeightPrediction {
pub const P2WPKH_MAX: Self = InputWeightPrediction::from_slice(0, &[73, 33]);
pub const P2TR_KEY_DEFAULT_SIGHASH: Self = InputWeightPrediction::from_slice(0, &[64]);
pub const P2TR_KEY_NON_DEFAULT_SIGHASH: Self = InputWeightPrediction::from_slice(0, &[65]);
pub const fn ground_p2wpkh(bytes_to_grind: usize) -> Self {
let der_signature_size = 10 + (62 - bytes_to_grind);
InputWeightPrediction::from_slice(0, &[der_signature_size, 33])
}
pub fn new<T>(input_script_len: usize, witness_element_lengths: T) -> Self
where
T: IntoIterator,
T::Item: Borrow<usize>,
{
let (count, total_size) =
witness_element_lengths.into_iter().fold((0, 0), |(count, total_size), elem_len| {
let elem_len = *elem_len.borrow();
let elem_size = elem_len + VarInt(elem_len as u64).size();
(count + 1, total_size + elem_size)
});
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).size();
InputWeightPrediction { script_size, witness_size }
}
pub const fn from_slice(input_script_len: usize, witness_element_lengths: &[usize]) -> Self {
let mut i = 0;
let mut total_size = 0;
while i < witness_element_lengths.len() {
let elem_len = witness_element_lengths[i];
let elem_size = elem_len + VarInt(elem_len as u64).size();
total_size += elem_size;
i += 1;
}
let witness_size = if !witness_element_lengths.is_empty() {
total_size + VarInt(witness_element_lengths.len() as u64).size()
} else {
0
};
let script_size = input_script_len + VarInt(input_script_len as u64).size();
InputWeightPrediction { script_size, witness_size }
}
}