1use 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#[derive(Debug)]
31pub enum MiningError {
32 NonceExhausted,
34 ChainError(ChainStateError),
36 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
60pub fn mine_block(mut block: Block) -> Result<Block, MiningError> {
80 let target_u256 = decode_compact_u256(block.header.bits);
81
82 if target_u256 == [0xff; 32] {
84 return Ok(block);
85 }
86
87 let target_u128 = decode_compact(block.header.bits);
90 if target_u128 == u128::MAX {
91 return Ok(block);
92 }
93
94 let prefix = header_prefix(&block.header);
96
97 for nonce in 0..=u32::MAX {
98 let mut header_bytes = prefix.clone();
100 header_bytes.extend_from_slice(&nonce.to_le_bytes());
101
102 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
114pub 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
152fn 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 let subsidy = get_block_subsidy(height, params);
164
165 let coinbase_scriptsig = build_coinbase_script(height);
168
169 let coinbase = Transaction::coinbase(
171 height,
172 coinbase_scriptsig,
173 vec![TxOut::new(subsidy, coinbase_output_script.clone())],
174 );
175
176 let time = std::time::SystemTime::now()
178 .duration_since(std::time::UNIX_EPOCH)
179 .unwrap_or_default()
180 .as_secs() as u32;
181
182 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 let header = BlockHeader::new(0x20000000, tip, merkle_root, time, bits, 0);
191 Block::new(header, vec![coinbase])
192}
193
194fn 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
214fn build_coinbase_script(height: u32) -> Script {
229 let mut script = Vec::new();
230
231 if height == 0 {
232 script.push(0x00);
234 } else if height <= 16 {
235 script.push(0x50 + height as u8);
237 } else {
238 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 buf.last().is_some_and(|&b| b & 0x80 != 0) {
247 buf.push(0);
248 }
249 script.push(buf.len() as u8); script.extend_from_slice(&buf);
251 }
252
253 while script.len() < 2 {
255 script.push(0x00); }
257
258 Script::from_bytes(script)
259}
260
261fn 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
274fn 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
283pub 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#[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 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, 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 #[test]
344 fn test_subsidy_initial() {
345 let params = ConsensusParams::mainnet();
346 assert_eq!(get_block_subsidy(0, ¶ms).as_sat(), 5_000_000_000);
347 assert_eq!(get_block_subsidy(1, ¶ms).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, ¶ms).as_sat(), 2_500_000_000);
354 assert_eq!(get_block_subsidy(420_000, ¶ms).as_sat(), 1_250_000_000);
355 assert_eq!(get_block_subsidy(630_000, ¶ms).as_sat(), 625_000_000);
356 }
357
358 #[test]
359 fn test_subsidy_regtest() {
360 let params = ConsensusParams::regtest();
361 assert_eq!(get_block_subsidy(0, ¶ms).as_sat(), 5_000_000_000);
363 assert_eq!(get_block_subsidy(149, ¶ms).as_sat(), 5_000_000_000);
364 assert_eq!(get_block_subsidy(150, ¶ms).as_sat(), 2_500_000_000);
365 assert_eq!(get_block_subsidy(300, ¶ms).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, ¶ms).as_sat(), 0);
372 }
373
374 #[test]
377 fn test_mine_block_regtest() {
378 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); }
403
404 #[test]
405 fn test_mine_block_low_difficulty() {
406 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 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 #[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 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 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 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 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 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 let b1 = chain.get_block(&hashes[0]).unwrap();
510 assert_eq!(b1.header.prev_block_hash, genesis_hash);
511
512 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 #[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 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 let mut challenge_bytes = vec![0x00, 0x14];
537 challenge_bytes.extend_from_slice(&pkh);
538 let challenge = Script::from_bytes(challenge_bytes);
539
540 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 let signed = sign_signet_block_p2wpkh(&block, &challenge, &secret_key_bytes).unwrap();
564
565 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 #[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 let h3 = double_sha256(b"different data");
592 assert_ne!(h1, h3);
593 }
594}