Skip to main content

blvm_primitives/
types.rs

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