Skip to main content

urma_runtime/p2p/
bootstrap.rs

1use super::headers::prove_batch;
2use crate::config::P2P_MEDIAN_TIME_SPAN;
3use crate::error::{Error, ensure};
4use bitcoin::block::Header;
5use urma_chain::observation::Chain;
6
7/// Explicit, checkpoint-relative bootstrap with sufficient preceding history
8/// for difficulty and median-time checks from the first successor onwards.
9///
10/// The operator trusts the anchor's chain membership and height. Its hash binds
11/// the ancestor bytes; their individual PoW is also checked. This is not a
12/// contextual consensus validation of history before the anchor, nor evidence
13/// that the supplied chain has the greatest work on the public network.
14/// Ancestors provide verification context, not an earlier selectable tip or
15/// application scan coverage. No successor hash is trusted or bypassed.
16#[derive(Clone, Debug)]
17pub struct HeaderBootstrap {
18    chain: Chain,
19    height: u64,
20    header: Header,
21    ancestors: Vec<Header>,
22}
23
24impl HeaderBootstrap {
25    /// `ancestors` contains exactly `min(height, context_headers(chain))`
26    /// headers, oldest first, ending immediately before `header`.
27    /// Validation completes before the value can be used to open any cache.
28    pub fn new(
29        chain: Chain,
30        height: u64,
31        header: Header,
32        ancestors: Vec<Header>,
33    ) -> Result<Self, Error> {
34        let count = context_headers(chain);
35        let expected = usize::try_from(height.min(u64::try_from(count)?))?;
36        ensure!(
37            ancestors.len() == expected,
38            "bootstrap needs exactly {expected} ancestors"
39        );
40        let oldest = ancestors.first().unwrap_or(&header);
41        if height == u64::try_from(ancestors.len())? {
42            ensure!(
43                oldest.block_hash() == chain.genesis()?.0,
44                "bootstrap prefix does not start at the configured chain genesis"
45            );
46        }
47        for (previous, next) in ancestors
48            .iter()
49            .zip(ancestors.iter().skip(1).chain(std::iter::once(&header)))
50        {
51            ensure!(
52                next.prev_blockhash == previous.block_hash(),
53                "bootstrap ancestors do not connect to the trusted anchor"
54            );
55        }
56        prove_batch(chain, &ancestors)?;
57        prove_batch(chain, std::slice::from_ref(&header))?;
58        Ok(Self {
59            chain,
60            height,
61            header,
62            ancestors,
63        })
64    }
65
66    pub fn chain(&self) -> Chain {
67        self.chain
68    }
69    pub fn height(&self) -> u64 {
70        self.height
71    }
72    pub fn header(&self) -> &Header {
73        &self.header
74    }
75    pub fn ancestors(&self) -> &[Header] {
76        &self.ancestors
77    }
78
79    pub(super) fn into_parts(self) -> (Chain, u64, Header, Vec<Header>) {
80        (self.chain, self.height, self.header, self.ancestors)
81    }
82}
83
84/// Ancestors before the anchor, excluding the anchor itself. The extra anchor
85/// yields lookback+1 preceding headers and the full median-time window.
86pub fn context_headers(chain: Chain) -> usize {
87    // All supported chain parameters fit usize on supported platforms.
88    // Use the interval as well so minimum-difficulty lookback cannot truncate
89    // before the last period boundary if those parameters later diverge.
90    chain
91        .params()
92        .retarget_lookback
93        .max(chain.params().retarget_interval - 1)
94        .max((P2P_MEDIAN_TIME_SPAN - 1) as u64) as usize
95}