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
250    Testnet,
251    /// Bitcoin regtest (local testing)
252    Regtest,
253    /// Bitcoin signet (BIP325 test network with block-solution challenge)
254    Signet,
255}
256
257/// Time context for consensus validation
258///
259/// Provides network time and median time-past for timestamp validation.
260/// Required for proper block header timestamp validation (BIP113).
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
262pub struct TimeContext {
263    /// Current network time (Unix timestamp)
264    /// Used to reject blocks with timestamps too far in the future
265    pub network_time: u64,
266    /// Median time-past of previous 11 blocks (BIP113)
267    /// Used to reject a block whose timestamp is not strictly later than this median
268    pub median_time_past: u64,
269}
270
271/// BIP54 timewarp: timestamps of boundary blocks for period-boundary checks.
272///
273/// When BIP54 is active, at height N with N % 2016 == 2015 we require
274/// header.timestamp >= timestamp_n_minus_2015; at N % 2016 == 0 we require
275/// header.timestamp >= timestamp_n_minus_1 - 7200. Callers (e.g. node) pass
276/// these when connecting a block at a period boundary.
277#[derive(Debug, Clone, Copy, PartialEq, Eq)]
278pub struct Bip54BoundaryTimestamps {
279    /// Timestamp of block at height N-1 (for first block of period check)
280    pub timestamp_n_minus_1: u64,
281    /// Timestamp of block at height N-2015 (for last block of period check)
282    pub timestamp_n_minus_2015: u64,
283}
284
285/// Stable identifier for each consensus-affecting fork (BIP or soft-fork bundle).
286///
287/// Used to query "is fork X active at height H?" via a unified activation table
288/// without hardcoding names in every validation function.
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
290pub enum ForkId {
291    /// BIP30: duplicate coinbase prevention (deactivation fork: active when height <= deactivation_height).
292    Bip30,
293    /// BIP16: P2SH.
294    Bip16,
295    /// BIP34: block height in coinbase.
296    Bip34,
297    /// BIP66: strict DER signatures.
298    Bip66,
299    /// BIP65: OP_CHECKLOCKTIMEVERIFY.
300    Bip65,
301    /// BIP112/BIP113: OP_CHECKSEQUENCEVERIFY (CSV). Activates at 419328 mainnet — **before** BIP147.
302    Bip112,
303    /// BIP147: SCRIPT_VERIFY_NULLDUMMY (SegWit deployment; mainnet 481824).
304    Bip147,
305    /// BIP141: SegWit.
306    SegWit,
307    /// BIP341: Taproot.
308    Taproot,
309    /// BIP119: OP_CTV (feature-gated).
310    Ctv,
311    /// BIP348: OP_CSFS (feature-gated).
312    Csfs,
313    /// BIP54: consensus cleanup (version-bits or override).
314    Bip54,
315}
316
317impl Network {
318    /// Get network from environment variable or default to mainnet
319    ///
320    /// Checks `BITCOIN_NETWORK` environment variable:
321    /// - "testnet" -> Network::Testnet
322    /// - "regtest" -> Network::Regtest
323    /// - "signet" -> Network::Signet
324    /// - otherwise -> Network::Mainnet
325    pub fn from_env() -> Self {
326        match std::env::var("BITCOIN_NETWORK").as_deref() {
327            Ok("testnet") => Network::Testnet,
328            Ok("regtest") => Network::Regtest,
329            Ok("signet") => Network::Signet,
330            _ => Network::Mainnet,
331        }
332    }
333
334    /// Get human-readable part (HRP) for Bech32 encoding
335    ///
336    /// Used by blvm-protocol for address encoding (BIP173/350/351)
337    pub fn hrp(&self) -> &'static str {
338        match self {
339            Network::Mainnet => "bc",
340            Network::Testnet => "tb",
341            Network::Regtest => "bcrt",
342            Network::Signet => "tb",
343        }
344    }
345}
346
347/// Block height: newtype wrapper for type safety
348///
349/// Prevents mixing up block heights with other u64 values (e.g., timestamps, counts).
350/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as u64.
351#[repr(transparent)]
352#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
353pub struct BlockHeight(pub u64);
354
355impl BlockHeight {
356    /// Create a new BlockHeight from a u64
357    #[inline(always)]
358    pub fn new(height: u64) -> Self {
359        BlockHeight(height)
360    }
361
362    /// Get the inner u64 value
363    #[inline(always)]
364    pub fn as_u64(self) -> u64 {
365        self.0
366    }
367}
368
369impl From<u64> for BlockHeight {
370    #[inline(always)]
371    fn from(height: u64) -> Self {
372        BlockHeight(height)
373    }
374}
375
376impl From<BlockHeight> for u64 {
377    #[inline(always)]
378    fn from(height: BlockHeight) -> Self {
379        height.0
380    }
381}
382
383impl std::ops::Deref for BlockHeight {
384    type Target = u64;
385
386    #[inline(always)]
387    fn deref(&self) -> &Self::Target {
388        &self.0
389    }
390}
391
392/// Block hash: newtype wrapper for type safety
393///
394/// Prevents mixing up block hashes with other Hash values (e.g., transaction hashes, merkle roots).
395/// Uses `#[repr(transparent)]` for zero-cost abstraction - same memory layout as Hash.
396#[repr(transparent)]
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
398pub struct BlockHash(pub Hash);
399
400impl BlockHash {
401    /// Create a new BlockHash from a Hash
402    #[inline(always)]
403    pub fn new(hash: Hash) -> Self {
404        BlockHash(hash)
405    }
406
407    /// Get the inner Hash value
408    #[inline(always)]
409    pub fn as_hash(self) -> Hash {
410        self.0
411    }
412
413    /// Get a reference to the inner Hash
414    #[inline(always)]
415    pub fn as_hash_ref(&self) -> &Hash {
416        &self.0
417    }
418}
419
420impl From<Hash> for BlockHash {
421    #[inline(always)]
422    fn from(hash: Hash) -> Self {
423        BlockHash(hash)
424    }
425}
426
427impl From<BlockHash> for Hash {
428    #[inline(always)]
429    fn from(hash: BlockHash) -> Self {
430        hash.0
431    }
432}
433
434impl std::ops::Deref for BlockHash {
435    type Target = Hash;
436
437    #[inline(always)]
438    fn deref(&self) -> &Self::Target {
439        &self.0
440    }
441}
442
443/// OutPoint: 𝒪 = ℍ × ℕ
444///
445/// Uses u32 for index (Bitcoin wire format); saves 24 bytes vs repr(align(64)) padding.
446#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
447pub struct OutPoint {
448    pub hash: Hash,
449    pub index: u32,
450}
451
452/// Transaction Input: ℐ = 𝒪 × 𝕊 × ℕ
453///
454/// Performance optimization: Hot fields (prevout, sequence) grouped together
455/// for better cache locality. script_sig is accessed less frequently.
456#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
457pub struct TransactionInput {
458    pub prevout: OutPoint,      // Hot: 40 bytes (frequently accessed)
459    pub sequence: Natural,      // Hot: 8 bytes (frequently accessed)
460    pub script_sig: ByteString, // Cold: Vec (pointer, less frequently accessed)
461}
462
463/// Transaction Output: 𝒯 = ℤ × 𝕊
464#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
465pub struct TransactionOutput {
466    pub value: Integer,
467    pub script_pubkey: ByteString,
468}
469
470/// Transaction: 𝒯𝒳 = ℕ × ℐ* × 𝒯* × ℕ
471///
472/// Performance optimization: Uses SmallVec for inputs/outputs to eliminate
473/// heap allocations for the common case of 1-2 inputs/outputs (80%+ of transactions).
474#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
475pub struct Transaction {
476    pub version: Natural,
477    #[cfg(feature = "production")]
478    pub inputs: SmallVec<[TransactionInput; 2]>,
479    #[cfg(not(feature = "production"))]
480    pub inputs: Vec<TransactionInput>,
481    #[cfg(feature = "production")]
482    pub outputs: SmallVec<[TransactionOutput; 2]>,
483    #[cfg(not(feature = "production"))]
484    pub outputs: Vec<TransactionOutput>,
485    pub lock_time: Natural,
486}
487
488/// Block Header: ℋ = ℤ × ℍ × ℍ × ℕ × ℕ × ℕ
489///
490/// `Default` is only used as a placeholder when extracting the header for BIP113
491/// tracking; the default value is never used for consensus.
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
493pub struct BlockHeader {
494    pub version: Integer,
495    pub prev_block_hash: Hash,
496    pub merkle_root: Hash,
497    pub timestamp: Natural,
498    pub bits: Natural,
499    pub nonce: Natural,
500}
501
502impl std::convert::AsRef<BlockHeader> for BlockHeader {
503    #[inline]
504    fn as_ref(&self) -> &BlockHeader {
505        self
506    }
507}
508
509/// Total Arc<Block> heap allocations created on IBD production paths.
510/// Incremented at: download `received_put` (live + CHUNK_OBSOLETE drain),
511/// `local_block` gap inject. Test dummies do not increment (not in MEM_REPORT).
512pub static ARC_BLOCK_CREATED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
513
514/// Total Arc<BlockHeader> heap allocations created (each Arc::new(header) call).
515/// Compare to jemalloc bin112 curregs to see how many have been freed.
516pub static ARC_BLOCKHEADER_CREATED: std::sync::atomic::AtomicU64 =
517    std::sync::atomic::AtomicU64::new(0);
518
519/// Live `Block` values created via `from_parts` / serde / `Clone` (IBD wire decode
520/// + store + deep copies). Struct-literal test/miner blocks are not incremented.
521///
522/// `Drop` floors at 0 (i64 saturating_sub does not).
523pub static BLOCK_LIVE: AtomicI64 = AtomicI64::new(0);
524
525/// Live `Box<[Transaction]>` whose heap size falls in jemalloc 768KiB–1.5MiB
526/// large classes (`n_tx * size_of::<Transaction>()`). dest-ba 650k bins of
527/// 1.00 / 1.25 MiB are this size class (256 B × 4096 / 5120 txs).
528pub static LARGE_TX_SLICE_LIVE: AtomicI64 = AtomicI64::new(0);
529
530/// Sum of those large slice heaps (bytes).
531pub static LARGE_TX_SLICE_BYTES: AtomicU64 = AtomicU64::new(0);
532
533/// Inclusive jemalloc-large window that covers the 768KiB / 1.00 / 1.25 / 1.50 MiB classes.
534pub const TX_SLICE_LARGE_MIN: usize = 786_432;
535pub const TX_SLICE_LARGE_MAX: usize = 1_572_864;
536
537#[inline]
538pub fn tx_slice_heap_bytes(n_tx: usize) -> usize {
539    n_tx.saturating_mul(std::mem::size_of::<Transaction>())
540}
541
542#[inline]
543fn note_block_new(n_tx: usize) {
544    BLOCK_LIVE.fetch_add(1, Ordering::Relaxed);
545    let b = tx_slice_heap_bytes(n_tx);
546    if (TX_SLICE_LARGE_MIN..=TX_SLICE_LARGE_MAX).contains(&b) {
547        LARGE_TX_SLICE_LIVE.fetch_add(1, Ordering::Relaxed);
548        LARGE_TX_SLICE_BYTES.fetch_add(b as u64, Ordering::Relaxed);
549    }
550}
551
552#[inline]
553fn note_block_drop(n_tx: usize) {
554    // i64 saturating_sub floors at i64::MIN, not 0. M-1 claimed this could
555    // not go negative; dest S-15 printed Block_live=-455775 because wire
556    // decode used a struct literal (no increment) and Drop still decremented.
557    BLOCK_LIVE
558        .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
559            Some(if v > 0 { v - 1 } else { 0 })
560        })
561        .ok();
562    let b = tx_slice_heap_bytes(n_tx);
563    if (TX_SLICE_LARGE_MIN..=TX_SLICE_LARGE_MAX).contains(&b) {
564        LARGE_TX_SLICE_LIVE
565            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
566                Some(if v > 0 { v - 1 } else { 0 })
567            })
568            .ok();
569        LARGE_TX_SLICE_BYTES
570            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |v| {
571                Some(v.saturating_sub(b as u64))
572            })
573            .ok();
574    }
575}
576
577/// Block: ℬ = ℋ × 𝒯𝒳*
578///
579/// Performance optimization: Uses Box<[Transaction]> instead of Vec<Transaction>
580/// since transactions are never modified after block creation. This saves 8 bytes
581/// (no capacity field) and provides better cache usage.
582#[derive(Debug, PartialEq, Eq, Serialize)]
583pub struct Block {
584    pub header: BlockHeader,
585    pub transactions: Box<[Transaction]>,
586}
587
588impl Block {
589    /// Wire-decode / explicit construction. Counts as live (M-2: serde-only
590    /// hooks missed `deserialize_block_with_witnesses`, which uses this path).
591    pub fn from_parts(header: BlockHeader, transactions: Box<[Transaction]>) -> Self {
592        note_block_new(transactions.len());
593        Self {
594            header,
595            transactions,
596        }
597    }
598}
599
600impl Clone for Block {
601    fn clone(&self) -> Self {
602        Self::from_parts(self.header.clone(), self.transactions.clone())
603    }
604}
605
606impl<'de> Deserialize<'de> for Block {
607    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
608        #[derive(Deserialize)]
609        struct BlockDe {
610            header: BlockHeader,
611            transactions: Box<[Transaction]>,
612        }
613        let raw = BlockDe::deserialize(deserializer)?;
614        note_block_new(raw.transactions.len());
615        Ok(Self {
616            header: raw.header,
617            transactions: raw.transactions,
618        })
619    }
620}
621
622impl Drop for Block {
623    fn drop(&mut self) {
624        note_block_drop(self.transactions.len());
625    }
626}
627
628/// UTXO: 𝒰 = ℤ × 𝕊 × ℕ
629#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
630pub struct UTXO {
631    pub value: Integer,
632    pub script_pubkey: SharedByteString,
633    pub height: Natural,
634    /// Whether this UTXO is from a coinbase transaction
635    /// Coinbase outputs require maturity (COINBASE_MATURITY blocks) before they can be spent
636    pub is_coinbase: bool,
637}
638
639/// UTXO Set: 𝒰𝒮 = 𝒪 → 𝒰
640///
641/// Arc<UTXO> avoids ~1500+ clones/block during IBD supplement_utxo_map and apply_sync_batch.
642/// In production builds, uses FxHashMap for 2-3x faster lookups in large UTXO sets.
643#[cfg(feature = "production")]
644pub type UtxoSet = FxHashMap<OutPoint, std::sync::Arc<UTXO>>;
645
646#[cfg(not(feature = "production"))]
647pub type UtxoSet = HashMap<OutPoint, std::sync::Arc<UTXO>>;
648
649/// Pre-allocate a UtxoSet for `n` entries. Avoids costly reallocation spikes when loading large
650/// checkpoints (at 50M entries the HashMap table alone is ~2.5 GB; a growth-triggered realloc
651/// temporarily doubles that).
652#[inline]
653pub fn utxo_set_with_capacity(n: usize) -> UtxoSet {
654    #[cfg(feature = "production")]
655    {
656        FxHashMap::with_capacity_and_hasher(n, Default::default())
657    }
658    #[cfg(not(feature = "production"))]
659    {
660        HashMap::with_capacity(n)
661    }
662}
663
664/// Insert owned UTXO into UtxoSet (wraps in Arc). Convenience for tests and one-off inserts.
665#[inline]
666pub fn utxo_set_insert(set: &mut UtxoSet, op: OutPoint, u: UTXO) {
667    use std::sync::Arc;
668    set.insert(op, Arc::new(u));
669}
670
671/// Validation result
672///
673/// Important: This result must be checked - ignoring validation results
674/// may cause consensus violations or security issues.
675#[must_use = "Validation result must be checked - ignoring may cause consensus violations"]
676#[derive(Debug, Clone, PartialEq, Eq)]
677pub enum ValidationResult {
678    Valid,
679    Invalid(String),
680}
681
682/// Script execution context
683#[derive(Debug, Clone)]
684pub struct ScriptContext {
685    pub script_sig: ByteString,
686    pub script_pubkey: ByteString,
687    pub witness: Option<ByteString>,
688    pub flags: u32,
689}
690
691/// Block validation context
692#[derive(Debug, Clone)]
693pub struct BlockContext {
694    pub height: Natural,
695    pub prev_headers: Vec<BlockHeader>,
696    pub utxo_set: UtxoSet,
697}
698
699#[cfg(test)]
700mod m1_block_live_tests {
701    use super::*;
702
703    #[test]
704    fn dest_ba_1mib_classes_are_tx_slice_widths() {
705        let tsz = std::mem::size_of::<Transaction>();
706        assert_eq!(tx_slice_heap_bytes(4096), 4096 * tsz);
707        assert_eq!(tx_slice_heap_bytes(5120), 5120 * tsz);
708        // production IBD: Transaction=256 → jemalloc 768KiB / 1.00 / 1.25 / 1.50 MiB.
709        if tsz == 256 {
710            assert_eq!(tx_slice_heap_bytes(3072), 786_432);
711            assert_eq!(tx_slice_heap_bytes(4096), 1_048_576);
712            assert_eq!(tx_slice_heap_bytes(5120), 1_310_720);
713            assert_eq!(tx_slice_heap_bytes(6144), 1_572_864);
714            assert!((TX_SLICE_LARGE_MIN..=TX_SLICE_LARGE_MAX).contains(&tx_slice_heap_bytes(4096)));
715            assert!((TX_SLICE_LARGE_MIN..=TX_SLICE_LARGE_MAX).contains(&tx_slice_heap_bytes(5120)));
716        }
717    }
718
719    #[test]
720    fn serde_notes_block_live() {
721        let literal = Block {
722            header: BlockHeader::default(),
723            transactions: Vec::new().into_boxed_slice(),
724        };
725        let json = serde_json::to_string(&literal).expect("ser");
726        drop(literal);
727        let before = BLOCK_LIVE.load(Ordering::Relaxed);
728        let decoded: Block = serde_json::from_str(&json).expect("de");
729        assert_eq!(BLOCK_LIVE.load(Ordering::Relaxed), before + 1);
730        drop(decoded);
731        assert_eq!(BLOCK_LIVE.load(Ordering::Relaxed), before);
732    }
733
734    #[test]
735    fn wire_decode_notes_block_live() {
736        use crate::serialization::block::{deserialize_block_with_witnesses, serialize_block};
737        let tx = Transaction {
738            version: 1,
739            inputs: crate::tx_inputs![TransactionInput {
740                prevout: OutPoint {
741                    hash: [1; 32],
742                    index: 0
743                },
744                script_sig: vec![0x51],
745                sequence: 0xffffffff,
746            }],
747            outputs: crate::tx_outputs![TransactionOutput {
748                value: 50_0000_0000,
749                script_pubkey: vec![0x51],
750            }],
751            lock_time: 0,
752        };
753        let built = Block::from_parts(BlockHeader::default(), vec![tx].into_boxed_slice());
754        let wire = serialize_block(&built);
755        drop(built);
756        let before = BLOCK_LIVE.load(Ordering::Relaxed);
757        let (decoded, _) = deserialize_block_with_witnesses(&wire).expect("wire decode");
758        assert_eq!(BLOCK_LIVE.load(Ordering::Relaxed), before + 1);
759        drop(decoded);
760        assert_eq!(BLOCK_LIVE.load(Ordering::Relaxed), before);
761    }
762
763    #[test]
764    fn drop_floors_live_at_zero() {
765        let before = BLOCK_LIVE.load(Ordering::Relaxed);
766        {
767            let _literal = Block {
768                header: BlockHeader::default(),
769                transactions: Vec::new().into_boxed_slice(),
770            };
771        }
772        assert_eq!(BLOCK_LIVE.load(Ordering::Relaxed), before);
773    }
774}