use std::collections::{HashMap, HashSet};
use std::fmt;
use std::future::Future;
use std::io::{Read, Seek, SeekFrom};
use sha2::{Digest, Sha256};
use crate::primitives::bsv::sighash::{TxInput, TxOutput};
use crate::primitives::{sha256d, to_hex, Reader, Writer};
use crate::script::{LockingScript, Spend, SpendParams, UnlockingScript};
use super::beef_tx::{ATOMIC_BEEF, BEEF_V1, BEEF_V2};
use super::chain_tracker::{ChainTracker, ChainTrackerError, MockChainTracker};
use super::merkle_path::{MerklePath, MerklePathLeaf};
pub type Hash32 = [u8; 32];
pub fn display_hex(hash: &Hash32) -> String {
let mut bytes = *hash;
bytes.reverse();
to_hex(&bytes)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Kind {
BadVersion,
BadVarint,
Truncated,
BadFlag,
TreeHeightOver64,
TreeHeightZero,
OffsetOutsideTree,
NoTxidAtLevelZero,
MissingSibling,
MismatchedNode,
RootNotCarried,
BumpIndexNamesNoBump,
TxidNotInBump,
InputNamesNoElement,
StubNotProven,
TrailingBytes,
SubjectMissing,
UnrelatedTransaction,
NoInputs,
}
impl Kind {
pub const ALL: [Kind; 19] = [
Kind::BadVersion,
Kind::BadVarint,
Kind::Truncated,
Kind::BadFlag,
Kind::TreeHeightOver64,
Kind::TreeHeightZero,
Kind::OffsetOutsideTree,
Kind::NoTxidAtLevelZero,
Kind::MissingSibling,
Kind::MismatchedNode,
Kind::RootNotCarried,
Kind::BumpIndexNamesNoBump,
Kind::TxidNotInBump,
Kind::InputNamesNoElement,
Kind::StubNotProven,
Kind::TrailingBytes,
Kind::SubjectMissing,
Kind::UnrelatedTransaction,
Kind::NoInputs,
];
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reason {
BadVersion {
word: u32,
},
BadVarint,
Truncated {
needed: u64,
},
BadFlag {
byte: u8,
},
TreeHeightOver64 {
height: u8,
},
TreeHeightZero,
OffsetOutsideTree {
level: u8,
offset: u64,
},
NoTxidAtLevelZero,
MissingSibling {
level: u8,
offset: u64,
},
MismatchedNode {
level: u8,
offset: u64,
},
RootNotCarried {
height: u64,
root: Hash32,
},
BumpIndexNamesNoBump {
index: u64,
},
TxidNotInBump {
index: u64,
txid: Hash32,
},
InputNamesNoElement {
txid: Hash32,
},
StubNotProven {
txid: Hash32,
},
TrailingBytes,
SubjectMissing {
subject: Hash32,
},
UnrelatedTransaction {
txid: Hash32,
},
NoInputs,
}
impl Reason {
pub fn kind(&self) -> Kind {
match self {
Reason::BadVersion { .. } => Kind::BadVersion,
Reason::BadVarint => Kind::BadVarint,
Reason::Truncated { .. } => Kind::Truncated,
Reason::BadFlag { .. } => Kind::BadFlag,
Reason::TreeHeightOver64 { .. } => Kind::TreeHeightOver64,
Reason::TreeHeightZero => Kind::TreeHeightZero,
Reason::OffsetOutsideTree { .. } => Kind::OffsetOutsideTree,
Reason::NoTxidAtLevelZero => Kind::NoTxidAtLevelZero,
Reason::MissingSibling { .. } => Kind::MissingSibling,
Reason::MismatchedNode { .. } => Kind::MismatchedNode,
Reason::RootNotCarried { .. } => Kind::RootNotCarried,
Reason::BumpIndexNamesNoBump { .. } => Kind::BumpIndexNamesNoBump,
Reason::TxidNotInBump { .. } => Kind::TxidNotInBump,
Reason::InputNamesNoElement { .. } => Kind::InputNamesNoElement,
Reason::StubNotProven { .. } => Kind::StubNotProven,
Reason::TrailingBytes => Kind::TrailingBytes,
Reason::SubjectMissing { .. } => Kind::SubjectMissing,
Reason::UnrelatedTransaction { .. } => Kind::UnrelatedTransaction,
Reason::NoInputs => Kind::NoInputs,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Refusal {
pub offset: u64,
pub reason: Reason,
}
impl Refusal {
fn new(offset: u64, reason: Reason) -> Self {
Self { offset, reason }
}
pub fn kind(&self) -> Kind {
self.reason.kind()
}
}
impl fmt::Display for Refusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid BEEF at byte {}: {:?}", self.offset, self.reason)
}
}
impl std::error::Error for Refusal {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Node {
Hash(Hash32),
Duplicate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Leaf {
pub at: u64,
pub offset: u64,
pub node: Node,
pub client: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Bump {
pub offset: u64,
pub block_height: u64,
pub levels: Vec<Vec<Leaf>>,
pub root: Hash32,
pub wire_len: u64,
}
impl Bump {
pub fn proven(&self) -> impl Iterator<Item = &Hash32> {
self.levels.first().into_iter().flatten().filter_map(|l| {
if let Node::Hash(h) = &l.node {
Some(h)
} else {
None
}
})
}
pub fn to_merkle_path(&self) -> crate::Result<MerklePath> {
let block_height = u32::try_from(self.block_height).map_err(|_| {
crate::Error::MerklePathError(format!(
"block height {} has no in-memory form",
self.block_height
))
})?;
let path = self
.levels
.iter()
.map(|level| {
level
.iter()
.map(|l| match &l.node {
Node::Hash(h) => MerklePathLeaf {
offset: l.offset,
hash: Some(display_hex(h)),
txid: l.client,
duplicate: false,
},
Node::Duplicate => MerklePathLeaf::new_duplicate(l.offset),
})
.collect()
})
.collect();
Ok(MerklePath { block_height, path })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputRef {
pub at: u64,
pub prev: Hash32,
pub vout: u32,
pub script: std::ops::Range<usize>,
pub sequence: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputRef {
pub satoshis: u64,
pub script: std::ops::Range<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TxBody {
pub raw: Vec<u8>,
pub version: u32,
pub inputs: Vec<InputRef>,
pub outputs: Vec<OutputRef>,
pub lock_time: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Element {
Bump(Bump),
Tx {
offset: u64,
txid: Hash32,
bump_index: Option<u64>,
body: TxBody,
},
TxidOnly {
offset: u64,
txid: Hash32,
},
}
impl Element {
pub fn offset(&self) -> u64 {
match self {
Element::Bump(b) => b.offset,
Element::Tx { offset, .. } | Element::TxidOnly { offset, .. } => *offset,
}
}
pub fn wire_len(&self) -> u64 {
match self {
Element::Bump(b) => b.wire_len,
Element::Tx { body, .. } => body.raw.len() as u64,
Element::TxidOnly { .. } => 32,
}
}
}
fn pair(left: &Hash32, right: &Hash32) -> Hash32 {
let mut data = [0u8; 64];
data[..32].copy_from_slice(left);
data[32..].copy_from_slice(right);
sha256d(&data)
}
struct PathNode {
offset: u64,
hash: Hash32,
at: u64,
}
pub(crate) fn bump_root(offset: u64, levels: &[Vec<Leaf>]) -> Result<Hash32, Refusal> {
let Some(level0) = levels.first() else {
return Err(Refusal::new(offset, Reason::TreeHeightZero));
};
let mut needed: Vec<PathNode> = level0
.iter()
.filter_map(|l| match l.node {
Node::Hash(hash) => Some(PathNode {
offset: l.offset,
hash,
at: l.at,
}),
Node::Duplicate => None,
})
.collect();
let Some(first_at) = needed.first().map(|n| n.at) else {
return Err(Refusal::new(offset, Reason::NoTxidAtLevelZero));
};
if let [only] = levels {
if let [Leaf {
offset: 0,
node: Node::Hash(txid),
..
}] = only.as_slice()
{
return Ok(*txid);
}
}
for (level, leaves) in levels.iter().enumerate() {
let level = level as u8;
let mut given: HashMap<u64, Node> = HashMap::with_capacity(leaves.len());
for l in leaves {
given.entry(l.offset).or_insert(l.node);
}
let mut computed: HashMap<u64, Hash32> = HashMap::with_capacity(needed.len());
for n in &needed {
computed.insert(n.offset, n.hash);
}
let mut parents: HashMap<u64, Hash32> = HashMap::with_capacity(needed.len());
let mut next: Vec<PathNode> = Vec::with_capacity(needed.len());
for n in &needed {
match given.get(&n.offset) {
Some(Node::Hash(g)) if *g == n.hash => {}
Some(_) => {
return Err(Refusal::new(
n.at,
Reason::MismatchedNode {
level,
offset: n.offset,
},
))
}
None => {}
}
let s = n.offset ^ 1;
let sibling: Option<Hash32> = match given.get(&s) {
Some(Node::Hash(h)) => Some(*h),
Some(Node::Duplicate) => None,
None => match computed.get(&s) {
Some(h) => Some(*h),
None => {
return Err(Refusal::new(
n.at,
Reason::MissingSibling { level, offset: s },
))
}
},
};
let parent = match sibling {
None => pair(&n.hash, &n.hash),
Some(h) if s % 2 == 1 => pair(&n.hash, &h),
Some(h) => pair(&h, &n.hash),
};
let up = n.offset / 2;
match parents.get(&up) {
Some(p) if *p == parent => {}
Some(_) => {
return Err(Refusal::new(
n.at,
Reason::MismatchedNode {
level: level + 1,
offset: up,
},
))
}
None => {
parents.insert(up, parent);
next.push(PathNode {
offset: up,
hash: parent,
at: n.at,
});
}
}
}
needed = next;
}
let height = levels.len() as u8;
match needed.as_slice() {
[root] => Ok(root.hash),
[] => Err(Refusal::new(
first_at,
Reason::MissingSibling {
level: height,
offset: 0,
},
)),
[r, ..] => Err(Refusal::new(
r.at,
Reason::OffsetOutsideTree {
level: height,
offset: r.offset,
},
)),
}
}
pub(crate) fn merkle_path_root(path: &MerklePath) -> Result<Hash32, Refusal> {
let levels: Vec<Vec<Leaf>> = path
.path
.iter()
.map(|level| {
level
.iter()
.filter_map(|l| {
let node = if l.duplicate {
Node::Duplicate
} else {
Node::Hash(wire_hash(l.hash.as_deref()?)?)
};
Some(Leaf {
at: 0,
offset: l.offset,
node,
client: l.txid,
})
})
.collect()
})
.collect();
bump_root(0, &levels)
}
pub(crate) fn wire_hash(display: &str) -> Option<Hash32> {
let mut h: Hash32 = crate::primitives::from_hex(display).ok()?.try_into().ok()?;
h.reverse();
Some(h)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Head,
Bumps,
Txs,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Frame {
pos: u64,
k: u64,
phase: Phase,
version: u32,
subject: Option<Hash32>,
bumps_left: u64,
txs_left: u64,
}
impl Frame {
fn start() -> Self {
Self {
pos: 0,
k: 0,
phase: Phase::Head,
version: 0,
subject: None,
bumps_left: 0,
txs_left: 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum St {
Boundary,
Word,
Subject,
Version,
NBumps,
BumpHeight,
TreeHeight,
NLeaves,
LeafOffset,
LeafFlag,
LeafHash,
NTxs,
V2Format,
V2Index,
StubTxid,
TxVersion,
NIn,
InPrev,
InVout,
InScriptLen,
InScript,
InSequence,
NOut,
OutValue,
OutScriptLen,
OutScript,
LockTime,
V1Has,
V1Index,
Done,
}
enum Field {
Fixed(usize),
Varint,
Run,
}
impl St {
fn field(self) -> Field {
match self {
St::Word | St::Version | St::TxVersion | St::InVout | St::InSequence | St::LockTime => {
Field::Fixed(4)
}
St::Subject | St::LeafHash | St::StubTxid | St::InPrev => Field::Fixed(32),
St::TreeHeight | St::LeafFlag | St::V2Format | St::V1Has => Field::Fixed(1),
St::OutValue => Field::Fixed(8),
St::InScript | St::OutScript => Field::Run,
_ => Field::Varint,
}
}
fn in_raw_tx(self) -> bool {
matches!(
self,
St::TxVersion
| St::NIn
| St::InPrev
| St::InVout
| St::InScriptLen
| St::InScript
| St::InSequence
| St::NOut
| St::OutValue
| St::OutScriptLen
| St::OutScript
| St::LockTime
)
}
}
struct BumpBuilder {
offset: u64,
block_height: u64,
tree_height: u8,
levels: Vec<Vec<Leaf>>,
leaves_left: u64,
leaf_at: u64,
leaf_offset: u64,
leaf_client: bool,
}
struct TxBuilder {
offset: u64,
bump_index: Option<u64>,
hasher: Sha256,
raw: Vec<u8>,
version: u32,
inputs: Vec<InputRef>,
outputs: Vec<OutputRef>,
ins_left: u64,
outs_left: u64,
in_at: u64,
in_prev: Hash32,
in_vout: u32,
script_start: usize,
out_satoshis: u64,
txid: Hash32,
lock_time: u32,
}
#[derive(Debug)]
pub enum Step {
Element(Element),
NeedMore,
Done,
}
pub struct BeefDecoder {
frame: Frame,
st: St,
scratch: [u8; 32],
have: usize,
field_at: u64,
run_len: u64,
run_left: u64,
bump: Option<BumpBuilder>,
tx: Option<TxBuilder>,
}
impl Default for BeefDecoder {
fn default() -> Self {
Self::new()
}
}
impl BeefDecoder {
pub fn new() -> Self {
Self::at(Frame::start())
}
fn at(frame: Frame) -> Self {
let st = if frame.phase == Phase::Head {
St::Word
} else {
St::Boundary
};
Self {
field_at: frame.pos,
frame,
st,
scratch: [0u8; 32],
have: 0,
run_len: 0,
run_left: 0,
bump: None,
tx: None,
}
}
pub fn offset(&self) -> u64 {
self.frame.pos
}
pub fn elements_read(&self) -> u64 {
self.frame.k
}
pub fn version(&self) -> Option<u32> {
(self.frame.phase != Phase::Head).then_some(self.frame.version)
}
pub fn subject(&self) -> Option<Hash32> {
self.frame.subject
}
fn header_read(&self) -> bool {
self.frame.phase != Phase::Head
}
pub fn next(&mut self, input: &mut &[u8]) -> Result<Step, Refusal> {
loop {
if self.st == St::Boundary {
self.leave_boundary();
}
if self.st == St::Done {
return if input.is_empty() {
Ok(Step::Done)
} else {
Err(Refusal::new(self.frame.pos, Reason::TrailingBytes))
};
}
let produced = match self.st.field() {
Field::Fixed(n) => {
if !self.fill(input, n) {
return Ok(Step::NeedMore);
}
self.absorb_scratch(n);
self.have = 0;
self.on_fixed(n)?
}
Field::Varint => {
if !self.fill(input, 1) {
return Ok(Step::NeedMore);
}
let n = match self.scratch[0] {
0xFD => 3,
0xFE => 5,
0xFF => 9,
_ => 1,
};
if !self.fill(input, n) {
return Ok(Step::NeedMore);
}
let mut le = [0u8; 8];
let value = if n == 1 {
self.scratch[0] as u64
} else {
le[..n - 1].copy_from_slice(&self.scratch[1..n]);
u64::from_le_bytes(le)
};
self.absorb_scratch(n);
self.have = 0;
self.on_varint(value)?
}
Field::Run => {
let take = self.run_left.min(input.len() as u64) as usize;
let (chunk, rest) = input.split_at(take);
*input = rest;
self.frame.pos += take as u64;
self.run_left -= take as u64;
if let Some(tx) = self.tx.as_mut() {
tx.hasher.update(chunk);
tx.raw.extend_from_slice(chunk);
}
if self.run_left > 0 {
return Ok(Step::NeedMore);
}
self.on_run_done();
None
}
};
if let Some(element) = produced {
self.frame.k += 1;
self.st = St::Boundary;
return Ok(Step::Element(element));
}
}
}
pub fn finish(&mut self) -> Result<(), Refusal> {
if self.st == St::Boundary {
self.leave_boundary();
}
if self.st == St::Done {
return Ok(());
}
let reason = match self.st.field() {
Field::Fixed(n) => Reason::Truncated { needed: n as u64 },
Field::Varint => Reason::BadVarint,
Field::Run => Reason::Truncated {
needed: self.run_len,
},
};
Err(Refusal::new(self.field_at, reason))
}
fn fill(&mut self, input: &mut &[u8], n: usize) -> bool {
if self.have == 0 {
self.field_at = self.frame.pos;
}
let take = n.saturating_sub(self.have).min(input.len());
self.scratch[self.have..self.have + take].copy_from_slice(&input[..take]);
*input = &input[take..];
self.have += take;
self.frame.pos += take as u64;
self.have >= n
}
fn absorb_scratch(&mut self, n: usize) {
if self.st.in_raw_tx() {
if let Some(tx) = self.tx.as_mut() {
tx.hasher.update(&self.scratch[..n]);
tx.raw.extend_from_slice(&self.scratch[..n]);
}
}
}
fn leave_boundary(&mut self) {
self.field_at = self.frame.pos;
self.st = match self.frame.phase {
Phase::Head => St::Word,
Phase::Bumps if self.frame.bumps_left > 0 => {
self.bump = Some(BumpBuilder {
offset: self.frame.pos,
block_height: 0,
tree_height: 0,
levels: Vec::new(),
leaves_left: 0,
leaf_at: 0,
leaf_offset: 0,
leaf_client: false,
});
St::BumpHeight
}
Phase::Bumps => St::NTxs,
Phase::Txs if self.frame.txs_left > 0 => {
if self.frame.version == BEEF_V2 {
St::V2Format
} else {
self.begin_tx(None);
St::TxVersion
}
}
Phase::Txs => St::Done,
};
}
fn begin_tx(&mut self, bump_index: Option<u64>) {
self.tx = Some(TxBuilder {
offset: self.frame.pos,
bump_index,
hasher: Sha256::new(),
raw: Vec::new(),
version: 0,
inputs: Vec::new(),
outputs: Vec::new(),
ins_left: 0,
outs_left: 0,
in_at: 0,
in_prev: [0u8; 32],
in_vout: 0,
script_start: 0,
out_satoshis: 0,
txid: [0u8; 32],
lock_time: 0,
});
}
fn u32_field(&self) -> u32 {
u32::from_le_bytes([
self.scratch[0],
self.scratch[1],
self.scratch[2],
self.scratch[3],
])
}
fn hash_field(&self) -> Hash32 {
self.scratch
}
fn check_version(&self, word: u32) -> Result<(), Refusal> {
if word == BEEF_V1 || word == BEEF_V2 {
Ok(())
} else {
Err(Refusal::new(self.field_at, Reason::BadVersion { word }))
}
}
fn on_fixed(&mut self, _n: usize) -> Result<Option<Element>, Refusal> {
match self.st {
St::Word => {
let word = self.u32_field();
if word == ATOMIC_BEEF {
self.st = St::Subject;
} else {
self.check_version(word)?;
self.frame.version = word;
self.st = St::NBumps;
}
}
St::Subject => {
self.frame.subject = Some(self.hash_field());
self.st = St::Version;
}
St::Version => {
let word = self.u32_field();
self.check_version(word)?;
self.frame.version = word;
self.st = St::NBumps;
}
St::TreeHeight => {
let height = self.scratch[0];
if height > 64 {
return Err(Refusal::new(
self.field_at,
Reason::TreeHeightOver64 { height },
));
}
if height == 0 {
return Err(Refusal::new(self.field_at, Reason::TreeHeightZero));
}
let bump = self.bump.as_mut().expect("a BUMP is in hand");
bump.tree_height = height;
self.st = St::NLeaves;
}
St::LeafFlag => {
let flag = self.scratch[0];
let bump = self.bump.as_mut().expect("a BUMP is in hand");
match flag {
1 => {
let leaf = Leaf {
at: bump.leaf_at,
offset: bump.leaf_offset,
node: Node::Duplicate,
client: false,
};
return self.leaf_done(leaf);
}
0 | 2 => {
bump.leaf_client = flag == 2;
self.st = St::LeafHash;
}
byte => return Err(Refusal::new(self.field_at, Reason::BadFlag { byte })),
}
}
St::LeafHash => {
let hash = self.hash_field();
let bump = self.bump.as_ref().expect("a BUMP is in hand");
let leaf = Leaf {
at: bump.leaf_at,
offset: bump.leaf_offset,
node: Node::Hash(hash),
client: bump.leaf_client,
};
return self.leaf_done(leaf);
}
St::V2Format => match self.scratch[0] {
2 => self.st = St::StubTxid,
1 => self.st = St::V2Index,
0 => {
self.begin_tx(None);
self.st = St::TxVersion;
}
byte => return Err(Refusal::new(self.field_at, Reason::BadFlag { byte })),
},
St::StubTxid => {
self.frame.txs_left -= 1;
return Ok(Some(Element::TxidOnly {
offset: self.field_at,
txid: self.hash_field(),
}));
}
St::TxVersion => {
let version = self.u32_field();
self.tx.as_mut().expect("a transaction is in hand").version = version;
self.st = St::NIn;
}
St::InPrev => {
let prev = self.hash_field();
let at = self.field_at;
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.in_at = at;
tx.in_prev = prev;
self.st = St::InVout;
}
St::InVout => {
let vout = self.u32_field();
self.tx.as_mut().expect("a transaction is in hand").in_vout = vout;
self.st = St::InScriptLen;
}
St::InSequence => {
let sequence = self.u32_field();
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.inputs.push(InputRef {
at: tx.in_at,
prev: tx.in_prev,
vout: tx.in_vout,
script: tx.script_start..tx.raw.len() - 4,
sequence,
});
tx.ins_left -= 1;
self.st = if tx.ins_left > 0 {
St::InPrev
} else {
St::NOut
};
}
St::OutValue => {
let mut le = [0u8; 8];
le.copy_from_slice(&self.scratch[..8]);
self.tx
.as_mut()
.expect("a transaction is in hand")
.out_satoshis = u64::from_le_bytes(le);
self.st = St::OutScriptLen;
}
St::LockTime => {
let lock_time = self.u32_field();
let v1 = self.frame.version == BEEF_V1;
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.lock_time = lock_time;
let once: [u8; 32] = tx.hasher.finalize_reset().into();
tx.txid = Sha256::digest(once).into();
if v1 {
self.st = St::V1Has;
} else {
return self.tx_done().map(Some);
}
}
St::V1Has => match self.scratch[0] {
0 => return self.tx_done().map(Some),
1 => self.st = St::V1Index,
byte => return Err(Refusal::new(self.field_at, Reason::BadFlag { byte })),
},
_ => unreachable!("not a fixed-width field"),
}
Ok(None)
}
fn on_varint(&mut self, value: u64) -> Result<Option<Element>, Refusal> {
match self.st {
St::NBumps => {
self.frame.bumps_left = value;
self.frame.phase = Phase::Bumps;
self.st = St::Boundary;
self.leave_boundary();
}
St::BumpHeight => {
self.bump.as_mut().expect("a BUMP is in hand").block_height = value;
self.st = St::TreeHeight;
}
St::NLeaves => {
let bump = self.bump.as_mut().expect("a BUMP is in hand");
bump.levels.push(Vec::new());
bump.leaves_left = value;
return self.next_leaf();
}
St::LeafOffset => {
let at = self.field_at;
let bump = self.bump.as_mut().expect("a BUMP is in hand");
let level = (bump.levels.len() - 1) as u8;
let shift = bump.tree_height - level;
if shift < 64 && value >> shift != 0 {
return Err(Refusal::new(
at,
Reason::OffsetOutsideTree {
level,
offset: value,
},
));
}
bump.leaf_at = at;
bump.leaf_offset = value;
self.st = St::LeafFlag;
}
St::NTxs => {
self.frame.txs_left = value;
self.frame.phase = Phase::Txs;
self.st = St::Boundary;
self.leave_boundary();
}
St::V2Index => {
self.begin_tx(Some(value));
self.st = St::TxVersion;
}
St::NIn => {
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.ins_left = value;
self.st = if value > 0 { St::InPrev } else { St::NOut };
}
St::InScriptLen | St::OutScriptLen => {
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.script_start = tx.raw.len();
self.run_len = value;
self.run_left = value;
self.field_at = self.frame.pos;
self.st = if self.st == St::InScriptLen {
St::InScript
} else {
St::OutScript
};
if value == 0 {
self.on_run_done();
}
}
St::NOut => {
let tx = self.tx.as_mut().expect("a transaction is in hand");
tx.outs_left = value;
self.st = if value > 0 {
St::OutValue
} else {
St::LockTime
};
}
St::V1Index => {
self.tx
.as_mut()
.expect("a transaction is in hand")
.bump_index = Some(value);
return self.tx_done().map(Some);
}
_ => unreachable!("not a varint"),
}
Ok(None)
}
fn on_run_done(&mut self) {
let tx = self.tx.as_mut().expect("a transaction is in hand");
if self.st == St::InScript {
self.st = St::InSequence;
} else {
tx.outputs.push(OutputRef {
satoshis: tx.out_satoshis,
script: tx.script_start..tx.raw.len(),
});
tx.outs_left -= 1;
self.st = if tx.outs_left > 0 {
St::OutValue
} else {
St::LockTime
};
}
}
fn leaf_done(&mut self, leaf: Leaf) -> Result<Option<Element>, Refusal> {
let bump = self.bump.as_mut().expect("a BUMP is in hand");
bump.levels
.last_mut()
.expect("a level is in hand")
.push(leaf);
bump.leaves_left -= 1;
self.next_leaf()
}
fn next_leaf(&mut self) -> Result<Option<Element>, Refusal> {
let bump = self.bump.as_mut().expect("a BUMP is in hand");
if bump.leaves_left > 0 {
self.st = St::LeafOffset;
return Ok(None);
}
if bump.levels.len() < bump.tree_height as usize {
self.st = St::NLeaves;
return Ok(None);
}
let bump = self.bump.take().expect("a BUMP is in hand");
let root = bump_root(bump.offset, &bump.levels)?;
self.frame.bumps_left -= 1;
Ok(Some(Element::Bump(Bump {
offset: bump.offset,
block_height: bump.block_height,
levels: bump.levels,
root,
wire_len: self.frame.pos - bump.offset,
})))
}
fn tx_done(&mut self) -> Result<Element, Refusal> {
let tx = self.tx.take().expect("a transaction is in hand");
if tx.inputs.is_empty() {
return Err(Refusal::new(tx.offset, Reason::NoInputs));
}
self.frame.txs_left -= 1;
Ok(Element::Tx {
offset: tx.offset,
txid: tx.txid,
bump_index: tx.bump_index,
body: TxBody {
raw: tx.raw,
version: tx.version,
inputs: tx.inputs,
outputs: tx.outputs,
lock_time: tx.lock_time,
},
})
}
}
#[derive(Debug)]
pub enum StreamError {
Io(std::io::Error),
Refused(Refusal),
}
impl fmt::Display for StreamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StreamError::Io(e) => write!(f, "the BEEF source failed: {e}"),
StreamError::Refused(r) => r.fmt(f),
}
}
}
impl std::error::Error for StreamError {}
impl From<std::io::Error> for StreamError {
fn from(e: std::io::Error) -> Self {
StreamError::Io(e)
}
}
impl From<Refusal> for StreamError {
fn from(r: Refusal) -> Self {
StreamError::Refused(r)
}
}
const CHUNK: usize = 16 * 1024;
pub struct BeefStream<R> {
source: R,
decoder: BeefDecoder,
buf: Box<[u8]>,
start: usize,
end: usize,
ended: bool,
}
impl<R: Read> BeefStream<R> {
pub fn new(source: R) -> Self {
Self::with_decoder(source, BeefDecoder::new())
}
fn with_decoder(source: R, decoder: BeefDecoder) -> Self {
Self {
source,
decoder,
buf: vec![0u8; CHUNK].into_boxed_slice(),
start: 0,
end: 0,
ended: false,
}
}
pub fn offset(&self) -> u64 {
self.decoder.offset()
}
pub fn elements_read(&self) -> u64 {
self.decoder.elements_read()
}
pub fn version(&self) -> Option<u32> {
self.decoder.version()
}
pub fn subject(&self) -> Option<Hash32> {
self.decoder.subject()
}
pub fn next_element(&mut self) -> Result<Option<Element>, StreamError> {
if self.ended {
return Ok(None);
}
loop {
let mut input = &self.buf[self.start..self.end];
let before = input.len();
let step = self.decoder.next(&mut input);
self.start += before - input.len();
match step? {
Step::Element(element) => return Ok(Some(element)),
Step::Done | Step::NeedMore => {}
}
let n = loop {
match self.source.read(&mut self.buf) {
Ok(n) => break n,
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
Err(e) => return Err(StreamError::Io(e)),
}
};
self.start = 0;
self.end = n;
if n == 0 {
self.decoder.finish()?;
self.ended = true;
return Ok(None);
}
}
}
}
impl<R: Read> Iterator for BeefStream<R> {
type Item = Result<Element, StreamError>;
fn next(&mut self) -> Option<Self::Item> {
self.next_element().transpose()
}
}
pub trait Headers {
fn carries(&self, height: u64, root: &Hash32) -> bool;
}
impl<H: Headers + ?Sized> Headers for &H {
fn carries(&self, height: u64, root: &Hash32) -> bool {
(**self).carries(height, root)
}
}
impl Headers for HashMap<u64, Hash32> {
fn carries(&self, height: u64, root: &Hash32) -> bool {
self.get(&height) == Some(root)
}
}
impl Headers for MockChainTracker {
fn carries(&self, height: u64, root: &Hash32) -> bool {
u32::try_from(height)
.ok()
.and_then(|h| self.roots.get(&h))
.is_some_and(|carried| carried.eq_ignore_ascii_case(&display_hex(root)))
}
}
pub struct HeadersFn<F>(pub F);
impl<F: Fn(u64, &Hash32) -> bool> Headers for HeadersFn<F> {
fn carries(&self, height: u64, root: &Hash32) -> bool {
(self.0)(height, root)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Retained {
satoshis: u64,
script: Box<[u8]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct TxEntry {
at: u64,
referenced: bool,
raw: bool,
outputs: Option<Box<[Option<Retained>]>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SpendRefusal {
ParentIsTxidOnly,
OutputNotAvailable {
vout: u32,
},
Script(String),
CreatesValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Stop {
Invalid(Refusal),
Spend {
offset: u64,
txid: Hash32,
input: Option<u32>,
why: SpendRefusal,
},
}
impl From<Refusal> for Stop {
fn from(r: Refusal) -> Self {
Stop::Invalid(r)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BeefIndex {
bumps: Vec<(u64, Hash32)>,
proven: HashMap<Hash32, u64>,
proven_again: HashSet<(Hash32, u64)>,
txs: HashMap<Hash32, TxEntry>,
last_raw: Option<(Hash32, u64)>,
steps: u64,
work: u64,
check_spends: bool,
wanted: Option<HashSet<(Hash32, u32)>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Referenced(HashSet<(Hash32, u32)>);
impl Referenced {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl BeefIndex {
pub fn new() -> Self {
Self::with_spend_checks(true)
}
pub fn structure_only() -> Self {
Self::with_spend_checks(false)
}
fn with_spend_checks(check_spends: bool) -> Self {
Self {
bumps: Vec::new(),
proven: HashMap::new(),
proven_again: HashSet::new(),
txs: HashMap::new(),
last_raw: None,
steps: 0,
work: 0,
check_spends,
wanted: None,
}
}
pub fn retaining(referenced: Referenced) -> Self {
let mut index = Self::with_spend_checks(true);
index.wanted = Some(referenced.0);
index
}
pub fn wanted_outpoints(&self) -> Option<u64> {
self.wanted.as_ref().map(|w| w.len() as u64)
}
pub fn steps(&self) -> u64 {
self.steps
}
pub fn entries(&self) -> u64 {
(self.bumps.len() + self.proven.len() + self.proven_again.len() + self.txs.len()) as u64
}
pub fn spec_size(&self) -> u64 {
self.entries() + self.proven.len() as u64
}
pub fn model_work(&self) -> u64 {
self.work
}
pub fn retained_outputs(&self) -> (u64, u64) {
let mut count = 0u64;
let mut bytes = 0u64;
for entry in self.txs.values() {
for out in entry.outputs.iter().flat_map(|o| o.iter()).flatten() {
count += 1;
bytes += out.script.len() as u64;
}
}
(count, bytes)
}
pub fn roots(&self) -> &[(u64, Hash32)] {
&self.bumps
}
fn bump_carries(&self, index: u64, txid: &Hash32) -> bool {
self.proven.get(txid) == Some(&index) || self.proven_again.contains(&(*txid, index))
}
pub fn fold<H: Headers + ?Sized>(
&mut self,
element: &Element,
headers: &H,
) -> Result<(), Stop> {
match element {
Element::Bump(bump) => {
let carried = headers.carries(bump.block_height, &bump.root);
self.fold_bump(bump, carried)
}
other => self.fold_checked(other),
}
}
pub fn fold_bump(&mut self, bump: &Bump, carried: bool) -> Result<(), Stop> {
if !carried {
return Err(Refusal::new(
bump.offset,
Reason::RootNotCarried {
height: bump.block_height,
root: bump.root,
},
)
.into());
}
let position = self.bumps.len() as u64;
self.bumps.push((bump.block_height, bump.root));
let mut hashed = 0u64;
for txid in bump.proven() {
hashed += 1;
match self.proven.get(txid) {
None => {
self.proven.insert(*txid, position);
}
Some(earliest) if *earliest != position => {
self.proven_again.insert((*txid, position));
}
Some(_) => {}
}
}
let leaves: u64 = bump.levels.iter().map(|l| l.len() as u64).sum();
let walk = 2 * leaves + 6 * bump.levels.len() as u64 * hashed;
self.steps += 1;
self.work += bump.wire_len + walk + 1 + 2 * hashed;
Ok(())
}
fn fold_checked(&mut self, element: &Element) -> Result<(), Stop> {
match element {
Element::Bump(_) => unreachable!("a BUMP is folded with its header answer"),
Element::TxidOnly { offset, txid } => {
if !self.proven.contains_key(txid) {
return Err(Refusal::new(*offset, Reason::StubNotProven { txid: *txid }).into());
}
self.txs.entry(*txid).or_insert(TxEntry {
at: *offset,
referenced: false,
raw: false,
outputs: None,
});
self.steps += 1;
self.work += 32 + 3;
Ok(())
}
Element::Tx {
offset,
txid,
bump_index,
body,
} => {
if body.inputs.is_empty() {
return Err(Refusal::new(*offset, Reason::NoInputs).into());
}
match bump_index {
Some(index) => {
if *index >= self.bumps.len() as u64 {
return Err(Refusal::new(
*offset,
Reason::BumpIndexNamesNoBump { index: *index },
)
.into());
}
if !self.bump_carries(*index, txid) {
return Err(Refusal::new(
*offset,
Reason::TxidNotInBump {
index: *index,
txid: *txid,
},
)
.into());
}
}
None => {
for input in &body.inputs {
if !self.txs.contains_key(&input.prev) {
return Err(Refusal::new(
input.at,
Reason::InputNamesNoElement { txid: input.prev },
)
.into());
}
}
}
}
let seen = self.txs.get(txid).is_some_and(|e| e.raw);
if self.check_spends && !seen {
if bump_index.is_none() {
self.check_spends_of(*offset, txid, body)?;
}
for input in &body.inputs {
self.spend_output(&input.prev, input.vout);
}
}
for input in &body.inputs {
if let Some(parent) = self.txs.get_mut(&input.prev) {
parent.referenced = true;
}
}
let keep = |vout: usize| match &self.wanted {
Some(wanted) => u32::try_from(vout).is_ok_and(|v| wanted.contains(&(*txid, v))),
None => true,
};
let outputs = (self.check_spends && !seen)
.then(|| {
body.outputs
.iter()
.enumerate()
.map(|(vout, o)| {
keep(vout).then(|| Retained {
satoshis: o.satoshis,
script: body.raw[o.script.clone()].into(),
})
})
.collect::<Box<[_]>>()
})
.filter(|outputs| outputs.iter().any(Option::is_some));
match self.txs.get_mut(txid) {
Some(entry) => {
if !entry.raw {
entry.raw = true;
entry.outputs = outputs;
}
}
None => {
self.txs.insert(
*txid,
TxEntry {
at: *offset,
referenced: false,
raw: true,
outputs,
},
);
}
}
self.last_raw = Some((*txid, *offset));
self.steps += 1;
self.work += body.raw.len() as u64 + 5 + 2 * body.inputs.len() as u64;
Ok(())
}
}
}
fn spend_output(&mut self, prev: &Hash32, vout: u32) {
if let Some(wanted) = self.wanted.as_mut() {
wanted.remove(&(*prev, vout));
}
let Some(parent) = self.txs.get_mut(prev) else {
return;
};
let Some(outputs) = parent.outputs.as_mut() else {
return;
};
if let Some(slot) = outputs.get_mut(vout as usize) {
*slot = None;
}
if outputs.iter().all(Option::is_none) {
parent.outputs = None;
}
}
fn check_spends_of(&self, offset: u64, txid: &Hash32, body: &TxBody) -> Result<(), Stop> {
let refuse = |at: u64, input: Option<u32>, why: SpendRefusal| Stop::Spend {
offset: at,
txid: *txid,
input,
why,
};
let mut sources: Vec<&Retained> = Vec::with_capacity(body.inputs.len());
let mut spent_here: HashSet<(&Hash32, u32)> = HashSet::new();
let mut input_total: u64 = 0;
for (vin, input) in body.inputs.iter().enumerate() {
let vin = Some(vin as u32);
let vout = input.vout;
let parent = self
.txs
.get(&input.prev)
.expect("the input names an earlier transaction");
if !parent.raw {
return Err(refuse(input.at, vin, SpendRefusal::ParentIsTxidOnly));
}
let slot = parent.outputs.as_ref().and_then(|o| o.get(vout as usize));
let source = match slot {
Some(Some(source)) if spent_here.insert((&input.prev, vout)) => source,
_ => {
return Err(refuse(
input.at,
vin,
SpendRefusal::OutputNotAvailable { vout },
))
}
};
input_total = input_total.checked_add(source.satoshis).ok_or_else(|| {
refuse(
input.at,
vin,
SpendRefusal::Script("input satoshis overflow".to_string()),
)
})?;
sources.push(source);
}
let mut output_total: u64 = 0;
for output in &body.outputs {
output_total = output_total
.checked_add(output.satoshis)
.ok_or_else(|| refuse(offset, None, SpendRefusal::CreatesValue))?;
}
let outputs: Vec<TxOutput> = body
.outputs
.iter()
.map(|o| TxOutput {
satoshis: o.satoshis,
script: body.raw[o.script.clone()].to_vec(),
})
.collect();
let inputs: Vec<TxInput> = body
.inputs
.iter()
.map(|i| TxInput {
txid: i.prev,
output_index: i.vout,
script: body.raw[i.script.clone()].to_vec(),
sequence: i.sequence,
})
.collect();
for (vin, (input, source)) in body.inputs.iter().zip(sources).enumerate() {
let script_error =
|message: String| refuse(input.at, Some(vin as u32), SpendRefusal::Script(message));
let other_inputs: Vec<TxInput> = inputs
.iter()
.enumerate()
.filter(|(i, _)| *i != vin)
.map(|(_, other)| other.clone())
.collect();
let locking_script = LockingScript::from_binary(&source.script)
.map_err(|e| script_error(e.to_string()))?;
let unlocking_script = UnlockingScript::from_binary(&body.raw[input.script.clone()])
.map_err(|e| script_error(e.to_string()))?;
let mut spend = Spend::new(SpendParams {
source_txid: input.prev,
source_output_index: input.vout,
source_satoshis: source.satoshis,
locking_script,
transaction_version: body.version as i32,
other_inputs,
outputs: outputs.clone(),
input_index: vin,
unlocking_script,
input_sequence: input.sequence,
lock_time: body.lock_time,
memory_limit: None,
});
match spend.validate() {
Ok(true) => {}
Ok(false) => return Err(script_error("the script did not succeed".to_string())),
Err(e) => return Err(script_error(e.message)),
}
}
if output_total > input_total {
return Err(refuse(offset, None, SpendRefusal::CreatesValue));
}
Ok(())
}
pub fn finish(&mut self, subject: Option<&Hash32>, subject_at: u64) -> Result<(), Refusal> {
let Some(subject) = subject else {
return Ok(());
};
if self.last_raw.map(|(txid, _)| txid) != Some(*subject) {
return Err(Refusal::new(
subject_at,
Reason::SubjectMissing { subject: *subject },
));
}
let trie_order = |txid: &Hash32| txid.map(u8::reverse_bits);
let unrelated = self
.txs
.iter()
.filter(|(txid, entry)| !entry.referenced && *txid != subject)
.min_by_key(|(txid, _)| trie_order(txid));
if let Some((txid, entry)) = unrelated {
return Err(Refusal::new(
entry.at,
Reason::UnrelatedTransaction { txid: *txid },
));
}
self.work += self.txs.len() as u64;
Ok(())
}
}
impl Default for BeefIndex {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Verdict {
Valid {
subject: Option<Hash32>,
roots: Vec<(u64, Hash32)>,
},
Invalid {
offset: u64,
kind: Kind,
reason: Reason,
},
SpendRefused {
offset: u64,
txid: Hash32,
input: Option<u32>,
why: SpendRefusal,
},
}
impl Verdict {
pub fn is_valid(&self) -> bool {
matches!(self, Verdict::Valid { .. })
}
}
impl From<Refusal> for Verdict {
fn from(r: Refusal) -> Self {
Verdict::Invalid {
offset: r.offset,
kind: r.reason.kind(),
reason: r.reason,
}
}
}
impl From<Stop> for Verdict {
fn from(stop: Stop) -> Self {
match stop {
Stop::Invalid(r) => r.into(),
Stop::Spend {
offset,
txid,
input,
why,
} => Verdict::SpendRefused {
offset,
txid,
input,
why,
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cursor {
frame: Frame,
index: BeefIndex,
subject: Option<Hash32>,
}
const CURSOR_MAGIC: &[u8; 4] = b"BSC1";
impl Cursor {
pub fn elements_read(&self) -> u64 {
self.frame.k
}
pub fn offset(&self) -> u64 {
self.frame.pos
}
pub fn index(&self) -> &BeefIndex {
&self.index
}
pub fn to_binary(&self) -> Vec<u8> {
fn put_hash(w: &mut Writer, h: &Option<Hash32>) {
match h {
Some(h) => {
w.write_u8(1);
w.write_bytes(h);
}
None => {
w.write_u8(0);
}
}
}
let mut w = Writer::new();
w.write_bytes(CURSOR_MAGIC);
w.write_u8(self.index.check_spends as u8);
put_hash(&mut w, &self.subject);
w.write_u64_le(self.frame.pos);
w.write_u64_le(self.frame.k);
w.write_u8(match self.frame.phase {
Phase::Head => 0,
Phase::Bumps => 1,
Phase::Txs => 2,
});
w.write_u32_le(self.frame.version);
put_hash(&mut w, &self.frame.subject);
w.write_u64_le(self.frame.bumps_left);
w.write_u64_le(self.frame.txs_left);
let index = &self.index;
w.write_u64_le(index.steps);
w.write_u64_le(index.work);
match &index.last_raw {
Some((txid, at)) => {
w.write_u8(1);
w.write_bytes(txid);
w.write_u64_le(*at);
}
None => {
w.write_u8(0);
}
}
w.write_var_int(index.bumps.len() as u64);
for (height, root) in &index.bumps {
w.write_u64_le(*height);
w.write_bytes(root);
}
let mut proven: Vec<(&Hash32, &u64)> = index.proven.iter().collect();
proven.sort_unstable();
w.write_var_int(proven.len() as u64);
for (txid, position) in proven {
w.write_bytes(txid);
w.write_u64_le(*position);
}
let mut again: Vec<&(Hash32, u64)> = index.proven_again.iter().collect();
again.sort_unstable();
w.write_var_int(again.len() as u64);
for (txid, position) in again {
w.write_bytes(txid);
w.write_u64_le(*position);
}
let mut txs: Vec<(&Hash32, &TxEntry)> = index.txs.iter().collect();
txs.sort_unstable_by_key(|(txid, _)| *txid);
w.write_var_int(txs.len() as u64);
for (txid, entry) in txs {
w.write_bytes(txid);
w.write_u64_le(entry.at);
w.write_u8(entry.referenced as u8 | (entry.raw as u8) << 1);
match &entry.outputs {
None => {
w.write_var_int(0);
}
Some(outputs) => {
w.write_var_int(outputs.len() as u64);
for slot in outputs.iter() {
match slot {
None => {
w.write_u8(0);
}
Some(out) => {
w.write_u8(1);
w.write_u64_le(out.satoshis);
w.write_var_bytes(&out.script);
}
}
}
}
}
}
match &index.wanted {
None => {
w.write_u8(0);
}
Some(wanted) => {
w.write_u8(1);
let mut wanted: Vec<&(Hash32, u32)> = wanted.iter().collect();
wanted.sort_unstable();
w.write_var_int(wanted.len() as u64);
for (txid, vout) in wanted {
w.write_bytes(txid);
w.write_u32_le(*vout);
}
}
}
w.into_bytes()
}
pub fn from_binary(bin: &[u8]) -> crate::Result<Self> {
fn bad(what: &str) -> crate::Error {
crate::Error::BeefError(format!("not a cursor: {what}"))
}
fn hash(r: &mut Reader) -> crate::Result<Hash32> {
let mut h = [0u8; 32];
h.copy_from_slice(r.read_bytes(32)?);
Ok(h)
}
fn opt_hash(r: &mut Reader) -> crate::Result<Option<Hash32>> {
match r.read_u8()? {
0 => Ok(None),
1 => Ok(Some(hash(r)?)),
_ => Err(bad("a presence byte")),
}
}
let mut r = Reader::new(bin);
if r.read_bytes(4)? != CURSOR_MAGIC {
return Err(bad("the leading word"));
}
let check_spends = match r.read_u8()? {
0 => false,
1 => true,
_ => return Err(bad("the mode byte")),
};
let subject = opt_hash(&mut r)?;
let pos = r.read_u64_le()?;
let k = r.read_u64_le()?;
let phase = match r.read_u8()? {
0 => Phase::Head,
1 => Phase::Bumps,
2 => Phase::Txs,
_ => return Err(bad("the phase byte")),
};
let version = r.read_u32_le()?;
let frame_subject = opt_hash(&mut r)?;
let bumps_left = r.read_u64_le()?;
let txs_left = r.read_u64_le()?;
let frame = Frame {
pos,
k,
phase,
version,
subject: frame_subject,
bumps_left,
txs_left,
};
let mut index = BeefIndex::with_spend_checks(check_spends);
index.steps = r.read_u64_le()?;
index.work = r.read_u64_le()?;
index.last_raw = match r.read_u8()? {
0 => None,
1 => {
let txid = hash(&mut r)?;
Some((txid, r.read_u64_le()?))
}
_ => return Err(bad("a presence byte")),
};
for _ in 0..r.read_var_int()? {
let height = r.read_u64_le()?;
index.bumps.push((height, hash(&mut r)?));
}
for _ in 0..r.read_var_int()? {
let txid = hash(&mut r)?;
index.proven.insert(txid, r.read_u64_le()?);
}
for _ in 0..r.read_var_int()? {
let txid = hash(&mut r)?;
index.proven_again.insert((txid, r.read_u64_le()?));
}
for _ in 0..r.read_var_int()? {
let txid = hash(&mut r)?;
let at = r.read_u64_le()?;
let flags = r.read_u8()?;
if flags > 3 {
return Err(bad("a transaction's flags"));
}
let n = r.read_var_int()?;
let outputs = if n == 0 {
None
} else {
let mut outputs = Vec::new();
for _ in 0..n {
outputs.push(match r.read_u8()? {
0 => None,
1 => {
let satoshis = r.read_u64_le()?;
Some(Retained {
satoshis,
script: r.read_var_bytes()?.into(),
})
}
_ => return Err(bad("a presence byte")),
});
}
Some(outputs.into_boxed_slice())
};
index.txs.insert(
txid,
TxEntry {
at,
referenced: flags & 1 == 1,
raw: flags & 2 == 2,
outputs,
},
);
}
index.wanted = match r.read_u8()? {
0 => None,
1 => {
let mut wanted = HashSet::new();
for _ in 0..r.read_var_int()? {
let txid = hash(&mut r)?;
wanted.insert((txid, r.read_u32_le()?));
}
Some(wanted)
}
_ => return Err(bad("a presence byte")),
};
if !r.is_empty() {
return Err(bad("bytes after its end"));
}
Ok(Self {
frame,
index,
subject,
})
}
}
struct Judge {
index: BeefIndex,
subject: Option<Hash32>,
subject_checked: bool,
}
impl Judge {
fn new(subject: Option<Hash32>, index: BeefIndex) -> Self {
Self {
index,
subject,
subject_checked: false,
}
}
fn cursor(&self, decoder: &BeefDecoder) -> Cursor {
Cursor {
frame: decoder.frame.clone(),
index: self.index.clone(),
subject: self.subject,
}
}
fn check_named_subject(&mut self, decoder: &BeefDecoder) -> Result<(), Refusal> {
if self.subject_checked || !decoder.header_read() {
return Ok(());
}
self.subject_checked = true;
match (self.subject, decoder.subject()) {
(Some(named), Some(prefixed)) if named != prefixed => {
Err(Refusal::new(4, Reason::SubjectMissing { subject: named }))
}
_ => Ok(()),
}
}
fn conclude(&mut self, decoder: &BeefDecoder) -> Verdict {
if let Err(r) = self.check_named_subject(decoder) {
return r.into();
}
let subject = self.subject.or(decoder.subject());
let subject_at = if decoder.subject().is_some() {
4
} else {
self.index.last_raw.map_or(decoder.offset(), |(_, at)| at)
};
match self.index.finish(subject.as_ref(), subject_at) {
Ok(()) => Verdict::Valid {
subject,
roots: self.index.bumps.clone(),
},
Err(r) => r.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Progress {
Stepped,
Verdict(Verdict),
}
pub struct StreamVerifier<R, H> {
source: BeefStream<R>,
judge: Judge,
headers: H,
verdict: Option<Verdict>,
}
impl<R: Read, H: Headers> StreamVerifier<R, H> {
pub fn new(source: R, headers: H, subject: Option<Hash32>) -> Self {
Self::from_parts(
BeefDecoder::new(),
Judge::new(subject, BeefIndex::new()),
source,
headers,
)
}
pub fn structure_only(source: R, headers: H, subject: Option<Hash32>) -> Self {
Self::from_parts(
BeefDecoder::new(),
Judge::new(subject, BeefIndex::structure_only()),
source,
headers,
)
}
pub fn retaining(
source: R,
headers: H,
subject: Option<Hash32>,
referenced: Referenced,
) -> Self {
Self::from_parts(
BeefDecoder::new(),
Judge::new(subject, BeefIndex::retaining(referenced)),
source,
headers,
)
}
pub fn resume(cursor: Cursor, source: R, headers: H) -> Self {
Self::from_parts(
BeefDecoder::at(cursor.frame),
Judge::new(cursor.subject, cursor.index),
source,
headers,
)
}
fn from_parts(decoder: BeefDecoder, judge: Judge, source: R, headers: H) -> Self {
Self {
source: BeefStream::with_decoder(source, decoder),
judge,
headers,
verdict: None,
}
}
pub fn cursor(&self) -> Cursor {
self.judge.cursor(&self.source.decoder)
}
pub fn index(&self) -> &BeefIndex {
&self.judge.index
}
pub fn step(&mut self) -> std::io::Result<Progress> {
if let Some(verdict) = &self.verdict {
return Ok(Progress::Verdict(verdict.clone()));
}
let verdict = match self.source.next_element() {
Err(StreamError::Io(e)) => return Err(e),
Err(StreamError::Refused(r)) => Some(r.into()),
Ok(element) => match self.judge.check_named_subject(&self.source.decoder) {
Err(r) => Some(r.into()),
Ok(()) => match element {
None => Some(self.judge.conclude(&self.source.decoder)),
Some(element) => self
.judge
.index
.fold(&element, &self.headers)
.err()
.map(Verdict::from),
},
},
};
match verdict {
None => Ok(Progress::Stepped),
Some(verdict) => {
self.verdict = Some(verdict.clone());
Ok(Progress::Verdict(verdict))
}
}
}
pub fn run(mut self) -> std::io::Result<Verdict> {
loop {
if let Progress::Verdict(verdict) = self.step()? {
return Ok(verdict);
}
}
}
}
pub fn verify_stream<R: Read, H: Headers>(
source: R,
headers: H,
subject: Option<Hash32>,
) -> std::io::Result<Verdict> {
StreamVerifier::new(source, headers, subject).run()
}
pub fn verify_stream_structure<R: Read, H: Headers>(
source: R,
headers: H,
subject: Option<Hash32>,
) -> std::io::Result<Verdict> {
StreamVerifier::structure_only(source, headers, subject).run()
}
pub fn referenced_outpoints<R: Read>(source: R) -> Result<Referenced, StreamError> {
let mut referenced = HashSet::new();
for element in BeefStream::new(source) {
if let Element::Tx {
bump_index: None,
body,
..
} = element?
{
referenced.extend(body.inputs.iter().map(|i| (i.prev, i.vout)));
}
}
Ok(Referenced(referenced))
}
pub fn verify_stream_two_pass<R: Read + Seek, H: Headers>(
mut source: R,
headers: H,
subject: Option<Hash32>,
) -> std::io::Result<Verdict> {
let start = source.stream_position()?;
let referenced = match referenced_outpoints(&mut source) {
Ok(referenced) => referenced,
Err(StreamError::Io(e)) => return Err(e),
Err(StreamError::Refused(r)) => return Ok(r.into()),
};
source.seek(SeekFrom::Start(start))?;
StreamVerifier::retaining(source, headers, subject, referenced).run()
}
pub fn resume<R: Read, H: Headers>(
cursor: Cursor,
source: R,
headers: H,
) -> std::io::Result<Verdict> {
StreamVerifier::resume(cursor, source, headers).run()
}
pub trait AsyncByteSource {
fn next_chunk(&mut self) -> impl Future<Output = std::io::Result<Option<Vec<u8>>>>;
}
#[derive(Debug)]
pub enum AsyncVerifyError {
Source(std::io::Error),
Headers(ChainTrackerError),
}
impl fmt::Display for AsyncVerifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsyncVerifyError::Source(e) => write!(f, "the BEEF source failed: {e}"),
AsyncVerifyError::Headers(e) => write!(f, "the header lookup failed: {e}"),
}
}
}
impl std::error::Error for AsyncVerifyError {}
pub struct AsyncStreamVerifier {
decoder: BeefDecoder,
judge: Judge,
}
impl AsyncStreamVerifier {
pub fn new(subject: Option<Hash32>) -> Self {
Self {
decoder: BeefDecoder::new(),
judge: Judge::new(subject, BeefIndex::new()),
}
}
pub fn structure_only(subject: Option<Hash32>) -> Self {
Self {
decoder: BeefDecoder::new(),
judge: Judge::new(subject, BeefIndex::structure_only()),
}
}
pub fn resume(cursor: Cursor) -> Self {
Self {
decoder: BeefDecoder::at(cursor.frame),
judge: Judge::new(cursor.subject, cursor.index),
}
}
pub fn cursor(&self) -> Cursor {
self.judge.cursor(&self.decoder)
}
pub async fn run<S: AsyncByteSource>(
&mut self,
source: &mut S,
tracker: &dyn ChainTracker,
) -> Result<Verdict, AsyncVerifyError> {
loop {
let chunk = source
.next_chunk()
.await
.map_err(AsyncVerifyError::Source)?;
let Some(chunk) = chunk else {
if let Err(r) = self.decoder.finish() {
return Ok(r.into());
}
return Ok(self.judge.conclude(&self.decoder));
};
let mut input = chunk.as_slice();
loop {
let step = match self.decoder.next(&mut input) {
Ok(step) => step,
Err(r) => return Ok(r.into()),
};
if let Err(r) = self.judge.check_named_subject(&self.decoder) {
return Ok(r.into());
}
let folded = match step {
Step::NeedMore | Step::Done => break,
Step::Element(Element::Bump(bump)) => {
let carried = match u32::try_from(bump.block_height) {
Ok(height) => tracker
.is_valid_root_for_height(&display_hex(&bump.root), height)
.await
.map_err(AsyncVerifyError::Headers)?,
Err(_) => false,
};
self.judge.index.fold_bump(&bump, carried)
}
Step::Element(other) => self.judge.index.fold_checked(&other),
};
if let Err(stop) = folded {
return Ok(stop.into());
}
}
}
}
}
pub async fn verify_stream_async<S: AsyncByteSource>(
source: &mut S,
tracker: &dyn ChainTracker,
subject: Option<Hash32>,
) -> Result<Verdict, AsyncVerifyError> {
AsyncStreamVerifier::new(subject).run(source, tracker).await
}