Skip to content

Commit db4bac8

Browse files
committed
feat(node): block-import skeleton
Declare the import_block(state, bytes) -> ImportOutcome contract. V1 is decode-only: malformed bytes error; well-formed bytes return ImportOutcome { hash, tx_count, applied: false }. Follow-up turns wire consensus validation, UTXO commit, chain tip advance, index / filter / coinstats updates, and RPC long-poll wake against this entry point. A unit test exercises the skeleton against the regtest genesis block. Op: extend
1 parent 51d4820 commit db4bac8

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

crates/node/src/import.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
}

crates/node/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub mod config;
1616
pub mod crash_recovery;
1717
/// Central synchronous event loop.
1818
pub mod event_loop;
19+
/// Block import pipeline.
20+
pub mod import;
1921
/// Tracing initialization.
2022
pub mod logging;
2123
/// Metrics instrumentation and optional exposition.

0 commit comments

Comments
 (0)