use stratum_core::{
bitcoin::{
absolute::LockTime,
script::ScriptBuf,
transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut, Version},
witness::Witness,
},
template_distribution_sv2::CoinbaseOutputConstraints,
};
const OFFSET_ADDITIONAL_SIZE: u32 = 43;
const OFFSET_MAX_SIGOPS: u16 = 4;
pub fn coinbase_output_constraints_message(
coinbase_outputs: Vec<TxOut>,
) -> CoinbaseOutputConstraints {
let max_size: u32 = coinbase_outputs.iter().map(|o| o.size() as u32).sum();
tracing::debug!(
max_size,
outputs_count = coinbase_outputs.len(),
"Calculated max coinbase output size"
);
let dummy_coinbase = Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::from(vec![vec![0; 32]]),
}],
output: coinbase_outputs,
};
let max_sigops = dummy_coinbase.total_sigop_cost(|_| None) as u16;
tracing::debug!(max_sigops, "Calculated max sigops for coinbase");
CoinbaseOutputConstraints {
coinbase_output_max_additional_size: max_size,
coinbase_output_max_additional_sigops: max_sigops,
}
}
pub fn coinbase_output_constraints_message_with_offset(
coinbase_outputs: Vec<TxOut>,
) -> CoinbaseOutputConstraints {
let constraints = coinbase_output_constraints_message(coinbase_outputs);
CoinbaseOutputConstraints {
coinbase_output_max_additional_size: constraints.coinbase_output_max_additional_size
+ OFFSET_ADDITIONAL_SIZE,
coinbase_output_max_additional_sigops: constraints.coinbase_output_max_additional_sigops
+ OFFSET_MAX_SIGOPS,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use stratum_core::bitcoin::{Address, Amount, Network, TxOut};
#[test]
fn test_offset_values_rationale() {
let addresses = vec![
("p2pkh", "19drg6CgjcvqFZSW5FLWdmqTBBeFLS5iC7"),
("p2sh", "18tRWCdi2Fc9CM57fUfmFK3ZC6cpGQeBkV"),
("p2wpkh", "bc1qwq787dzgj2w8hh58t4clr594y0cjgjashr0fz5"),
("p2wsh", "bc1qn04san36d0j76j0xksz2tesmtww7uf24j2x6v3"),
(
"p2tr",
"bc1p8fltq0npm605tzl22gqewhy9dt5l25m7x67832vyhh9aem24rgdsgqwtpu",
),
];
let mut max_size = 0u32;
let mut max_sigops = 0u16;
let mut max_size_type = "";
let mut max_sigops_type = "";
for (name, addr_str) in addresses {
let addr = Address::from_str(addr_str)
.unwrap()
.require_network(Network::Bitcoin)
.unwrap();
let script = addr.script_pubkey();
let coinbase_outputs = vec![TxOut {
value: Amount::from_sat(50_0000_0000),
script_pubkey: script.clone(),
}];
let dummy_coinbase = Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::from(vec![vec![0; 32]]),
}],
output: coinbase_outputs.clone(),
};
let size_of_current_address: u32 =
coinbase_outputs.iter().map(|o| o.size() as u32).sum();
let sigops_of_current_address = dummy_coinbase.total_sigop_cost(|_| None) as u16;
if size_of_current_address > max_size {
max_size = size_of_current_address;
max_size_type = name;
}
if sigops_of_current_address > max_sigops {
max_sigops = sigops_of_current_address;
max_sigops_type = name;
}
println!("--- {}", name);
println!("address: {}", addr);
println!("script: {}", script.to_hex_string());
println!(
"max_size={} max_sigops={}",
size_of_current_address, sigops_of_current_address
);
}
println!("\n=== worst case summary ===");
println!("largest output size: {} ({})", max_size, max_size_type);
println!("largest sigops: {} ({})", max_sigops, max_sigops_type);
}
#[test]
fn test_coinbase_output_constraints_message_exact() {
let coinbase_outputs = vec![TxOut {
value: Amount::from_sat(50_0000_0000),
script_pubkey: ScriptBuf::from_hex(
"0020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
.unwrap(),
}];
let result = coinbase_output_constraints_message(coinbase_outputs.clone());
let expected_size: u32 = coinbase_outputs.iter().map(|o| o.size() as u32).sum();
let dummy_coinbase = Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: ScriptBuf::new(),
sequence: Sequence::MAX,
witness: Witness::from(vec![vec![0; 32]]),
}],
output: coinbase_outputs,
};
let expected_sigops = dummy_coinbase.total_sigop_cost(|_| None) as u16;
assert_eq!(result.coinbase_output_max_additional_size, expected_size);
assert_eq!(
result.coinbase_output_max_additional_sigops,
expected_sigops
);
}
#[test]
fn test_coinbase_output_constraints_message_with_offset() {
let coinbase_outputs = vec![TxOut {
value: Amount::from_sat(50_0000_0000),
script_pubkey: ScriptBuf::from_hex(
"0020aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
.unwrap(),
}];
let result_with_offset =
coinbase_output_constraints_message_with_offset(coinbase_outputs.clone());
let result_exact = coinbase_output_constraints_message(coinbase_outputs);
assert_eq!(
result_with_offset.coinbase_output_max_additional_size,
result_exact.coinbase_output_max_additional_size + OFFSET_ADDITIONAL_SIZE
);
assert_eq!(
result_with_offset.coinbase_output_max_additional_sigops,
result_exact.coinbase_output_max_additional_sigops + OFFSET_MAX_SIGOPS
);
}
#[test]
fn test_offset_values_are_applied_correctly() {
let addr = Address::from_str("bc1qwq787dzgj2w8hh58t4clr594y0cjgjashr0fz5")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap();
let coinbase_outputs = vec![TxOut {
value: Amount::from_sat(50_0000_0000),
script_pubkey: addr.script_pubkey(),
}];
let exact = coinbase_output_constraints_message(coinbase_outputs.clone());
let with_offset = coinbase_output_constraints_message_with_offset(coinbase_outputs);
assert_eq!(
with_offset.coinbase_output_max_additional_size,
exact.coinbase_output_max_additional_size + OFFSET_ADDITIONAL_SIZE
);
assert_eq!(
with_offset.coinbase_output_max_additional_sigops,
exact.coinbase_output_max_additional_sigops + OFFSET_MAX_SIGOPS
);
}
}