# Class BSV::Network::SpvHeaderChainTracker <a id="class-BSV-Network-SpvHeaderChainTracker"></a>

**Inherits:** `BSV::Transaction::ChainTracker`

PoW-validated chain tracker for the opt-in `spv_headers` trust model (HLR
#335).

The sibling {ChainTracker} trusts a chain-query Service's answer for a merkle
root outright. This tracker trusts **no** service answer on its face: it
extends a locally-validated header chain from a baked-in {Checkpoints
checkpoint} via {HeaderSyncer} — every header PoW-checked and linked to its
predecessor before it is believed — and answers merkle-root questions only
from that validated chain. The trust surface is the one checkpoint header;
everything else is verified.

Satisfies the SDK's {Transaction::ChainTracker} duck type so it drops into
+Transaction::Tx#verify+ exactly where {ChainTracker} does. It is selected at
boot when <code>config.trust_model</code> is <code>:spv_headers</code>;
otherwise the trusted-service {ChainTracker} is used and behaviour is
unchanged.

## Fail-closed

Matches the sibling's posture: any error, any height below the checkpoint, and
any height the sync could not reach all resolve to `false` — verification
fails rather than passing on data the wallet could not validate.

## Constants
### `MATURITY_HEADROOM` <a id="constant-MATURITY_HEADROOM"></a> <a id="MATURITY_HEADROOM-constant"></a>
Headroom synced above a queried height so the SDK's coinbase maturity check
(an offset-0 leaf must sit ≥ 100 blocks below the tip) is always satisfied for
the leaves being verified. {#current_height} returns the validated tip, so
syncing this far past the leaf keeps the tip ≥ 100 above it.

## Public Instance Methods
### `current_height()` <a id="method-i-current_height"></a> <a id="current_height-instance_method"></a>
The current chain height: the validated tip from the store.

Deliberately NOT the <code>:current_height</code> Services call — that asks an
untrusted service for the tip, which this model does not believe. The
+{MATURITY_HEADROOM}+-block over-sync in {#valid_root_for_height?} guarantees
the tip sits far enough above any leaf being verified for the SDK's
coinbase-maturity check.
- **@return** [Integer]

### `initialize(store:, services:, network: = :mainnet, checkpoint: = nil)` <a id="method-i-initialize"></a> <a id="initialize-instance_method"></a>
- **@param** `store` [BSV::Wallet::Store] block-header persistence
- **@param** `services` [BSV::Network::Services] chain-query routing layer
- **@param** `network` [Symbol] +:mainnet+ — selects the default checkpoint
- **@param** `checkpoint` [Hash, nil] explicit +{ height:, header: }+
override (+config.spv_checkpoint+); +nil+ → {Checkpoints.for}.
- **@return** [SpvHeaderChainTracker] a new instance of SpvHeaderChainTracker

### `known_roots_for_heights(heights)` <a id="method-i-known_roots_for_heights"></a> <a id="known_roots_for_heights-instance_method"></a>
Batched merkle-root lookup for anchor liveness (HLR #516 Sub 6).

Returns +{ height => root_bytes | nil }+ — one entry per input height. Values
are wire-order 32-byte binary bytes drawn from the locally-validated header
chain. `nil` for heights outside the validated range (below the checkpoint,
above the tip the sync could reach, or reachable only via a failed sync) — the
caller must not conflate "unknown" with "mismatch".

Empty input short-circuits: no sync, empty Hash returned.

Extends the validated chain up to +max(height) + MATURITY_HEADROOM+ once, so a
batch of nearby heights costs one sync rather than one per height.
- **@param** `heights` [Array<Integer>]
- **@return** [Hash{Integer => String, nil}]

### `valid_root_for_height?(root, height)` <a id="method-i-valid_root_for_height-3F"></a> <a id="valid_root_for_height?-instance_method"></a>
Verify that a merkle root is valid for the given block height, against the
locally-validated header chain.

Fail-closed below the checkpoint (the chain has no validated header there to
check against). Otherwise extends the validated chain to +height +
{MATURITY_HEADROOM}+ (keeping the tip far enough above the leaf for the SDK's
coinbase-maturity check), then compares the stored header's merkle_root at
`height` to the request. A height the sync could not reach is "not covered" →
`false`.
- **@param** `root` [String] merkle root as display-order hex (the SDK's
+MerklePath#compute_root_hex+ output)
- **@param** `height` [Integer] block height
- **@return** [Boolean]
