Skip to main content

abtc_application/
miner.rs

1//! Block Mining — Nonce Grinding and Block Generation
2//!
3//! This module provides the core mining functionality:
4//!
5//! - **`mine_block`** takes a block template and grinds the nonce until the
6//!   block hash satisfies the proof-of-work target. For regtest (where the
7//!   target is effectively unlimited) this returns almost immediately.
8//!
9//! - **`generate_blocks`** is the equivalent of Bitcoin Core's `generatetoaddress`
10//!   RPC — it mines N blocks sequentially, connecting each to the chain state.
11//!   This is indispensable for integration tests and regtest workflows.
12//!
13//! The mining loop is intentionally single-threaded and synchronous — real miners
14//! use ASICs, so there's no point in optimising the nonce search beyond what is
15//! needed for testing.
16
17use abtc_domain::consensus::{
18    decode_compact, decode_compact_u256, hash_meets_target, ConsensusParams,
19};
20use abtc_domain::primitives::{Amount, Block, BlockHash, BlockHeader, Hash256, Transaction, TxOut};
21use abtc_domain::script::Script;
22
23use crate::chain_state::{ChainState, ChainStateError};
24
25use sha2::{Digest, Sha256};
26
27// ── Mining error ───────────────────────────────────────────────────
28
29/// Errors that can occur during mining.
30#[derive(Debug)]
31pub enum MiningError {
32    /// The nonce space was exhausted without finding a valid hash.
33    NonceExhausted,
34    /// The mined block failed validation when connected to the chain.
35    ChainError(ChainStateError),
36    /// Signet block signing failed (BIP325).
37    SignetSigningFailed(String),
38}
39
40impl std::fmt::Display for MiningError {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            MiningError::NonceExhausted => write!(f, "nonce space exhausted"),
44            MiningError::ChainError(e) => write!(f, "chain error: {}", e),
45            MiningError::SignetSigningFailed(reason) => {
46                write!(f, "signet signing failed: {}", reason)
47            }
48        }
49    }
50}
51
52impl std::error::Error for MiningError {}
53
54impl From<ChainStateError> for MiningError {
55    fn from(e: ChainStateError) -> Self {
56        MiningError::ChainError(e)
57    }
58}
59
60// ── Core mining function ───────────────────────────────────────────
61
62/// Mine a block by grinding the nonce until the PoW target is met.
63///
64/// Takes a mutable block (header + transactions already assembled) and
65/// increments `header.nonce` from 0 through `u32::MAX`. For each candidate
66/// the 80-byte header is double-SHA256'd and the first 16 bytes are compared
67/// (little-endian u128) against the decoded compact target.
68///
69/// # Performance
70///
71/// For efficiency the first 76 bytes of the serialised header are computed
72/// once, and only the final 4 nonce bytes are varied in the inner loop.
73///
74/// # Returns
75///
76/// The block with a valid nonce, or `MiningError::NonceExhausted` if no
77/// valid nonce exists (astronomically unlikely on mainnet difficulty, and
78/// impossible on regtest where the target is u128::MAX).
79pub fn mine_block(mut block: Block) -> Result<Block, MiningError> {
80    let target_u256 = decode_compact_u256(block.header.bits);
81
82    // Fast path: regtest target has all bytes at max, any nonce works.
83    if target_u256 == [0xff; 32] {
84        return Ok(block);
85    }
86
87    // Also fast-path for regtest bits like 0x207fffff where the u128 decoding
88    // saturated — check if the target is effectively unlimited.
89    let target_u128 = decode_compact(block.header.bits);
90    if target_u128 == u128::MAX {
91        return Ok(block);
92    }
93
94    // Pre-compute the first 76 bytes of the header (everything except nonce).
95    let prefix = header_prefix(&block.header);
96
97    for nonce in 0..=u32::MAX {
98        // Build the full 80-byte header with this nonce.
99        let mut header_bytes = prefix.clone();
100        header_bytes.extend_from_slice(&nonce.to_le_bytes());
101
102        // Double-SHA256.
103        let hash = double_sha256(&header_bytes);
104
105        if hash_meets_target(&hash, &target_u256) {
106            block.header.nonce = nonce;
107            return Ok(block);
108        }
109    }
110
111    Err(MiningError::NonceExhausted)
112}
113
114// ── Block generation (regtest helper) ──────────────────────────────
115
116/// Mine `count` blocks on top of the current chain, returning their hashes.
117///
118/// This is the functional equivalent of Bitcoin Core's `generatetoaddress`.
119/// Each block contains only a coinbase transaction paying to the given
120/// `coinbase_script`. Mempool transaction selection is not performed here —
121/// that's the job of `SimpleMiner`/`BlockTemplateProvider`. This function
122/// is focused on rapidly extending the chain for testing.
123///
124/// # Arguments
125///
126/// * `chain` — mutable reference to the chain state manager.
127/// * `count` — number of blocks to mine.
128/// * `coinbase_script` — script to use for coinbase outputs.
129///
130/// # Returns
131///
132/// A vector of `BlockHash` values for the newly mined blocks.
133pub fn generate_blocks(
134    chain: &mut ChainState,
135    count: u32,
136    coinbase_script: &Script,
137) -> Result<Vec<BlockHash>, MiningError> {
138    let mut hashes = Vec::with_capacity(count as usize);
139
140    for _ in 0..count {
141        let block = build_next_block(chain, coinbase_script);
142        let mined = mine_block(block)?;
143        let hash = mined.block_hash();
144
145        chain.process_block(mined)?;
146        hashes.push(hash);
147    }
148
149    Ok(hashes)
150}
151
152/// Build the next block on top of the current chain tip.
153///
154/// Creates a proper coinbase with the correct subsidy for the next height
155/// and assembles a single-transaction block ready for mining.
156fn build_next_block(chain: &ChainState, coinbase_output_script: &Script) -> Block {
157    let tip = chain.tip();
158    let height = chain.tip_height() + 1;
159    let params = chain.params();
160    let bits = params.pow_limit_bits;
161
162    // Subsidy calculation (matches SimpleMiner::get_block_subsidy).
163    let subsidy = get_block_subsidy(height, params);
164
165    // Build a valid coinbase scriptSig with BIP34 height encoding.
166    // The scriptSig must be at least 2 bytes (MIN_COINBASE_SCRIPT_SIZE).
167    let coinbase_scriptsig = build_coinbase_script(height);
168
169    // Coinbase transaction.
170    let coinbase = Transaction::coinbase(
171        height,
172        coinbase_scriptsig,
173        vec![TxOut::new(subsidy, coinbase_output_script.clone())],
174    );
175
176    // Timestamp: current time or, for determinism in tests, just height.
177    let time = std::time::SystemTime::now()
178        .duration_since(std::time::UNIX_EPOCH)
179        .unwrap_or_default()
180        .as_secs() as u32;
181
182    // Compute merkle root.
183    let temp_block = Block::new(
184        BlockHeader::new(0x20000000, tip, Hash256::zero(), time, bits, 0),
185        vec![coinbase.clone()],
186    );
187    let merkle_root = temp_block.compute_merkle_root();
188
189    // Final block.
190    let header = BlockHeader::new(0x20000000, tip, merkle_root, time, bits, 0);
191    Block::new(header, vec![coinbase])
192}
193
194/// Calculate block subsidy following the halving schedule.
195///
196/// Initial subsidy is 50 BTC = 5,000,000,000 satoshis.
197/// Halves every `subsidy_halving_interval` blocks.
198/// After 64 halvings the subsidy is zero.
199fn get_block_subsidy(height: u32, params: &ConsensusParams) -> Amount {
200    let interval = params.subsidy_halving_interval;
201    if interval == 0 {
202        return Amount::from_sat(5_000_000_000);
203    }
204
205    let halvings = height / interval;
206    if halvings >= 64 {
207        return Amount::from_sat(0);
208    }
209
210    let initial: i64 = 50 * 100_000_000;
211    Amount::from_sat(initial >> halvings)
212}
213
214// ── Coinbase script builder ────────────────────────────────────────
215
216/// Build a BIP34-compliant coinbase scriptSig encoding the block height.
217///
218/// BIP34 requires the coinbase scriptSig to start with a serialised
219/// CScriptNum of the block height. The encoding is:
220///   - height 0:       `[OP_0]` (1 byte) — pad to 2 bytes
221///   - height 1..16:   `[OP_n]` (1 byte) — pad to 2 bytes
222///   - height 17..255: `[0x01, height_byte]` (2 bytes)
223///   - height 256..65535: `[0x02, lo, hi]` (3 bytes)
224///   - etc.
225///
226/// We always ensure the result is at least `MIN_COINBASE_SCRIPT_SIZE` (2)
227/// bytes by appending a padding byte if needed.
228fn build_coinbase_script(height: u32) -> Script {
229    let mut script = Vec::new();
230
231    if height == 0 {
232        // OP_0
233        script.push(0x00);
234    } else if height <= 16 {
235        // OP_1 through OP_16
236        script.push(0x50 + height as u8);
237    } else {
238        // Serialise height as a minimal CScriptNum push.
239        let mut h = height;
240        let mut buf = Vec::new();
241        while h > 0 {
242            buf.push((h & 0xff) as u8);
243            h >>= 8;
244        }
245        // If the top bit is set, add a zero byte to keep it positive.
246        if buf.last().is_some_and(|&b| b & 0x80 != 0) {
247            buf.push(0);
248        }
249        script.push(buf.len() as u8); // push length
250        script.extend_from_slice(&buf);
251    }
252
253    // Pad to minimum 2 bytes if needed (e.g. height 0 or 1..16 produce 1 byte).
254    while script.len() < 2 {
255        script.push(0x00); // OP_0 padding
256    }
257
258    Script::from_bytes(script)
259}
260
261// ── Low-level helpers ──────────────────────────────────────────────
262
263/// Serialize the first 76 bytes of a block header (everything except nonce).
264fn header_prefix(h: &BlockHeader) -> Vec<u8> {
265    let mut data = Vec::with_capacity(76);
266    data.extend_from_slice(&h.version.to_le_bytes());
267    data.extend_from_slice(h.prev_block_hash.as_bytes());
268    data.extend_from_slice(h.merkle_root.as_bytes());
269    data.extend_from_slice(&h.time.to_le_bytes());
270    data.extend_from_slice(&h.bits.to_le_bytes());
271    data
272}
273
274/// Double-SHA256 of arbitrary data.
275fn double_sha256(data: &[u8]) -> [u8; 32] {
276    let first = Sha256::digest(data);
277    let second = Sha256::digest(first);
278    let mut out = [0u8; 32];
279    out.copy_from_slice(&second);
280    out
281}
282
283// ── Signet block signing (BIP325) ────────────────────────────────
284
285/// Sign a signet block for a P2WPKH challenge script.
286///
287/// Delegates to `abtc_domain::consensus::signet::sign_block_p2wpkh` for the
288/// actual BIP325 signing logic (sighash computation, ECDSA signing, commitment
289/// construction). This wrapper maps `SignetError` into `MiningError`.
290///
291/// The `secret_key_bytes` must be 32 raw bytes of a valid secp256k1 secret key
292/// whose compressed public key hashes to the P2WPKH witness program in `challenge`.
293///
294/// # Returns
295///
296/// A new block with the signed signet commitment in place and an updated
297/// merkle root.
298pub fn sign_signet_block_p2wpkh(
299    block: &Block,
300    challenge: &Script,
301    secret_key_bytes: &[u8; 32],
302) -> Result<Block, MiningError> {
303    abtc_domain::consensus::signet::sign_block_p2wpkh(block, challenge, secret_key_bytes)
304        .map_err(|e| MiningError::SignetSigningFailed(e.to_string()))
305}
306
307// ── Tests ──────────────────────────────────────────────────────────
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use abtc_domain::consensus::ConsensusParams;
313    use abtc_domain::primitives::{Amount, Block, BlockHash, BlockHeader, Hash256, TxOut};
314    use abtc_domain::script::Script;
315
316    /// Helper: create a regtest genesis block.
317    fn regtest_genesis() -> Block {
318        let params = ConsensusParams::regtest();
319        let coinbase = Transaction::coinbase(
320            0,
321            build_coinbase_script(0),
322            vec![TxOut::new(
323                Amount::from_sat(50 * 100_000_000),
324                Script::new(),
325            )],
326        );
327        let header = BlockHeader::new(
328            1,
329            BlockHash::zero(),
330            Hash256::zero(),
331            1296688602, // regtest genesis timestamp
332            params.pow_limit_bits,
333            0,
334        );
335        let mut block = Block::new(header, vec![coinbase]);
336        let merkle = block.compute_merkle_root();
337        block.header.merkle_root = merkle;
338        block
339    }
340
341    // ── Subsidy tests ──────────────────────────────────────────
342
343    #[test]
344    fn test_subsidy_initial() {
345        let params = ConsensusParams::mainnet();
346        assert_eq!(get_block_subsidy(0, &params).as_sat(), 5_000_000_000);
347        assert_eq!(get_block_subsidy(1, &params).as_sat(), 5_000_000_000);
348    }
349
350    #[test]
351    fn test_subsidy_halving() {
352        let params = ConsensusParams::mainnet();
353        assert_eq!(get_block_subsidy(210_000, &params).as_sat(), 2_500_000_000);
354        assert_eq!(get_block_subsidy(420_000, &params).as_sat(), 1_250_000_000);
355        assert_eq!(get_block_subsidy(630_000, &params).as_sat(), 625_000_000);
356    }
357
358    #[test]
359    fn test_subsidy_regtest() {
360        let params = ConsensusParams::regtest();
361        // Regtest halves every 150 blocks.
362        assert_eq!(get_block_subsidy(0, &params).as_sat(), 5_000_000_000);
363        assert_eq!(get_block_subsidy(149, &params).as_sat(), 5_000_000_000);
364        assert_eq!(get_block_subsidy(150, &params).as_sat(), 2_500_000_000);
365        assert_eq!(get_block_subsidy(300, &params).as_sat(), 1_250_000_000);
366    }
367
368    #[test]
369    fn test_subsidy_exhausted() {
370        let params = ConsensusParams::mainnet();
371        assert_eq!(get_block_subsidy(210_000 * 64, &params).as_sat(), 0);
372    }
373
374    // ── mine_block tests ───────────────────────────────────────
375
376    #[test]
377    fn test_mine_block_regtest() {
378        // With regtest difficulty (target = u128::MAX) mining should succeed
379        // immediately with nonce 0.
380        let genesis = regtest_genesis();
381        let params = ConsensusParams::regtest();
382
383        let coinbase = Transaction::coinbase(
384            1,
385            build_coinbase_script(1),
386            vec![TxOut::new(Amount::from_sat(5_000_000_000), Script::new())],
387        );
388        let header = BlockHeader::new(
389            0x20000000,
390            genesis.block_hash(),
391            Hash256::zero(),
392            1296688602 + 1,
393            params.pow_limit_bits,
394            0,
395        );
396        let mut block = Block::new(header, vec![coinbase]);
397        let merkle = block.compute_merkle_root();
398        block.header.merkle_root = merkle;
399
400        let mined = mine_block(block).unwrap();
401        assert_eq!(mined.header.nonce, 0); // regtest fast-path
402    }
403
404    #[test]
405    fn test_mine_block_low_difficulty() {
406        // Use a target that exercises the real mining loop but finishes fast.
407        // bits = 0x2100ffff: exponent 0x21 = 33, mantissa 0x00ffff.
408        // decode_compact_u256 places 0x00ffff at byte offset 33-3 = 30, so
409        // the target's byte 31 is 0x00, byte 30 is 0xff, byte 29 is 0xff,
410        // and everything below is 0x00. The hash only needs its topmost byte
411        // (LE byte 31) to be 0x00, which happens ~1/256 tries — a few hundred
412        // nonces at most.
413        let coinbase = Transaction::coinbase(
414            1,
415            build_coinbase_script(1),
416            vec![TxOut::new(Amount::from_sat(5_000_000_000), Script::new())],
417        );
418        let bits = 0x2100ffffu32;
419        let header = BlockHeader::new(
420            0x20000000,
421            BlockHash::zero(),
422            Hash256::zero(),
423            12345,
424            bits,
425            0,
426        );
427        let mut block = Block::new(header, vec![coinbase]);
428        let merkle = block.compute_merkle_root();
429        block.header.merkle_root = merkle;
430
431        let mined = mine_block(block).unwrap();
432        // Verify the mined block actually satisfies PoW (full 256-bit).
433        let hash = mined.header.block_hash();
434        let target = decode_compact_u256(bits);
435        assert!(
436            hash_meets_target(hash.as_bytes(), &target),
437            "mined block should satisfy PoW"
438        );
439    }
440
441    // ── generate_blocks tests ──────────────────────────────────
442
443    #[test]
444    fn test_generate_single_block() {
445        let genesis = regtest_genesis();
446        let params = ConsensusParams::regtest();
447        let mut chain = ChainState::new(genesis, params).unwrap();
448
449        let hashes = generate_blocks(&mut chain, 1, &Script::new()).unwrap();
450        assert_eq!(hashes.len(), 1);
451        assert_eq!(chain.tip_height(), 1);
452        assert_eq!(chain.tip(), hashes[0]);
453    }
454
455    #[test]
456    fn test_generate_chain_of_blocks() {
457        let genesis = regtest_genesis();
458        let params = ConsensusParams::regtest();
459        let mut chain = ChainState::new(genesis, params).unwrap();
460
461        let hashes = generate_blocks(&mut chain, 50, &Script::new()).unwrap();
462        assert_eq!(hashes.len(), 50);
463        assert_eq!(chain.tip_height(), 50);
464        assert_eq!(chain.tip(), hashes[49]);
465
466        // Each block should be distinct.
467        let unique: std::collections::HashSet<_> = hashes.iter().collect();
468        assert_eq!(unique.len(), 50);
469    }
470
471    #[test]
472    fn test_generate_past_first_halving() {
473        // Regtest halves at height 150. Mine 160 blocks and verify the
474        // subsidy change is reflected in coinbase values.
475        let genesis = regtest_genesis();
476        let params = ConsensusParams::regtest();
477        let mut chain = ChainState::new(genesis, params).unwrap();
478
479        let hashes = generate_blocks(&mut chain, 160, &Script::new()).unwrap();
480        assert_eq!(hashes.len(), 160);
481        assert_eq!(chain.tip_height(), 160);
482
483        // Block at height 149 should have 50 BTC coinbase.
484        let block_149 = chain.get_block(&hashes[148]).unwrap();
485        assert_eq!(
486            block_149.transactions[0].total_output_value().as_sat(),
487            5_000_000_000
488        );
489
490        // Block at height 150 should have 25 BTC coinbase (first halving).
491        let block_150 = chain.get_block(&hashes[149]).unwrap();
492        assert_eq!(
493            block_150.transactions[0].total_output_value().as_sat(),
494            2_500_000_000
495        );
496    }
497
498    #[test]
499    fn test_generate_blocks_connect_sequentially() {
500        // Verify that each block's prev_block_hash points to the previous tip.
501        let genesis = regtest_genesis();
502        let genesis_hash = genesis.block_hash();
503        let params = ConsensusParams::regtest();
504        let mut chain = ChainState::new(genesis, params).unwrap();
505
506        let hashes = generate_blocks(&mut chain, 5, &Script::new()).unwrap();
507
508        // First block should point to genesis.
509        let b1 = chain.get_block(&hashes[0]).unwrap();
510        assert_eq!(b1.header.prev_block_hash, genesis_hash);
511
512        // Each subsequent block should point to the previous one.
513        for i in 1..5 {
514            let blk = chain.get_block(&hashes[i]).unwrap();
515            assert_eq!(blk.header.prev_block_hash, hashes[i - 1]);
516        }
517    }
518
519    // ── Signet signing tests ─────────────────────────────────────
520
521    #[test]
522    fn test_sign_signet_block_p2wpkh() {
523        use abtc_domain::consensus::signet::{build_signet_commitment, validate_signet_block};
524        use abtc_domain::crypto::hashing::hash160;
525        use abtc_domain::script::Witness;
526
527        // Generate a P2WPKH challenge from a known secret key
528        let secret_key_bytes: [u8; 32] = [0x42; 32];
529        let secp = abtc_domain::secp256k1::Secp256k1::new();
530        let sk = abtc_domain::secp256k1::SecretKey::from_slice(&secret_key_bytes).unwrap();
531        let pk = abtc_domain::secp256k1::PublicKey::from_secret_key(&secp, &sk);
532        let compressed = pk.serialize();
533        let pkh = hash160(&compressed);
534
535        // P2WPKH challenge: OP_0 <20-byte pubkey hash>
536        let mut challenge_bytes = vec![0x00, 0x14];
537        challenge_bytes.extend_from_slice(&pkh);
538        let challenge = Script::from_bytes(challenge_bytes);
539
540        // Build block template with placeholder signet commitment
541        let placeholder = build_signet_commitment(&Witness::new());
542        let coinbase = Transaction::coinbase(
543            1,
544            build_coinbase_script(1),
545            vec![
546                TxOut::new(Amount::from_sat(5_000_000_000), Script::new()),
547                placeholder,
548            ],
549        );
550        let header = BlockHeader::new(
551            0x20000000,
552            BlockHash::zero(),
553            Hash256::zero(),
554            1231006505,
555            0x207fffff,
556            0,
557        );
558        let mut block = Block::new(header, vec![coinbase]);
559        let merkle = block.compute_merkle_root();
560        block.header.merkle_root = merkle;
561
562        // Sign the block
563        let signed = sign_signet_block_p2wpkh(&block, &challenge, &secret_key_bytes).unwrap();
564
565        // Validate it
566        let result = validate_signet_block(&signed, &challenge);
567        assert!(
568            result.is_ok(),
569            "signed signet block should validate: {:?}",
570            result
571        );
572    }
573
574    // ── Low-level helper tests ─────────────────────────────────
575
576    #[test]
577    fn test_header_prefix_length() {
578        let header = BlockHeader::new(1, BlockHash::zero(), Hash256::zero(), 0, 0, 0);
579        let prefix = header_prefix(&header);
580        assert_eq!(prefix.len(), 76);
581    }
582
583    #[test]
584    fn test_double_sha256_deterministic() {
585        let data = b"hello bitcoin";
586        let h1 = double_sha256(data);
587        let h2 = double_sha256(data);
588        assert_eq!(h1, h2);
589
590        // Different input yields different hash.
591        let h3 = double_sha256(b"different data");
592        assert_ne!(h1, h3);
593    }
594}