|
| 1 | +//! Block import pipeline (skeleton). |
| 2 | +//! |
| 3 | +//! The real pipeline lands as follow-up turns wire P2P → download → |
| 4 | +//! decode → consensus validation → UTXO commit → chain tip advance |
| 5 | +//! → index / filter / coinstats updates → RPC long-poll wake. This |
| 6 | +//! file declares the contract those commits fill in. |
| 7 | +
|
| 8 | +use anyhow::{Context as _, Result}; |
| 9 | +use bitcoin::Block; |
| 10 | +use bitcoin::consensus::Decodable as _; |
| 11 | +use bitcoin::hashes::Hash as _; |
| 12 | +use bitcoin_rs_primitives::Hash256; |
| 13 | + |
| 14 | +use crate::state::NodeState; |
| 15 | + |
| 16 | +/// Outcome of importing one block. |
| 17 | +#[derive(Clone, Debug, PartialEq, Eq)] |
| 18 | +pub struct ImportOutcome { |
| 19 | + /// Block hash in canonical little-endian form. |
| 20 | + pub hash: Hash256, |
| 21 | + /// Number of transactions in the block. |
| 22 | + pub tx_count: usize, |
| 23 | + /// Whether the block was applied to the active chain. |
| 24 | + /// |
| 25 | + /// In the v1 skeleton this is always `false` — the validation |
| 26 | + /// pipeline is not yet wired. Future commits flip it to `true` |
| 27 | + /// once UTXO commit + chain tip advance succeed. |
| 28 | + pub applied: bool, |
| 29 | +} |
| 30 | + |
| 31 | +/// Decodes `block_bytes` and returns the synthetic outcome. |
| 32 | +/// |
| 33 | +/// V1 contract: decode-only. Returns an error if the bytes are |
| 34 | +/// malformed. Returns `ImportOutcome { applied: false, ... }` on |
| 35 | +/// successful decode. |
| 36 | +pub fn import_block(_state: &NodeState, block_bytes: &[u8]) -> Result<ImportOutcome> { |
| 37 | + let mut cursor = std::io::Cursor::new(block_bytes); |
| 38 | + let block = Block::consensus_decode(&mut cursor) |
| 39 | + .with_context(|| format!("decode block ({} bytes)", block_bytes.len()))?; |
| 40 | + let block_hash = block.block_hash(); |
| 41 | + let hash = Hash256::from_le_bytes(block_hash.as_byte_array()); |
| 42 | + Ok(ImportOutcome { |
| 43 | + hash, |
| 44 | + tx_count: block.txdata.len(), |
| 45 | + applied: false, |
| 46 | + }) |
| 47 | +} |
| 48 | + |
| 49 | +#[cfg(test)] |
| 50 | +mod tests { |
| 51 | + use super::*; |
| 52 | + use tempfile::tempdir; |
| 53 | + |
| 54 | + #[test] |
| 55 | + fn import_decodes_a_well_formed_block() -> Result<()> { |
| 56 | + // Smallest valid Bitcoin block: regtest genesis. |
| 57 | + let block_hex = "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000"; |
| 58 | + let bytes = hex_decode(block_hex)?; |
| 59 | + let dir = tempdir()?; |
| 60 | + let mut config = crate::Config::default_for_network(crate::Network::Regtest); |
| 61 | + config.data_dir = dir.path().join("node"); |
| 62 | + config.p2p_listen.clear(); |
| 63 | + let state = NodeState::open(config)?; |
| 64 | + let outcome = import_block(&state, &bytes)?; |
| 65 | + assert_eq!(outcome.tx_count, 1, "genesis has one transaction"); |
| 66 | + assert!(!outcome.applied, "v1 skeleton must not apply blocks"); |
| 67 | + Ok(()) |
| 68 | + } |
| 69 | + |
| 70 | + fn hex_decode(hex: &str) -> Result<Vec<u8>> { |
| 71 | + let mut bytes = Vec::with_capacity(hex.len() / 2); |
| 72 | + let chars: Vec<char> = hex.chars().collect(); |
| 73 | + for pair in chars.chunks(2) { |
| 74 | + let high = pair[0] |
| 75 | + .to_digit(16) |
| 76 | + .with_context(|| format!("non-hex char {}", pair[0]))?; |
| 77 | + let low = pair[1] |
| 78 | + .to_digit(16) |
| 79 | + .with_context(|| format!("non-hex char {}", pair[1]))?; |
| 80 | + bytes.push( |
| 81 | + u8::try_from((high << 4) | low).with_context(|| "hex value out of u8 range")?, |
| 82 | + ); |
| 83 | + } |
| 84 | + Ok(bytes) |
| 85 | + } |
| 86 | +} |
0 commit comments