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