1use abtc_domain::consensus::rules;
12use abtc_domain::consensus::ConsensusParams;
13use abtc_domain::crypto::signing::TransactionSignatureChecker;
14use abtc_domain::primitives::{Block, BlockHash, Transaction};
15use abtc_domain::script::{verify_script_with_witness, ScriptFlags};
16use abtc_ports::{
17 BlockStore, BlockTemplateProvider, ChainStateStore, MempoolPort, PeerManager, UtxoEntry,
18};
19use std::sync::Arc;
20
21pub struct BlockchainService {
25 block_store: Arc<dyn BlockStore>,
26 chain_state: Arc<dyn ChainStateStore>,
27 peer_manager: Arc<dyn PeerManager>,
28 consensus_params: ConsensusParams,
30}
31
32impl BlockchainService {
33 pub fn new(
35 block_store: Arc<dyn BlockStore>,
36 chain_state: Arc<dyn ChainStateStore>,
37 peer_manager: Arc<dyn PeerManager>,
38 ) -> Self {
39 Self::with_params(
40 block_store,
41 chain_state,
42 peer_manager,
43 ConsensusParams::mainnet(),
44 )
45 }
46
47 pub fn with_params(
49 block_store: Arc<dyn BlockStore>,
50 chain_state: Arc<dyn ChainStateStore>,
51 peer_manager: Arc<dyn PeerManager>,
52 consensus_params: ConsensusParams,
53 ) -> Self {
54 BlockchainService {
55 block_store,
56 chain_state,
57 peer_manager,
58 consensus_params,
59 }
60 }
61
62 pub async fn validate_and_accept_block(&self, block: &Block) -> Result<(), String> {
72 let block_hash = block.block_hash();
73 tracing::info!("Validating block: {}", block_hash);
74
75 if self
77 .block_store
78 .has_block(&block_hash)
79 .await
80 .map_err(|e| e.to_string())?
81 {
82 return Err("Block already exists".to_string());
83 }
84
85 if !block.has_valid_merkle_root() {
87 return Err("Invalid merkle root".to_string());
88 }
89
90 rules::check_block(block, &self.consensus_params)
92 .map_err(|e| format!("Block validation failed: {}", e))?;
93
94 let (_, current_height) = self
96 .chain_state
97 .get_best_chain_tip()
98 .await
99 .map_err(|e| e.to_string())?;
100 let new_height = current_height + 1;
101
102 let script_flags = ScriptFlags::standard();
104
105 for tx in block.transactions.iter() {
108 if tx.is_coinbase() {
109 continue;
110 }
111
112 for (input_idx, input) in tx.inputs.iter().enumerate() {
113 let utxo = self
115 .chain_state
116 .get_utxo(&input.previous_output.txid, input.previous_output.vout)
117 .await
118 .map_err(|e| e.to_string())?
119 .ok_or_else(|| {
120 format!(
121 "Missing UTXO for input {}:{} in tx {}",
122 input.previous_output.txid,
123 input.previous_output.vout,
124 tx.txid()
125 )
126 })?;
127
128 let script_pubkey = &utxo.output.script_pubkey;
129 let spent_amount = utxo.output.value;
130
131 let checker =
136 if script_pubkey.is_witness_program() || Self::is_p2sh_witness(tx, input_idx) {
137 TransactionSignatureChecker::new_witness_v0(tx, input_idx, spent_amount)
138 } else {
139 TransactionSignatureChecker::new(tx, input_idx, spent_amount)
140 };
141
142 verify_script_with_witness(
143 &input.script_sig,
144 script_pubkey,
145 &input.witness,
146 script_flags,
147 &checker,
148 )
149 .map_err(|e| {
150 format!(
151 "Script verification failed for input {} of tx {}: {:?}",
152 input_idx,
153 tx.txid(),
154 e
155 )
156 })?;
157 }
158 }
159
160 let mut utxo_adds = Vec::new();
162 let mut utxo_removes = Vec::new();
163
164 for (tx_idx, tx) in block.transactions.iter().enumerate() {
165 let txid = tx.txid();
166
167 if !tx.is_coinbase() {
169 for input in &tx.inputs {
170 utxo_removes.push((input.previous_output.txid, input.previous_output.vout));
171 }
172 }
173
174 for (vout, output) in tx.outputs.iter().enumerate() {
176 let entry = UtxoEntry {
177 output: output.clone(),
178 height: new_height,
179 is_coinbase: tx_idx == 0,
180 };
181 utxo_adds.push((txid, vout as u32, entry));
182 }
183 }
184
185 self.chain_state
187 .write_utxo_set(utxo_adds, utxo_removes)
188 .await
189 .map_err(|e| e.to_string())?;
190
191 self.block_store
193 .store_block(block, new_height)
194 .await
195 .map_err(|e| e.to_string())?;
196
197 self.chain_state
199 .write_chain_tip(block_hash, new_height)
200 .await
201 .map_err(|e| e.to_string())?;
202
203 let peer_count = self
205 .peer_manager
206 .broadcast_block(block)
207 .await
208 .map_err(|e| e.to_string())?;
209
210 tracing::info!(
211 "Accepted block {} at height {} (broadcast to {} peers)",
212 block_hash,
213 new_height,
214 peer_count
215 );
216
217 Ok(())
218 }
219
220 pub async fn process_new_transaction(&self, tx: &Transaction) -> Result<(), String> {
227 let txid = tx.txid();
228 tracing::debug!("Processing transaction: {}", txid);
229
230 rules::check_transaction(tx)
232 .map_err(|e| format!("Transaction validation failed: {}", e))?;
233
234 if tx.is_coinbase() {
236 return Err("Cannot submit coinbase transaction to mempool".to_string());
237 }
238
239 for input in &tx.inputs {
241 let has_utxo = self
242 .chain_state
243 .has_utxo(&input.previous_output.txid, input.previous_output.vout)
244 .await
245 .map_err(|e| e.to_string())?;
246
247 if !has_utxo {
248 return Err(format!(
249 "Input {}:{} references non-existent or already-spent UTXO",
250 input.previous_output.txid, input.previous_output.vout
251 ));
252 }
253 }
254
255 let mut total_input_value: i64 = 0;
257 for input in &tx.inputs {
258 let utxo = self
259 .chain_state
260 .get_utxo(&input.previous_output.txid, input.previous_output.vout)
261 .await
262 .map_err(|e| e.to_string())?
263 .ok_or_else(|| "UTXO disappeared during validation".to_string())?;
264
265 total_input_value += utxo.output.value.as_sat();
266 }
267
268 let total_output_value = tx.total_output_value().as_sat();
269 if total_input_value < total_output_value {
270 return Err(format!(
271 "Transaction outputs ({}) exceed inputs ({})",
272 total_output_value, total_input_value
273 ));
274 }
275
276 let fee = total_input_value - total_output_value;
277
278 let script_flags = ScriptFlags::standard();
280
281 for (input_idx, input) in tx.inputs.iter().enumerate() {
282 let utxo = self
283 .chain_state
284 .get_utxo(&input.previous_output.txid, input.previous_output.vout)
285 .await
286 .map_err(|e| e.to_string())?
287 .ok_or_else(|| "UTXO disappeared during script verification".to_string())?;
288
289 let script_pubkey = &utxo.output.script_pubkey;
290 let spent_amount = utxo.output.value;
291
292 let checker =
293 if script_pubkey.is_witness_program() || Self::is_p2sh_witness(tx, input_idx) {
294 TransactionSignatureChecker::new_witness_v0(tx, input_idx, spent_amount)
295 } else {
296 TransactionSignatureChecker::new(tx, input_idx, spent_amount)
297 };
298
299 verify_script_with_witness(
300 &input.script_sig,
301 script_pubkey,
302 &input.witness,
303 script_flags,
304 &checker,
305 )
306 .map_err(|e| {
307 format!(
308 "Script verification failed for input {} of tx {}: {:?}",
309 input_idx, txid, e
310 )
311 })?;
312 }
313
314 tracing::debug!("Transaction {} validated (fee: {} satoshis)", txid, fee);
315
316 Ok(())
317 }
318
319 fn is_p2sh_witness(tx: &Transaction, input_idx: usize) -> bool {
326 let script_sig = &tx.inputs[input_idx].script_sig;
327 let bytes = script_sig.as_bytes();
328
329 if bytes.is_empty() {
333 return false;
334 }
335
336 #[allow(clippy::if_same_then_else)]
340 let inner = if bytes.len() == 23 && bytes[0] == 0x16 {
341 &bytes[1..]
342 } else if bytes.len() == 35 && bytes[0] == 0x22 {
343 &bytes[1..]
344 } else {
345 return false;
346 };
347
348 if inner.len() >= 2 {
352 let version_byte = inner[0];
353 let push_len = inner[1] as usize;
354 let is_valid_version =
355 version_byte == 0x00 || (0x51..=0x60).contains(&version_byte);
356 let is_valid_program =
357 (push_len == 20 || push_len == 32) && inner.len() == 2 + push_len;
358 return is_valid_version && is_valid_program;
359 }
360
361 false
362 }
363
364 pub async fn get_chain_info(&self) -> Result<ChainInfo, String> {
366 let (best_block_hash, height) = self
367 .chain_state
368 .get_best_chain_tip()
369 .await
370 .map_err(|e| e.to_string())?;
371
372 Ok(ChainInfo {
373 best_block_hash,
374 height,
375 blocks: height + 1,
376 })
377 }
378
379 pub async fn get_block(&self, hash: &BlockHash) -> Result<Option<Block>, String> {
381 self.block_store
382 .get_block(hash)
383 .await
384 .map_err(|e| e.to_string())
385 }
386}
387
388pub struct MempoolService {
390 mempool: Arc<dyn MempoolPort>,
391 _chain_state: Arc<dyn ChainStateStore>,
393}
394
395impl MempoolService {
396 pub fn new(mempool: Arc<dyn MempoolPort>, chain_state: Arc<dyn ChainStateStore>) -> Self {
398 MempoolService {
399 mempool,
400 _chain_state: chain_state,
401 }
402 }
403
404 pub async fn submit_transaction(&self, tx: &Transaction) -> Result<String, String> {
406 tracing::info!("Submitting transaction: {}", tx.txid());
407
408 self.mempool
409 .add_transaction(tx)
410 .await
411 .map_err(|e| e.to_string())?;
412
413 Ok(tx.txid().to_hex_reversed())
414 }
415
416 pub async fn get_mempool_contents(&self) -> Result<Vec<String>, String> {
418 let txs = self
419 .mempool
420 .get_all_transactions()
421 .await
422 .map_err(|e| e.to_string())?;
423
424 Ok(txs
425 .iter()
426 .map(|entry| entry.tx.txid().to_hex_reversed())
427 .collect())
428 }
429
430 pub async fn estimate_fee(&self, target_blocks: u32) -> Result<f64, String> {
432 self.mempool
433 .estimate_fee(target_blocks)
434 .await
435 .map_err(|e| e.to_string())
436 }
437
438 pub async fn get_mempool_info(&self) -> Result<abtc_ports::MempoolInfo, String> {
440 self.mempool
441 .get_mempool_info()
442 .await
443 .map_err(|e| e.to_string())
444 }
445
446 pub async fn get_mempool_entry(
448 &self,
449 txid: &abtc_domain::primitives::Txid,
450 ) -> Option<abtc_ports::MempoolEntry> {
451 self.mempool.get_transaction(txid).await.ok().flatten()
452 }
453}
454
455pub struct MiningService {
457 template_provider: Arc<dyn BlockTemplateProvider>,
458 blockchain: Arc<BlockchainService>,
459 consensus_params: ConsensusParams,
461}
462
463impl MiningService {
464 pub fn new(
466 template_provider: Arc<dyn BlockTemplateProvider>,
467 blockchain: Arc<BlockchainService>,
468 ) -> Self {
469 Self::with_params(template_provider, blockchain, ConsensusParams::mainnet())
470 }
471
472 pub fn with_params(
474 template_provider: Arc<dyn BlockTemplateProvider>,
475 blockchain: Arc<BlockchainService>,
476 consensus_params: ConsensusParams,
477 ) -> Self {
478 MiningService {
479 template_provider,
480 blockchain,
481 consensus_params,
482 }
483 }
484
485 pub async fn generate_block_template(
487 &self,
488 coinbase_script: &abtc_domain::script::Script,
489 ) -> Result<abtc_ports::BlockTemplate, String> {
490 self.template_provider
491 .create_block_template(coinbase_script, &self.consensus_params)
492 .await
493 .map_err(|e| e.to_string())
494 }
495
496 pub async fn submit_mined_block(&self, block: &Block) -> Result<(), String> {
498 self.blockchain.validate_and_accept_block(block).await
499 }
500}
501
502#[derive(Clone, Debug)]
504pub struct ChainInfo {
505 pub best_block_hash: BlockHash,
507 pub height: u32,
509 pub blocks: u32,
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn test_chain_info_creation() {
519 let info = ChainInfo {
520 best_block_hash: BlockHash::zero(),
521 height: 0,
522 blocks: 1,
523 };
524 assert_eq!(info.height, 0);
525 }
526}