Skip to main content

blvm_primitives/
types.rs

1//! Essential Bitcoin types for consensus validation
2
3use serde::{Deserialize, Serialize};
4
5#[cfg(feature = "production")]
6use rustc_hash::FxHashMap;
7#[cfg(feature = "production")]
8use smallvec::SmallVec;
9#[cfg(not(feature = "production"))]
10use std::collections::HashMap;
11
12// Re-export smallvec for macro use in other crates
13#[cfg(feature = "production")]
14pub use smallvec;
15
16/// Helper macro to create Transaction inputs/outputs that works with both Vec and SmallVec
17#[cfg(feature = "production")]
18#[macro_export]
19macro_rules! tx_inputs {
20    ($($item:expr),* $(,)?) => {
21        {
22            $crate::smallvec::SmallVec::from_vec(vec![$($item),*])
23        }
24    };
25}
26
27#[cfg(not(feature = "production"))]
28#[macro_export]
29macro_rules! tx_inputs {
30    ($($item:expr),* $(,)?) => {
31        vec![$($item),*]
32    };
33}
34
35#[cfg(feature = "production")]
36#[macro_export]
37macro_rules! tx_outputs {
38    ($($item:expr),* $(,)?) => {
39        {
40            $crate::smallvec::SmallVec::from_vec(vec![$($item),*])
41        }
42    };
43}
44
45#[cfg(not(feature = "production"))]
46#[macro_export]
47macro_rules! tx_outputs {
48    ($($item:expr),* $(,)?) => {
49        vec![$($item),*]
50    };
51}
52
53/// Hash type: 256-bit hash
54pub type Hash = [u8; 32];
55
56/// Byte string type
57pub type ByteString = Vec<u8>;
58
59/// Witness data: stack of witness elements (SegWit/Taproot)
60///
61/// For SegWit: Vector of byte strings representing witness stack elements.
62/// For Taproot: Vector containing control block and script path data.
63pub type Witness = Vec<ByteString>;
64
65/// Maximum script_pubkey stored inline (no `Arc` heap allocation). On-disk format is unaffected
66/// (custom Serde serializes only the live bytes). 25 bytes covers P2PKH (25 bytes exactly),
67/// P2SH (23), and P2WPKH (22) — ~97% of all UTXOs on mainnet at heights ≤ 600k.
68/// P2WSH (34) and P2TR (34) fall through to the `Arc<[u8]>` path transparently; they are
69/// negligible pre-SegWit and still a small minority post-SegWit at these heights.
70/// Saves ~40 bytes per UTXO vs the old 64-byte cap (~2.2 GB at 55M entries).
71/// Reducing below 25 would push P2PKH to Arc, adding back ~1.6 GB for 70% of UTXOs.
72const SHARED_BYTE_INLINE_CAP: usize = 25;
73
74/// Live count of SharedByteString instances using heap Arc<[u8]> (script > 25 bytes).
75/// Each live Shared variant holds one Arc heap allocation of (16 + script_len) bytes.
76/// If this grows unboundedly it means Arc<[u8]> script refs are being retained somewhere.
77pub static SBS_SHARED_LIVE: std::sync::atomic::AtomicI64 = std::sync::atomic::AtomicI64::new(0);
78/// Total Shared variants ever created (never decremented).
79pub static SBS_SHARED_TOTAL: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
80
81enum SharedRepr {
82    Inline {
83        len: u8,
84        data: [u8; SHARED_BYTE_INLINE_CAP],
85    },
86    Shared(std::sync::Arc<[u8]>),
87}
88
89/// Shareable script_pubkey for UTXO: small scripts use inline storage; longer use `Arc<[u8]>`.
90/// Clone is cheap (inline copies up to `SHARED_BYTE_INLINE_CAP` bytes, shared is `Arc::clone`). Serde matches `ByteString`.
91#[derive(Clone)]
92pub struct SharedByteString(SharedRepr);
93
94impl std::fmt::Debug for SharedByteString {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.debug_tuple("SharedByteString")
97            .field(&self.as_slice())
98            .finish()
99    }
100}
101
102impl PartialEq for SharedByteString {
103    #[inline]
104    fn eq(&self, other: &Self) -> bool {
105        self.as_slice() == other.as_slice()
106    }
107}
108
109impl Eq for SharedByteString {}
110
111impl std::hash::Hash for SharedByteString {
112    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
113        self.as_slice().hash(state);
114    }
115}
116
117impl SharedByteString {
118    #[inline]
119    fn as_slice(&self) -> &[u8] {
120        match &self.0 {
121            SharedRepr::Inline { len, data } => &data[..*len as usize],
122            SharedRepr::Shared(a) => a,
123        }
124    }
125
126    #[inline]
127    fn from_bytes(v: &[u8]) -> Self {
128        if v.len() <= SHARED_BYTE_INLINE_CAP {
129            let mut data = [0u8; SHARED_BYTE_INLINE_CAP];
130            data[..v.len()].copy_from_slice(v);
131            Self(SharedRepr::Inline {
132                len: v.len() as u8,
133                data,
134            })
135        } else {
136            SBS_SHARED_LIVE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
137            SBS_SHARED_TOTAL.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
138            Self(SharedRepr::Shared(std::sync::Arc::from(v)))
139        }
140    }
141}
142
143// Manual Clone (not `#[derive]`): SharedRepr::Shared must bump SBS_SHARED_LIVE on clone.
144// Arc<[u8]> refcount increments but no new heap allocation. SBS_SHARED_TOTAL counts
145// distinct heap allocations (from_bytes only). SBS_SHARED_LIVE counts live Shared
146// *instances* across clones.
147impl Clone for SharedRepr {
148    fn clone(&self) -> Self {
149        match self {
150            SharedRepr::Inline { len, data } => SharedRepr::Inline {
151                len: *len,
152                data: *data,
153            },
154            SharedRepr::Shared(a) => {
155                SBS_SHARED_LIVE.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
156                SharedRepr::Shared(std::sync::Arc::clone(a))
157            }
158        }
159    }
160}
161
162impl Drop for SharedRepr {
163    fn drop(&mut self) {
164        if matches!(self, SharedRepr::Shared(_)) {
165            SBS_SHARED_LIVE.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
166        }
167    }
168}
169
170impl std::ops::Deref for SharedByteString {
171    type Target = [u8];
172    #[inline]
173    fn deref(&self) -> &[u8] {
174        self.as_slice()
175    }
176}
177
178impl Serialize for SharedByteString {
179    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
180        self.as_slice().serialize(s)
181    }
182}
183
184impl<'de> Deserialize<'de> for SharedByteString {
185    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
186        let v: Vec<u8> = Deserialize::deserialize(d)?;
187        Ok(Self::from_bytes(&v))
188    }
189}
190
191impl From<ByteString> for SharedByteString {
192    #[inline]
193    fn from(v: ByteString) -> Self {
194        Self::from_bytes(v.as_slice())
195    }
196}
197
198impl From<&[u8]> for SharedByteString {
199    #[inline]
200    fn from(v: &[u8]) -> Self {
201        Self::from_bytes(v)
202    }
203}
204
205impl Default for SharedByteString {
206    #[inline]
207    fn default() -> Self {
208        Self(SharedRepr::Inline {
209            len: 0,
210            data: [0u8; SHARED_BYTE_INLINE_CAP],
211        })
212    }
213}
214
215impl AsRef<[u8]> for SharedByteString {
216    #[inline]
217    fn as_ref(&self) -> &[u8] {
218        self.as_slice()
219    }
220}
221
222impl SharedByteString {
223    /// Owning clone of bytes as `Arc<[u8]>`. May allocate once when storing an inline script.
224    #[inline]
225    pub fn as_arc(&self) -> std::sync::Arc<[u8]> {
226        match &self.0 {
227            SharedRepr::Shared(a) => std::sync::Arc::clone(a),
228            SharedRepr::Inline { len, data } => {
229                std::sync::Arc::from(data[..*len as usize].to_vec().into_boxed_slice())
230            }
231        }
232    }
233}
234
235/// Natural number type
236pub type Natural = u64;
237
238/// Integer type
239pub type Integer = i64;
240
241/// Network type for consensus validation
242///
243/// Used to determine activation heights for various BIPs and consensus rules.
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
245pub enum Network {
246    /// Bitcoin mainnet
247    Mainnet,
248    /// Bitcoin testnet
249    Testnet,
250    /// Bitcoin regtest (local testing)
251    Regtest,
252    /// Bitcoin signet (BIP325 test network with block-solution challenge)
253    Signet,
254}
255
256/// Time context for consensus validation
257///
258/// Provides network time and median time-past for timestamp validation.
259/// Required for proper block header timestamp validation (BIP113).
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261pub struct TimeContext {
262    /// Current network time (Unix timestamp)
263    /// Used to reject blocks with timestamps too far in the future
264    pub network_time: u64,
265    /// Median time-past of previous 11 blocks (BIP113)
266    /// Used to reject blocks with timestamps before median time-past
267    pub median_time_past: u64,
268}
269
270/// BIP54 timewarp: timestamps of boundary blocks for period-boundary checks.
271///
272/// When BIP54 is active, at height N with N % 2016 == 2015 we require
273/// header.timestamp >= timestamp_n_minus_2015; at N % 2016 == 0 we require
274/// header.timestamp >= timestamp_n_minus_1 - 7200. Callers (e.g. node) pass
275/// these when connecting a block at a period boundary.
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277pub struct Bip54BoundaryTimestamps {
278    /// Timestamp of block at height N-1 (for first block of period check)
279    pub timestamp_n_minus_1: u64,
280    /// Timestamp of block at height N-2015 (for last block of period check)
281    pub timestamp_n_minus_2015: u64,
282}
283
284/// Stable identifier for each consensus-affecting fork (BIP or soft-fork bundle).
285///
286/// Used to query "is fork X active at height H?" via a unified activation table
287/// without hardcoding names in every validation function.
288#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
289pub enum ForkId {
290    /// BIP30: duplicate coinbase prevention (deactivation fork: active when height <= deactivation_height).
291    Bip30,
292    /// BIP16: P2SH.
293    Bip16,
294    /// BIP34: block height in coinbase.
295    Bip34,
296    /// BIP66: strict DER signatures.
297    Bip66,
298    /// BIP65: OP_CHECKLOCKTIMEVERIFY.
299    Bip65,
300    /// BIP112/BIP113: OP_CHECKSEQUENCEVERIFY (CSV). Activates at 419328 mainnet — **before** BIP147.
301    Bip112,
302    /// BIP147: SCRIPT_VERIFY_NULLDUMMY (SegWit deployment; mainnet 481824).
303    Bip147,
304    /// BIP141: SegWit.
305    SegWit,
306    /// BIP341: Taproot.
307    Taproot,
308    /// BIP119: OP_CTV (feature-gated).
309    Ctv,
310    /// BIP348: OP_CSFS (feature-gated).
311    Csfs,
312    /// BIP54: consensus cleanup (version-bits or override).
313    Bip54,
314}
315
316impl Network {
317    /// Get network from environment variable or default to mainnet
318    ///
319    /// Checks `BITCOIN_NETWORK` environment variable:
320    /// - "testnet" -> Network::Testnet
321    /// - "regtest" -> Network::Regtest
322    /// - "signet" -> Network::Signet
323    /// - otherwise -> Network::Mainnet
324    pub fn from_env() -> Self {
325        match std::env::var("BITCOIN_NETWORK").as_deref() {
326            Ok("testnet") => Network::Testnet,
327            Ok("regtest") => Network::Regtest,
328            Ok("signet") => Network::Signet,
329            _ => Network::Mainnet,
330        }
331    }
332
333    /// Get human-readable part (HRP) for Bech32 encoding
334    ///
335    /// Used by blvm-protocol for address encoding (BIP173/350/351)
336    pub fn hrp(&self) -> &'static str {
337        match self {
338            Network::Mainnet => "bc",
339            Network::Testnet => "tb",
340            Network::Regtest => "bcrt",
341            Network::Signet => "tb",
342        }
343    }
344}
345
346/// Block height: newtype wrapper for type safety
347///
348/// Prevents mixing up block heights with other u64 values (e.g., timestamps, counts).
349/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as u64.
350#[repr(transparent)]
351#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
352pub struct BlockHeight(pub u64);
353
354impl BlockHeight {
355    /// Create a new BlockHeight from a u64
356    #[inline(always)]
357    pub fn new(height: u64) -> Self {
358        BlockHeight(height)
359    }
360
361    /// Get the inner u64 value
362    #[inline(always)]
363    pub fn as_u64(self) -> u64 {
364        self.0
365    }
366}
367
368impl From<u64> for BlockHeight {
369    #[inline(always)]
370    fn from(height: u64) -> Self {
371        BlockHeight(height)
372    }
373}
374
375impl From<BlockHeight> for u64 {
376    #[inline(always)]
377    fn from(height: BlockHeight) -> Self {
378        height.0
379    }
380}
381
382impl std::ops::Deref for BlockHeight {
383    type Target = u64;
384
385    #[inline(always)]
386    fn deref(&self) -> &Self::Target {
387        &self.0
388    }
389}
390
391/// Block hash: newtype wrapper for type safety
392///
393/// Prevents mixing up block hashes with other Hash values (e.g., transaction hashes, merkle roots).
394/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as Hash.
395#[repr(transparent)]
396#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
397pub struct BlockHash(pub Hash);
398
399impl BlockHash {
400    /// Create a new BlockHash from a Hash
401    #[inline(always)]
402    pub fn new(hash: Hash) -> Self {
403        BlockHash(hash)
404    }
405
406    /// Get the inner Hash value
407    #[inline(always)]
408    pub fn as_hash(self) -> Hash {
409        self.0
410    }
411
412    /// Get a reference to the inner Hash
413    #[inline(always)]
414    pub fn as_hash_ref(&self) -> &Hash {
415        &self.0
416    }
417}
418
419impl From<Hash> for BlockHash {
420    #[inline(always)]
421    fn from(hash: Hash) -> Self {
422        BlockHash(hash)
423    }
424}
425
426impl From<BlockHash> for Hash {
427    #[inline(always)]
428    fn from(hash: BlockHash) -> Self {
429        hash.0
430    }
431}
432
433impl std::ops::Deref for BlockHash {
434    type Target = Hash;
435
436    #[inline(always)]
437    fn deref(&self) -> &Self::Target {
438        &self.0
439    }
440}
441
442/// OutPoint: 𝒪 = ℍ × ℕ
443///
444/// Uses u32 for index (Bitcoin wire format); saves 24 bytes vs repr(align(64)) padding.
445#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
446pub struct OutPoint {
447    pub hash: Hash,
448    pub index: u32,
449}
450
451/// Transaction Input: ℐ = 𝒪 × 𝕊 × ℕ
452///
453/// Performance optimization: Hot fields (prevout, sequence) grouped together
454/// for better cache locality. script_sig is accessed less frequently.
455#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
456pub struct TransactionInput {
457    pub prevout: OutPoint,      // Hot: 40 bytes (frequently accessed)
458    pub sequence: Natural,      // Hot: 8 bytes (frequently accessed)
459    pub script_sig: ByteString, // Cold: Vec (pointer, less frequently accessed)
460}
461
462/// Transaction Output: 𝒯 = ℤ × 𝕊
463#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
464pub struct TransactionOutput {
465    pub value: Integer,
466    pub script_pubkey: ByteString,
467}
468
469/// Transaction: 𝒯𝒳 = ℕ × ℐ* × 𝒯* × ℕ
470///
471/// Performance optimization: Uses SmallVec for inputs/outputs to eliminate
472/// heap allocations for the common case of 1-2 inputs/outputs (80%+ of transactions).
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct Transaction {
475    pub version: Natural,
476    #[cfg(feature = "production")]
477    pub inputs: SmallVec<[TransactionInput; 2]>,
478    #[cfg(not(feature = "production"))]
479    pub inputs: Vec<TransactionInput>,
480    #[cfg(feature = "production")]
481    pub outputs: SmallVec<[TransactionOutput; 2]>,
482    #[cfg(not(feature = "production"))]
483    pub outputs: Vec<TransactionOutput>,
484    pub lock_time: Natural,
485}
486
487/// Block Header: ℋ = ℤ × ℍ × ℍ × ℕ × ℕ × ℕ
488///
489/// `Default` is only used as a placeholder when extracting the header for BIP113
490/// tracking; the default value is never used for consensus.
491#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
492pub struct BlockHeader {
493    pub version: Integer,
494    pub prev_block_hash: Hash,
495    pub merkle_root: Hash,
496    pub timestamp: Natural,
497    pub bits: Natural,
498    pub nonce: Natural,
499}
500
501impl std::convert::AsRef<BlockHeader> for BlockHeader {
502    #[inline]
503    fn as_ref(&self) -> &BlockHeader {
504        self
505    }
506}
507
508/// Total Arc<Block> heap allocations created on IBD production paths.
509/// Incremented at: download `received_put` (live + CHUNK_OBSOLETE drain),
510/// `local_block` gap inject. Test dummies do not increment (not in MEM_REPORT).
511pub static ARC_BLOCK_CREATED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
512
513/// Total Arc<BlockHeader> heap allocations created (each Arc::new(header) call).
514/// Compare to jemalloc bin112 curregs to see how many have been freed.
515pub static ARC_BLOCKHEADER_CREATED: std::sync::atomic::AtomicU64 =
516    std::sync::atomic::AtomicU64::new(0);
517
518/// Block: ℬ = ℋ × 𝒯𝒳*
519///
520/// Performance optimization: Uses Box<[Transaction]> instead of Vec<Transaction>
521/// since transactions are never modified after block creation. This saves 8 bytes
522/// (no capacity field) and provides better cache usage.
523#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
524pub struct Block {
525    pub header: BlockHeader,
526    pub transactions: Box<[Transaction]>,
527}
528
529/// UTXO: 𝒰 = ℤ × 𝕊 × ℕ
530#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
531pub struct UTXO {
532    pub value: Integer,
533    pub script_pubkey: SharedByteString,
534    pub height: Natural,
535    /// Whether this UTXO is from a coinbase transaction
536    /// Coinbase outputs require maturity (COINBASE_MATURITY blocks) before they can be spent
537    pub is_coinbase: bool,
538}
539
540/// UTXO Set: 𝒰𝒮 = 𝒪 → 𝒰
541///
542/// Arc<UTXO> avoids ~1500+ clones/block during IBD supplement_utxo_map and apply_sync_batch.
543/// In production builds, uses FxHashMap for 2-3x faster lookups in large UTXO sets.
544#[cfg(feature = "production")]
545pub type UtxoSet = FxHashMap<OutPoint, std::sync::Arc<UTXO>>;
546
547#[cfg(not(feature = "production"))]
548pub type UtxoSet = HashMap<OutPoint, std::sync::Arc<UTXO>>;
549
550/// Pre-allocate a UtxoSet for `n` entries. Avoids costly reallocation spikes when loading large
551/// checkpoints (at 50M entries the HashMap table alone is ~2.5 GB; a growth-triggered realloc
552/// temporarily doubles that).
553#[inline]
554pub fn utxo_set_with_capacity(n: usize) -> UtxoSet {
555    #[cfg(feature = "production")]
556    {
557        FxHashMap::with_capacity_and_hasher(n, Default::default())
558    }
559    #[cfg(not(feature = "production"))]
560    {
561        HashMap::with_capacity(n)
562    }
563}
564
565/// Insert owned UTXO into UtxoSet (wraps in Arc). Convenience for tests and one-off inserts.
566#[inline]
567pub fn utxo_set_insert(set: &mut UtxoSet, op: OutPoint, u: UTXO) {
568    use std::sync::Arc;
569    set.insert(op, Arc::new(u));
570}
571
572/// Validation result
573///
574/// Important: This result must be checked - ignoring validation results
575/// may cause consensus violations or security issues.
576#[must_use = "Validation result must be checked - ignoring may cause consensus violations"]
577#[derive(Debug, Clone, PartialEq, Eq)]
578pub enum ValidationResult {
579    Valid,
580    Invalid(String),
581}
582
583/// Script execution context
584#[derive(Debug, Clone)]
585pub struct ScriptContext {
586    pub script_sig: ByteString,
587    pub script_pubkey: ByteString,
588    pub witness: Option<ByteString>,
589    pub flags: u32,
590}
591
592/// Block validation context
593#[derive(Debug, Clone)]
594pub struct BlockContext {
595    pub height: Natural,
596    pub prev_headers: Vec<BlockHeader>,
597    pub utxo_set: UtxoSet,
598}