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    /// Bitcoin signet (BIP325 test network with block-solution challenge)
218    Signet,
219}
220
221/// Time context for consensus validation
222///
223/// Provides network time and median time-past for timestamp validation.
224/// Required for proper block header timestamp validation (BIP113).
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub struct TimeContext {
227    /// Current network time (Unix timestamp)
228    /// Used to reject blocks with timestamps too far in the future
229    pub network_time: u64,
230    /// Median time-past of previous 11 blocks (BIP113)
231    /// Used to reject blocks with timestamps before median time-past
232    pub median_time_past: u64,
233}
234
235/// BIP54 timewarp: timestamps of boundary blocks for period-boundary checks.
236///
237/// When BIP54 is active, at height N with N % 2016 == 2015 we require
238/// header.timestamp >= timestamp_n_minus_2015; at N % 2016 == 0 we require
239/// header.timestamp >= timestamp_n_minus_1 - 7200. Callers (e.g. node) pass
240/// these when connecting a block at a period boundary.
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct Bip54BoundaryTimestamps {
243    /// Timestamp of block at height N-1 (for first block of period check)
244    pub timestamp_n_minus_1: u64,
245    /// Timestamp of block at height N-2015 (for last block of period check)
246    pub timestamp_n_minus_2015: u64,
247}
248
249/// Stable identifier for each consensus-affecting fork (BIP or soft-fork bundle).
250///
251/// Used to query "is fork X active at height H?" via a unified activation table
252/// without hardcoding names in every validation function.
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
254pub enum ForkId {
255    /// BIP30: duplicate coinbase prevention (deactivation fork: active when height <= deactivation_height).
256    Bip30,
257    /// BIP16: P2SH.
258    Bip16,
259    /// BIP34: block height in coinbase.
260    Bip34,
261    /// BIP66: strict DER signatures.
262    Bip66,
263    /// BIP65: OP_CHECKLOCKTIMEVERIFY.
264    Bip65,
265    /// BIP112/BIP113: OP_CHECKSEQUENCEVERIFY (CSV). Activates at 419328 mainnet — **before** BIP147.
266    Bip112,
267    /// BIP147: SCRIPT_VERIFY_NULLDUMMY (SegWit deployment; mainnet 481824).
268    Bip147,
269    /// BIP141: SegWit.
270    SegWit,
271    /// BIP341: Taproot.
272    Taproot,
273    /// BIP119: OP_CTV (feature-gated).
274    Ctv,
275    /// BIP348: OP_CSFS (feature-gated).
276    Csfs,
277    /// BIP54: consensus cleanup (version-bits or override).
278    Bip54,
279}
280
281impl Network {
282    /// Get network from environment variable or default to mainnet
283    ///
284    /// Checks `BITCOIN_NETWORK` environment variable:
285    /// - "testnet" -> Network::Testnet
286    /// - "regtest" -> Network::Regtest
287    /// - "signet" -> Network::Signet
288    /// - otherwise -> Network::Mainnet
289    pub fn from_env() -> Self {
290        match std::env::var("BITCOIN_NETWORK").as_deref() {
291            Ok("testnet") => Network::Testnet,
292            Ok("regtest") => Network::Regtest,
293            Ok("signet") => Network::Signet,
294            _ => Network::Mainnet,
295        }
296    }
297
298    /// Get human-readable part (HRP) for Bech32 encoding
299    ///
300    /// Used by blvm-protocol for address encoding (BIP173/350/351)
301    pub fn hrp(&self) -> &'static str {
302        match self {
303            Network::Mainnet => "bc",
304            Network::Testnet => "tb",
305            Network::Regtest => "bcrt",
306            Network::Signet => "tb",
307        }
308    }
309}
310
311/// Block height: newtype wrapper for type safety
312///
313/// Prevents mixing up block heights with other u64 values (e.g., timestamps, counts).
314/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as u64.
315#[repr(transparent)]
316#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
317pub struct BlockHeight(pub u64);
318
319impl BlockHeight {
320    /// Create a new BlockHeight from a u64
321    #[inline(always)]
322    pub fn new(height: u64) -> Self {
323        BlockHeight(height)
324    }
325
326    /// Get the inner u64 value
327    #[inline(always)]
328    pub fn as_u64(self) -> u64 {
329        self.0
330    }
331}
332
333impl From<u64> for BlockHeight {
334    #[inline(always)]
335    fn from(height: u64) -> Self {
336        BlockHeight(height)
337    }
338}
339
340impl From<BlockHeight> for u64 {
341    #[inline(always)]
342    fn from(height: BlockHeight) -> Self {
343        height.0
344    }
345}
346
347impl std::ops::Deref for BlockHeight {
348    type Target = u64;
349
350    #[inline(always)]
351    fn deref(&self) -> &Self::Target {
352        &self.0
353    }
354}
355
356/// Block hash: newtype wrapper for type safety
357///
358/// Prevents mixing up block hashes with other Hash values (e.g., transaction hashes, merkle roots).
359/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as Hash.
360#[repr(transparent)]
361#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
362pub struct BlockHash(pub Hash);
363
364impl BlockHash {
365    /// Create a new BlockHash from a Hash
366    #[inline(always)]
367    pub fn new(hash: Hash) -> Self {
368        BlockHash(hash)
369    }
370
371    /// Get the inner Hash value
372    #[inline(always)]
373    pub fn as_hash(self) -> Hash {
374        self.0
375    }
376
377    /// Get a reference to the inner Hash
378    #[inline(always)]
379    pub fn as_hash_ref(&self) -> &Hash {
380        &self.0
381    }
382}
383
384impl From<Hash> for BlockHash {
385    #[inline(always)]
386    fn from(hash: Hash) -> Self {
387        BlockHash(hash)
388    }
389}
390
391impl From<BlockHash> for Hash {
392    #[inline(always)]
393    fn from(hash: BlockHash) -> Self {
394        hash.0
395    }
396}
397
398impl std::ops::Deref for BlockHash {
399    type Target = Hash;
400
401    #[inline(always)]
402    fn deref(&self) -> &Self::Target {
403        &self.0
404    }
405}
406
407/// OutPoint: 𝒪 = ℍ × ℕ
408///
409/// Uses u32 for index (Bitcoin wire format); saves 24 bytes vs repr(align(64)) padding.
410#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
411pub struct OutPoint {
412    pub hash: Hash,
413    pub index: u32,
414}
415
416/// Transaction Input: ℐ = 𝒪 × 𝕊 × ℕ
417///
418/// Performance optimization: Hot fields (prevout, sequence) grouped together
419/// for better cache locality. script_sig is accessed less frequently.
420#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
421pub struct TransactionInput {
422    pub prevout: OutPoint,      // Hot: 40 bytes (frequently accessed)
423    pub sequence: Natural,      // Hot: 8 bytes (frequently accessed)
424    pub script_sig: ByteString, // Cold: Vec (pointer, less frequently accessed)
425}
426
427/// Transaction Output: 𝒯 = ℤ × 𝕊
428#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
429pub struct TransactionOutput {
430    pub value: Integer,
431    pub script_pubkey: ByteString,
432}
433
434/// Transaction: 𝒯𝒳 = ℕ × ℐ* × 𝒯* × ℕ
435///
436/// Performance optimization: Uses SmallVec for inputs/outputs to eliminate
437/// heap allocations for the common case of 1-2 inputs/outputs (80%+ of transactions).
438#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
439pub struct Transaction {
440    pub version: Natural,
441    #[cfg(feature = "production")]
442    pub inputs: SmallVec<[TransactionInput; 2]>,
443    #[cfg(not(feature = "production"))]
444    pub inputs: Vec<TransactionInput>,
445    #[cfg(feature = "production")]
446    pub outputs: SmallVec<[TransactionOutput; 2]>,
447    #[cfg(not(feature = "production"))]
448    pub outputs: Vec<TransactionOutput>,
449    pub lock_time: Natural,
450}
451
452/// Block Header: ℋ = ℤ × ℍ × ℍ × ℕ × ℕ × ℕ
453///
454/// `Default` is only used as a placeholder when extracting the header for BIP113
455/// tracking; the default value is never used for consensus.
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
457pub struct BlockHeader {
458    pub version: Integer,
459    pub prev_block_hash: Hash,
460    pub merkle_root: Hash,
461    pub timestamp: Natural,
462    pub bits: Natural,
463    pub nonce: Natural,
464}
465
466impl std::convert::AsRef<BlockHeader> for BlockHeader {
467    #[inline]
468    fn as_ref(&self) -> &BlockHeader {
469        self
470    }
471}
472
473/// Block: ℬ = ℋ × 𝒯𝒳*
474///
475/// Performance optimization: Uses Box<[Transaction]> instead of Vec<Transaction>
476/// since transactions are never modified after block creation. This saves 8 bytes
477/// (no capacity field) and provides better cache usage.
478#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
479pub struct Block {
480    pub header: BlockHeader,
481    pub transactions: Box<[Transaction]>,
482}
483
484/// UTXO: 𝒰 = ℤ × 𝕊 × ℕ
485#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
486pub struct UTXO {
487    pub value: Integer,
488    pub script_pubkey: SharedByteString,
489    pub height: Natural,
490    /// Whether this UTXO is from a coinbase transaction
491    /// Coinbase outputs require maturity (COINBASE_MATURITY blocks) before they can be spent
492    pub is_coinbase: bool,
493}
494
495/// UTXO Set: 𝒰𝒮 = 𝒪 → 𝒰
496///
497/// Arc<UTXO> avoids ~1500+ clones/block during IBD supplement_utxo_map and apply_sync_batch.
498/// In production builds, uses FxHashMap for 2-3x faster lookups in large UTXO sets.
499#[cfg(feature = "production")]
500pub type UtxoSet = FxHashMap<OutPoint, std::sync::Arc<UTXO>>;
501
502#[cfg(not(feature = "production"))]
503pub type UtxoSet = HashMap<OutPoint, std::sync::Arc<UTXO>>;
504
505/// Pre-allocate a UtxoSet for `n` entries. Avoids costly reallocation spikes when loading large
506/// checkpoints (at 50M entries the HashMap table alone is ~2.5 GB; a growth-triggered realloc
507/// temporarily doubles that).
508#[inline]
509pub fn utxo_set_with_capacity(n: usize) -> UtxoSet {
510    #[cfg(feature = "production")]
511    {
512        FxHashMap::with_capacity_and_hasher(n, Default::default())
513    }
514    #[cfg(not(feature = "production"))]
515    {
516        HashMap::with_capacity(n)
517    }
518}
519
520/// Insert owned UTXO into UtxoSet (wraps in Arc). Convenience for tests and one-off inserts.
521#[inline]
522pub fn utxo_set_insert(set: &mut UtxoSet, op: OutPoint, u: UTXO) {
523    use std::sync::Arc;
524    set.insert(op, Arc::new(u));
525}
526
527/// Validation result
528///
529/// Important: This result must be checked - ignoring validation results
530/// may cause consensus violations or security issues.
531#[must_use = "Validation result must be checked - ignoring may cause consensus violations"]
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub enum ValidationResult {
534    Valid,
535    Invalid(String),
536}
537
538/// Script execution context
539#[derive(Debug, Clone)]
540pub struct ScriptContext {
541    pub script_sig: ByteString,
542    pub script_pubkey: ByteString,
543    pub witness: Option<ByteString>,
544    pub flags: u32,
545}
546
547/// Block validation context
548#[derive(Debug, Clone)]
549pub struct BlockContext {
550    pub height: Natural,
551    pub prev_headers: Vec<BlockHeader>,
552    pub utxo_set: UtxoSet,
553}