use crate::jet::Jet;
use std::{cmp, fmt};
use crate::value::Word;
#[cfg(feature = "elements")]
use elements::encode::Encodable;
#[cfg(feature = "elements")]
use std::{convert::TryFrom, io};
#[cfg(feature = "bitcoin")]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct U32Weight(u32);
#[cfg(feature = "bitcoin")]
impl std::ops::Sub for U32Weight {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0.saturating_sub(rhs.0))
}
}
#[cfg(feature = "bitcoin")]
impl From<bitcoin::Weight> for U32Weight {
fn from(value: bitcoin::Weight) -> Self {
Self(u32::try_from(value.to_wu()).unwrap_or(u32::MAX))
}
}
#[cfg(feature = "bitcoin")]
impl From<U32Weight> for bitcoin::Weight {
fn from(value: U32Weight) -> Self {
bitcoin::Weight::from_wu(u64::from(value.0))
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Cost(u32);
impl Cost {
const OVERHEAD: Self = Cost(100);
const NEVER_EXECUTED: Self = Cost(0);
pub const CONSENSUS_MAX: Self = Cost(4_000_050_000);
pub const fn of_type(bit_width: usize) -> Self {
Cost(bit_width as u32)
}
pub const fn from_milliweight(milliweight: u32) -> Self {
Cost(milliweight)
}
pub fn is_consensus_valid(self) -> bool {
self <= Self::CONSENSUS_MAX
}
#[cfg(feature = "elements")]
fn get_budget(script_witness: &Vec<Vec<u8>>) -> U32Weight {
let mut sink = io::sink();
let witness_stack_serialized_len = script_witness
.consensus_encode(&mut sink)
.expect("writing to sink never fails");
let budget = u32::try_from(witness_stack_serialized_len)
.expect("Serialized witness stack must be shorter than 2^32 elements")
.saturating_add(50);
U32Weight(budget)
}
#[cfg(feature = "elements")]
pub fn is_budget_valid(self, script_witness: &Vec<Vec<u8>>) -> bool {
let budget = Self::get_budget(script_witness);
self.0 <= budget.0.saturating_mul(1000)
}
#[cfg(feature = "elements")]
pub fn get_padding(self, script_witness: &Vec<Vec<u8>>) -> Option<Vec<u8>> {
let weight = U32Weight::from(self);
let budget = Self::get_budget(script_witness);
if weight <= budget {
return None;
}
let required_padding = weight - budget - U32Weight(2);
let padding_len = required_padding.0 as usize; let annex_bytes: Vec<u8> = std::iter::once(0x50)
.chain(std::iter::repeat(0x00).take(padding_len))
.collect();
Some(annex_bytes)
}
}
impl fmt::Display for Cost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl std::ops::Add for Cost {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Cost(self.0.saturating_add(rhs.0))
}
}
#[cfg(feature = "bitcoin")]
impl From<U32Weight> for Cost {
fn from(value: U32Weight) -> Self {
Self(value.0.saturating_mul(1000))
}
}
#[cfg(feature = "bitcoin")]
impl From<Cost> for U32Weight {
fn from(value: Cost) -> Self {
Self(value.0.saturating_add(999) / 1000)
}
}
#[cfg(feature = "bitcoin")]
impl From<bitcoin::Weight> for Cost {
fn from(value: bitcoin::Weight) -> Self {
Self(U32Weight::from(value).0.saturating_mul(1000))
}
}
#[cfg(feature = "bitcoin")]
impl From<Cost> for bitcoin::Weight {
fn from(value: Cost) -> Self {
bitcoin::Weight::from_wu(u64::from(U32Weight::from(value).0))
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeBounds {
pub extra_cells: usize,
pub extra_frames: usize,
pub cost: Cost,
}
impl NodeBounds {
const NOP: Self = NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::OVERHEAD,
};
const NEVER_EXECUTED: Self = NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::NEVER_EXECUTED,
};
fn from_child(child: Self) -> Self {
NodeBounds {
extra_cells: child.extra_cells,
extra_frames: child.extra_frames,
cost: Cost::OVERHEAD + child.cost,
}
}
pub fn iden(target_type: usize) -> NodeBounds {
NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::OVERHEAD + Cost::of_type(target_type),
}
}
pub const fn unit() -> NodeBounds {
NodeBounds::NOP
}
pub fn injl(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn injr(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn take(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn drop(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn comp(left: Self, right: Self, mid_ty_bit_width: usize) -> NodeBounds {
NodeBounds {
extra_cells: mid_ty_bit_width + cmp::max(left.extra_cells, right.extra_cells),
extra_frames: 1 + cmp::max(left.extra_frames, right.extra_frames),
cost: Cost::OVERHEAD + Cost::of_type(mid_ty_bit_width) + left.cost + right.cost,
}
}
pub fn case(left: Self, right: Self) -> NodeBounds {
NodeBounds {
extra_cells: cmp::max(left.extra_cells, right.extra_cells),
extra_frames: cmp::max(left.extra_frames, right.extra_frames),
cost: Cost::OVERHEAD + cmp::max(left.cost, right.cost),
}
}
pub fn assertl(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn assertr(child: Self) -> NodeBounds {
Self::from_child(child)
}
pub fn pair(left: Self, right: Self) -> NodeBounds {
NodeBounds {
extra_cells: cmp::max(left.extra_cells, right.extra_cells),
extra_frames: cmp::max(left.extra_frames, right.extra_frames),
cost: Cost::OVERHEAD + left.cost + right.cost,
}
}
pub fn disconnect(
left: Self,
right: Self,
left_target_b_bit_width: usize, left_source_bit_width: usize,
left_target_bit_width: usize,
) -> NodeBounds {
NodeBounds {
extra_cells: left_source_bit_width
+ left_target_bit_width
+ cmp::max(left.extra_cells, right.extra_cells),
extra_frames: 2 + cmp::max(left.extra_frames, right.extra_frames),
cost: Cost::OVERHEAD
+ Cost::of_type(left_source_bit_width)
+ Cost::of_type(left_source_bit_width)
+ Cost::of_type(left_target_bit_width)
+ Cost::of_type(left_target_b_bit_width)
+ left.cost
+ right.cost,
}
}
pub fn witness(target_ty_bit_width: usize) -> NodeBounds {
NodeBounds {
extra_cells: target_ty_bit_width,
extra_frames: 0,
cost: Cost::OVERHEAD + Cost::of_type(target_ty_bit_width),
}
}
pub fn jet<J: Jet>(jet: J) -> NodeBounds {
NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::OVERHEAD + jet.cost(),
}
}
pub fn const_word(word: &Word) -> NodeBounds {
NodeBounds {
extra_cells: 0,
extra_frames: 0,
cost: Cost::OVERHEAD + Cost::of_type(word.len()),
}
}
pub const fn fail() -> NodeBounds {
NodeBounds::NEVER_EXECUTED
}
}
pub(crate) const IO_EXTRA_FRAMES: usize = 2;
#[cfg(test)]
mod tests {
use super::*;
use simplicity_sys::ffi::bounded::cost_overhead;
#[test]
fn test_overhead() {
assert_eq!(Cost::OVERHEAD.0, cost_overhead());
}
#[test]
#[cfg(feature = "bitcoin")]
fn cost_to_weight() {
let test_vectors = vec![
(Cost::NEVER_EXECUTED, 0),
(Cost::from_milliweight(1), 1),
(Cost::from_milliweight(999), 1),
(Cost::from_milliweight(1_000), 1),
(Cost::from_milliweight(1_001), 2),
(Cost::from_milliweight(1_999), 2),
(Cost::from_milliweight(2_000), 2),
(Cost::CONSENSUS_MAX, 4_000_050),
];
for (cost, expected_weight) in test_vectors {
let converted_cost = U32Weight::from(cost);
let expected_weight = U32Weight(expected_weight);
assert_eq!(converted_cost, expected_weight);
}
}
#[test]
#[cfg(feature = "elements")]
fn test_get_padding() {
let empty = 51_000;
let test_vectors = vec![
(Cost::from_milliweight(0), vec![], None),
(Cost::from_milliweight(empty), vec![], None),
(Cost::from_milliweight(empty + 1), vec![], Some(1)),
(Cost::from_milliweight(empty + 2_000), vec![], Some(1)),
(Cost::from_milliweight(empty + 2_001), vec![], Some(2)),
(Cost::from_milliweight(empty + 3_000), vec![], Some(2)),
(Cost::from_milliweight(empty + 3_001), vec![], Some(3)),
(Cost::from_milliweight(empty + 4_000), vec![], Some(3)),
(Cost::from_milliweight(empty + 4_001), vec![], Some(4)),
(Cost::from_milliweight(empty + 50_000), vec![], Some(49)),
];
for (cost, mut witness, maybe_padding) in test_vectors {
match maybe_padding {
None => {
assert!(cost.is_budget_valid(&witness));
assert!(cost.get_padding(&witness).is_none());
}
Some(expected_annex_len) => {
assert!(!cost.is_budget_valid(&witness));
let annex_bytes = cost.get_padding(&witness).expect("not enough budget");
assert_eq!(expected_annex_len, annex_bytes.len());
witness.extend(std::iter::once(annex_bytes));
assert!(cost.is_budget_valid(&witness));
witness.pop();
assert!(!cost.is_budget_valid(&witness), "Padding must be minimal");
}
}
}
}
}