Skip to content

Implement get_headers as bounded selected-parent-chain traversal#993

Open
demisrael wants to merge 2 commits into
kaspanet:masterfrom
demisrael:kas-806-get-headers-rpc
Open

Implement get_headers as bounded selected-parent-chain traversal#993
demisrael wants to merge 2 commits into
kaspanet:masterfrom
demisrael:kas-806-get-headers-rpc

Conversation

@demisrael
Copy link
Copy Markdown
Contributor

Closes #806.

RpcApi::get_headers (gRPC getHeaders, wRPC getHeaders, WASM getHeaders)
previously returned NotImplemented. The endpoint is now implemented as a
bounded selected-parent-chain traversal using existing consensus read APIs.
The endpoint shape (start_hash, limit, is_ascending) is preserved, and
no new external dependencies are introduced.

Behavior

  • is_ascending = true — walks the selected parent chain from start_hash
    toward the current sink.
  • is_ascending = false — walks from start_hash toward genesis.
  • limit counts returned headers and is inclusive of start_hash when
    limit > 0. limit = 0 returns an empty headers vector without
    touching consensus iterators beyond start-hash validation.
  • start_hash must be on the selected parent chain. Unknown hashes and
    off-chain hashes return typed RPC errors and the request is rejected
    before any unbounded allocation.
  • The safe-RPC window cap is enforced before consensus reads, so a large
    u64 limit cannot allocate an unbounded vector.

Public API additions

Two new public consensus helpers carry the selected-parent-chain contract on
their doc comments and are reused by the RPC service:

  • ConsensusApi::get_virtual_selected_chain_from
  • ConsensusSessionOwned::async_get_virtual_selected_chain_from

Public Rust, proto, and WASM doc comments for get_headers / getHeaders
are updated to describe the selected-parent-chain direction and inclusive
limit semantics.

gRPC wire compatibility

GetHeadersResponseMessage adds an additive blockHeaders = 2 field
carrying full RpcBlockHeader records. The legacy headers = 1
hash-string field is preserved. The decoder:

  • accepts a payload with only blockHeaders set;
  • accepts a payload with both fields set when every legacy hash matches its
    full-header hash;
  • rejects a hash-only payload (which would previously have decoded to an
    empty success response);
  • rejects length and hash mismatches between the two fields;
  • preserves precedence of explicit error payloads over both fields.

wRPC and WASM require no transport-side wiring changes: both already
dispatch GetHeaders through the core RpcApi trait, so implementing
RpcCoreService::get_headers_call and updating the gRPC converter is
sufficient to make all three transports functional. The WASM diff is
limited to doc-comment updates on the existing binding.

Interactive CLI (kaspa-cli)

The second commit (b2b3c772) wires the existing GetHeaders RPC into
the interactive kaspa-cli rpc command surface so operators can drive
the endpoint against a running node:

rpc get-headers <start_hash> <limit> [ascending|descending|asc|desc|true|false|1|0]
  • Direction defaults to ascending when omitted.
  • start_hash and limit use the existing RpcHash and u64 parsers.
  • The CLI reuses the existing rpc.get_headers_call(...) request shape
    and prints the returned GetHeadersResponse (header records).
  • Unit tests in cli/src/modules/rpc.rs cover the direction parser:
    default behavior, accepted aliases, and rejection of unknown values.

No RPC wire fields, op IDs, or other CLI command surfaces were changed.

Tests

  • testing/integration::rpc_tests::sanity_test — the GetHeaders arm now
    asserts a successful response instead of asserting NotImplemented.
  • testing/integration::rpc_tests::get_headers_selected_chain_test — new;
    exercises ascending, descending, limit = 0, limit = 1, sink-rooted
    ascending, genesis-rooted ascending, unknown start_hash, and off-chain
    start_hash error paths against a mined simnet.
  • gRPC converter unit tests in rpc/grpc/core/src/convert/message.rs
    new; cover empty payload acceptance, hash-only rejection, matching
    mixed-payload success, length mismatch, hash mismatch, and error
    precedence.
  • CLI unit tests in cli/src/modules/rpc.rs — direction parser default,
    alias acceptance, and unknown-value rejection.

Complexity

  • Time: O(min(limit, selected-chain distance to endpoint)) consensus
    reads. No broad DAG traversal.
  • Memory: O(returned headers). No allocation based directly on an
    unchecked u64 request limit.

Verification

  • cargo nextest run --workspace — PASS (full workspace)
  • cargo test --doc --workspace — PASS (doctests)
  • cargo nextest run -p kaspa-testing-integration rpc_tests::sanity_test — PASS
  • cargo nextest run -p kaspa-testing-integration rpc_tests::get_headers_selected_chain_test — PASS
  • cargo test -p kaspa-grpc-core get_headers_response — PASS (focused gRPC converter tests)
  • cargo check --tests --workspace --benches — PASS
  • cargo build --workspace --tests --benches — PASS
  • cargo fmt --all -- --check — PASS
  • cargo +1.93.0 clippy --workspace --tests --benches --examples -- -D warnings — PASS
  • RUSTUP_TOOLCHAIN=1.93.0 ./check — PASS

Live-node CLI smoke (interactive kaspa-cli)

Validated against a running testnet-10 node over wRPC:

  • rpc get-headers <genesis> 0 ascending — empty headers vector.
  • rpc get-headers <genesis> 1 ascending — single header (genesis).
  • rpc get-headers <genesis> 2 ascending — genesis + next selected-chain header.
  • rpc get-headers <sink> N descending — sink first, walking selected parents.
  • rpc get-headers <unknown-hash> N ascending — typed RPC error
    (cannot find header ...), no panic.
  • Oversized request beyond the safe-RPC window — typed safe-mode rejection
    before any unbounded allocation.

Toolchain note

Clippy verification is on the project's pinned Rust 1.93.0 toolchain. Under
stable ≥ 1.95 there is an unrelated clippy::explicit-counter-loop lint in
crypto/merkle/src/lib.rs:14, outside this diff; the pinned 1.93.0
toolchain (the project's CI gate) is clean.

Out of scope

  • Removing get_headers / getHeaders from public APIs.
  • Broad DAG parent/children discovery behind the existing three-field
    request (would require an API redesign).
  • Changes to GetHeadersRequest fields or RpcApiOps / KaspadPayloadOps
    numbering.
  • Consensus header storage, pruning rules, GHOSTDAG selection, or p2p
    header-sync semantics.

demisrael added 2 commits May 10, 2026 10:21
…versal

Replace the NotImplemented stub with a bounded selected-parent-chain
walk over existing consensus reads. The request shape (start_hash,
limit, is_ascending) is preserved. GetHeadersResponseMessage gains an
additive blockHeaders field carrying full header records; the legacy
headers hash-string field is retained for wire compatibility.

Closes kaspanet#806

Signed-off-by: Dmitry Perchanov <demisrael@gmail.com>
Wire the existing GetHeaders RPC into the interactive `kaspa-cli rpc`
surface so operators can exercise the endpoint against a running node.
The command takes a start hash, a limit, and an optional direction
(ascending by default; descending alias accepted), and prints the
returned header records.

Add unit coverage for the direction parser: default, accepted aliases,
and rejection of unknown values.

Signed-off-by: Dmitry Perchanov <demisrael@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rpc: get headers isn't implemented

1 participant