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#[derive(Clone)]
75enum SharedRepr {
76    Inline {
77        len: u8,
78        data: [u8; SHARED_BYTE_INLINE_CAP],
79    },
80    Shared(std::sync::Arc<[u8]>),
81}
82
83/// Shareable script_pubkey for UTXO: small scripts use inline storage; longer use `Arc<[u8]>`.
84/// Clone is cheap (inline copies up to 64 bytes, shared is `Arc::clone`). Serde matches `ByteString`.
85#[derive(Clone)]
86pub struct SharedByteString(SharedRepr);
87
88impl std::fmt::Debug for SharedByteString {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.debug_tuple("SharedByteString")
91            .field(&self.as_slice())
92            .finish()
93    }
94}
95
96impl PartialEq for SharedByteString {
97    #[inline]
98    fn eq(&self, other: &Self) -> bool {
99        self.as_slice() == other.as_slice()
100    }
101}
102
103impl Eq for SharedByteString {}
104
105impl std::hash::Hash for SharedByteString {
106    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
107        self.as_slice().hash(state);
108    }
109}
110
111impl SharedByteString {
112    #[inline]
113    fn as_slice(&self) -> &[u8] {
114        match &self.0 {
115            SharedRepr::Inline { len, data } => &data[..*len as usize],
116            SharedRepr::Shared(a) => a,
117        }
118    }
119
120    #[inline]
121    fn from_bytes(v: &[u8]) -> Self {
122        if v.len() <= SHARED_BYTE_INLINE_CAP {
123            let mut data = [0u8; SHARED_BYTE_INLINE_CAP];
124            data[..v.len()].copy_from_slice(v);
125            Self(SharedRepr::Inline {
126                len: v.len() as u8,
127                data,
128            })
129        } else {
130            Self(SharedRepr::Shared(std::sync::Arc::from(v)))
131        }
132    }
133}
134
135impl std::ops::Deref for SharedByteString {
136    type Target = [u8];
137    #[inline]
138    fn deref(&self) -> &[u8] {
139        self.as_slice()
140    }
141}
142
143impl Serialize for SharedByteString {
144    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
145        self.as_slice().serialize(s)
146    }
147}
148
149impl<'de> Deserialize<'de> for SharedByteString {
150    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
151        let v: Vec<u8> = Deserialize::deserialize(d)?;
152        Ok(Self::from_bytes(&v))
153    }
154}
155
156impl From<ByteString> for SharedByteString {
157    #[inline]
158    fn from(v: ByteString) -> Self {
159        Self::from_bytes(v.as_slice())
160    }
161}
162
163impl From<&[u8]> for SharedByteString {
164    #[inline]
165    fn from(v: &[u8]) -> Self {
166        Self::from_bytes(v)
167    }
168}
169
170impl Default for SharedByteString {
171    #[inline]
172    fn default() -> Self {
173        Self(SharedRepr::Inline {
174            len: 0,
175            data: [0u8; SHARED_BYTE_INLINE_CAP],
176        })
177    }
178}
179
180impl AsRef<[u8]> for SharedByteString {
181    #[inline]
182    fn as_ref(&self) -> &[u8] {
183        self.as_slice()
184    }
185}
186
187impl SharedByteString {
188    /// Owning clone of bytes as `Arc<[u8]>`. May allocate once when storing an inline script.
189    #[inline]
190    pub fn as_arc(&self) -> std::sync::Arc<[u8]> {
191        match &self.0 {
192            SharedRepr::Shared(a) => std::sync::Arc::clone(a),
193            SharedRepr::Inline { len, data } => {
194                std::sync::Arc::from(data[..*len as usize].to_vec().into_boxed_slice())
195            }
196        }
197    }
198}
199
200/// Natural number type
201pub type Natural = u64;
202
203/// Integer type
204pub type Integer = i64;
205
206/// Network type for consensus validation
207///
208/// Used to determine activation heights for various BIPs and consensus rules.
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
210pub enum Network {
211    /// Bitcoin mainnet
212    Mainnet,
213    /// Bitcoin testnet
214    Testnet,
215    /// Bitcoin regtest (local testing)
216    Regtest,
217}
218
219/// Time context for consensus validation
220///
221/// Provides network time and median time-past for timestamp validation.
222/// Required for proper block header timestamp validation (BIP113).
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
224pub struct TimeContext {
225    /// Current network time (Unix timestamp)
226    /// Used to reject blocks with timestamps too far in the future
227    pub network_time: u64,
228    /// Median time-past of previous 11 blocks (BIP113)
229    /// Used to reject blocks with timestamps before median time-past
230    pub median_time_past: u64,
231}
232
233/// BIP54 timewarp: timestamps of boundary blocks for period-boundary checks.
234///
235/// When BIP54 is active, at height N with N % 2016 == 2015 we require
236/// header.timestamp >= timestamp_n_minus_2015; at N % 2016 == 0 we require
237/// header.timestamp >= timestamp_n_minus_1 - 7200. Callers (e.g. node) pass
238/// these when connecting a block at a period boundary.
239#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub struct Bip54BoundaryTimestamps {
241    /// Timestamp of block at height N-1 (for first block of period check)
242    pub timestamp_n_minus_1: u64,
243    /// Timestamp of block at height N-2015 (for last block of period check)
244    pub timestamp_n_minus_2015: u64,
245}
246
247/// Stable identifier for each consensus-affecting fork (BIP or soft-fork bundle).
248///
249/// Used to query "is fork X active at height H?" via a unified activation table
250/// without hardcoding names in every validation function.
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252pub enum ForkId {
253    /// BIP30: duplicate coinbase prevention (deactivation fork: active when height <= deactivation_height).
254    Bip30,
255    /// BIP16: P2SH.
256    Bip16,
257    /// BIP34: block height in coinbase.
258    Bip34,
259    /// BIP66: strict DER signatures.
260    Bip66,
261    /// BIP65: OP_CHECKLOCKTIMEVERIFY.
262    Bip65,
263    /// BIP112/BIP113: OP_CHECKSEQUENCEVERIFY (CSV). Activates at 419328 mainnet — **before** BIP147.
264    Bip112,
265    /// BIP147: SCRIPT_VERIFY_NULLDUMMY (SegWit deployment; mainnet 481824).
266    Bip147,
267    /// BIP141: SegWit.
268    SegWit,
269    /// BIP341: Taproot.
270    Taproot,
271    /// BIP119: OP_CTV (feature-gated).
272    Ctv,
273    /// BIP348: OP_CSFS (feature-gated).
274    Csfs,
275    /// BIP54: consensus cleanup (version-bits or override).
276    Bip54,
277}
278
279impl Network {
280    /// Get network from environment variable or default to mainnet
281    ///
282    /// Checks `BITCOIN_NETWORK` environment variable:
283    /// - "testnet" -> Network::Testnet
284    /// - "regtest" -> Network::Regtest
285    /// - otherwise -> Network::Mainnet
286    pub fn from_env() -> Self {
287        match std::env::var("BITCOIN_NETWORK").as_deref() {
288            Ok("testnet") => Network::Testnet,
289            Ok("regtest") => Network::Regtest,
290            _ => Network::Mainnet,
291        }
292    }
293
294    /// Get human-readable part (HRP) for Bech32 encoding
295    ///
296    /// Used by blvm-protocol for address encoding (BIP173/350/351)
297    pub fn hrp(&self) -> &'static str {
298        match self {
299            Network::Mainnet => "bc",
300            Network::Testnet => "tb",
301            Network::Regtest => "bcrt",
302        }
303    }
304}
305
306/// Block height: newtype wrapper for type safety
307///
308/// Prevents mixing up block heights with other u64 values (e.g., timestamps, counts).
309/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as u64.
310#[repr(transparent)]
311#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
312pub struct BlockHeight(pub u64);
313
314impl BlockHeight {
315    /// Create a new BlockHeight from a u64
316    #[inline(always)]
317    pub fn new(height: u64) -> Self {
318        BlockHeight(height)
319    }
320
321    /// Get the inner u64 value
322    #[inline(always)]
323    pub fn as_u64(self) -> u64 {
324        self.0
325    }
326}
327
328impl From<u64> for BlockHeight {
329    #[inline(always)]
330    fn from(height: u64) -> Self {
331        BlockHeight(height)
332    }
333}
334
335impl From<BlockHeight> for u64 {
336    #[inline(always)]
337    fn from(height: BlockHeight) -> Self {
338        height.0
339    }
340}
341
342impl std::ops::Deref for BlockHeight {
343    type Target = u64;
344
345    #[inline(always)]
346    fn deref(&self) -> &Self::Target {
347        &self.0
348    }
349}
350
351/// Block hash: newtype wrapper for type safety
352///
353/// Prevents mixing up block hashes with other Hash values (e.g., transaction hashes, merkle roots).
354/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as Hash.
355#[repr(transparent)]
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
357pub struct BlockHash(pub Hash);
358
359impl BlockHash {
360    /// Create a new BlockHash from a Hash
361    #[inline(always)]
362    pub fn new(hash: Hash) -> Self {
363        BlockHash(hash)
364    }
365
366    /// Get the inner Hash value
367    #[inline(always)]
368    pub fn as_hash(self) -> Hash {
369        self.0
370    }
371
372    /// Get a reference to the inner Hash
373    #[inline(always)]
374    pub fn as_hash_ref(&self) -> &Hash {
375        &self.0
376    }
377}
378
379impl From<Hash> for BlockHash {
380    #[inline(always)]
381    fn from(hash: Hash) -> Self {
382        BlockHash(hash)
383    }
384}
385
386impl From<BlockHash> for Hash {
387    #[inline(always)]
388    fn from(hash: BlockHash) -> Self {
389        hash.0
390    }
391}
392
393impl std::ops::Deref for BlockHash {
394    type Target = Hash;
395
396    #[inline(always)]
397    fn deref(&self) -> &Self::Target {
398        &self.0
399    }
400}
401
402/// OutPoint: 𝒪 = ℍ × ℕ
403///
404/// Uses u32 for index (Bitcoin wire format); saves 24 bytes vs repr(align(64)) padding.
405#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
406pub struct OutPoint {
407    pub hash: Hash,
408    pub index: u32,
409}
410
411/// Transaction Input: ℐ = 𝒪 × 𝕊 × ℕ
412///
413/// Performance optimization: Hot fields (prevout, sequence) grouped together
414/// for better cache locality. script_sig is accessed less frequently.
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
416pub struct TransactionInput {
417    pub prevout: OutPoint,      // Hot: 40 bytes (frequently accessed)
418    pub sequence: Natural,      // Hot: 8 bytes (frequently accessed)
419    pub script_sig: ByteString, // Cold: Vec (pointer, less frequently accessed)
420}
421
422/// Transaction Output: 𝒯 = ℤ × 𝕊
423#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
424pub struct TransactionOutput {
425    pub value: Integer,
426    pub script_pubkey: ByteString,
427}
428
429/// Transaction: 𝒯𝒳 = ℕ × ℐ* × 𝒯* × ℕ
430///
431/// Performance optimization: Uses SmallVec for inputs/outputs to eliminate
432/// heap allocations for the common case of 1-2 inputs/outputs (80%+ of transactions).
433#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
434pub struct Transaction {
435    pub version: Natural,
436    #[cfg(feature = "production")]
437    pub inputs: SmallVec<[TransactionInput; 2]>,
438    #[cfg(not(feature = "production"))]
439    pub inputs: Vec<TransactionInput>,
440    #[cfg(feature = "production")]
441    pub outputs: SmallVec<[TransactionOutput; 2]>,
442    #[cfg(not(feature = "production"))]
443    pub outputs: Vec<TransactionOutput>,
444    pub lock_time: Natural,
445}
446
447/// Block Header: ℋ = ℤ × ℍ × ℍ × ℕ × ℕ × ℕ
448///
449/// `Default` is only used as a placeholder when extracting the header for BIP113
450/// tracking; the default value is never used for consensus.
451#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
452pub struct BlockHeader {
453    pub version: Integer,
454    pub prev_block_hash: Hash,
455    pub merkle_root: Hash,
456    pub timestamp: Natural,
457    pub bits: Natural,
458    pub nonce: Natural,
459}
460
461impl std::convert::AsRef<BlockHeader> for BlockHeader {
462    #[inline]
463    fn as_ref(&self) -> &BlockHeader {
464        self
465    }
466}
467
468/// Block: ℬ = ℋ × 𝒯𝒳*
469///
470/// Performance optimization: Uses Box<[Transaction]> instead of Vec<Transaction>
471/// since transactions are never modified after block creation. This saves 8 bytes
472/// (no capacity field) and provides better cache usage.
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct Block {
475    pub header: BlockHeader,
476    pub transactions: Box<[Transaction]>,
477}
478
479/// UTXO: 𝒰 = ℤ × 𝕊 × ℕ
480#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
481pub struct UTXO {
482    pub value: Integer,
483    pub script_pubkey: SharedByteString,
484    pub height: Natural,
485    /// Whether this UTXO is from a coinbase transaction
486    /// Coinbase outputs require maturity (COINBASE_MATURITY blocks) before they can be spent
487    pub is_coinbase: bool,
488}
489
490/// UTXO Set: 𝒰𝒮 = 𝒪 → 𝒰
491///
492/// Arc<UTXO> avoids ~1500+ clones/block during IBD supplement_utxo_map and apply_sync_batch.
493/// In production builds, uses FxHashMap for 2-3x faster lookups in large UTXO sets.
494#[cfg(feature = "production")]
495pub type UtxoSet = FxHashMap<OutPoint, std::sync::Arc<UTXO>>;
496
497#[cfg(not(feature = "production"))]
498pub type UtxoSet = HashMap<OutPoint, std::sync::Arc<UTXO>>;
499
500/// Pre-allocate a UtxoSet for `n` entries. Avoids costly reallocation spikes when loading large
501/// checkpoints (at 50M entries the HashMap table alone is ~2.5 GB; a growth-triggered realloc
502/// temporarily doubles that).
503#[inline]
504pub fn utxo_set_with_capacity(n: usize) -> UtxoSet {
505    #[cfg(feature = "production")]
506    {
507        FxHashMap::with_capacity_and_hasher(n, Default::default())
508    }
509    #[cfg(not(feature = "production"))]
510    {
511        HashMap::with_capacity(n)
512    }
513}
514
515/// Insert owned UTXO into UtxoSet (wraps in Arc). Convenience for tests and one-off inserts.
516#[inline]
517pub fn utxo_set_insert(set: &mut UtxoSet, op: OutPoint, u: UTXO) {
518    use std::sync::Arc;
519    set.insert(op, Arc::new(u));
520}
521
522/// Validation result
523///
524/// Important: This result must be checked - ignoring validation results
525/// may cause consensus violations or security issues.
526#[must_use = "Validation result must be checked - ignoring may cause consensus violations"]
527#[derive(Debug, Clone, PartialEq, Eq)]
528pub enum ValidationResult {
529    Valid,
530    Invalid(String),
531}
532
533/// Script execution context
534#[derive(Debug, Clone)]
535pub struct ScriptContext {
536    pub script_sig: ByteString,
537    pub script_pubkey: ByteString,
538    pub witness: Option<ByteString>,
539    pub flags: u32,
540}
541
542/// Block validation context
543#[derive(Debug, Clone)]
544pub struct BlockContext {
545    pub height: Natural,
546    pub prev_headers: Vec<BlockHeader>,
547    pub utxo_set: UtxoSet,
548}