Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Store Handlers and Verification Tests

Crate: kinetic-network Stage: 8 Reading time: 20 minutes Depends on: 16_store_core.md, 15_store_verification.md, kinetic-core, kinetic-types, kinetic-kid


What Is This?

This document provides an exhaustive explanation of the test suites that validate the behavior of the Kinetic network’s localized storage handlers and data verification logic.

Specifically, it covers the test cases found in handlers_tests.rs and verification_tests.rs within the store module of the kinetic-network crate.

These test suites are meticulously designed to simulate and assert the correct processing of network primitives under various conditions.

The primitives tested include:

  • Reveals (namespace claims)
  • Heartbeats (liveness signals)
  • Host routing updates
  • Decentralized identity (KID) authorizations

The tests simulate various edge cases, time-shifting scenarios, and active attack vectors.

They serve as the executable proof that the theoretical security boundaries designed into the Kinetic network actually function correctly in the compiled Rust binaries.


Why Kinetic Needs This

In a decentralized peer-to-peer network like Kinetic, nodes operate in an environment of zero inherent trust.

Nodes cannot blindly trust the incoming data they receive from other peers.

Every piece of data traversing the gossipsub network or the Kademlia DHT must be rigorously validated.

It must be scrutinized.

It must be bounds-checked before it is ever committed to local persistent storage.

If the handlers and verification logic have bugs, or if they behave unexpectedly under load, the network becomes immediately vulnerable to several critical, systemic failures:

  1. Spam and Resource Exhaustion (Denial of Service):

    • Without strict rate limiting on incoming reveals, an attacker could trivially flood the network.
    • They could send valid but excessive name registrations.
    • Because evaluating a reveal requires verifying a Verifiable Delay Function (VDF) proof—a computationally expensive operation—and then writing it to the Sled database, a flood would bloat the storage of all nodes.
    • It would force them to expend all available CPU cycles.
    • This would effectively halt the network.
    • The rate-limiting tests ensure this theoretical defense is practically enforced.
  2. Replay Attacks and State Reversion:

    • If heartbeats are not monotonic, a malicious peer could capture an old, valid heartbeat from the network traffic.
    • They could replay it hours or days later.
    • This could falsely keep a name alive that should have expired.
    • Or worse, it could overwrite a newer, legitimate routing state with an obsolete one.
    • The heartbeat monotonicity tests ensure time only flows forward.
  3. Routing Poisoning and Topology Disruption:

    • If host routing records are accepted without strict freshness checks relative to the current Drand pulse, the DHT becomes poisoned.
    • If malformed PeerId formats cause the Rust runtime to panic, an attacker could either misdirect network traffic to malicious nodes or perform a denial-of-service attack.
    • A single malformed identity key could crash every node it touches.
    • The tests prove that the network handles toxic data gracefully.
  4. Post-Quantum Cryptography Integration Failures:

    • The Kinetic network relies on ML-DSA (Module-Lattice-Based Digital Signature Algorithm) for future-proof security.
    • If the authorization logic fails to properly parse ML-DSA signatures, the post-quantum security guarantee of the network collapses.
    • If it fails to base64-decode the controller keys embedded within KID documents, it collapses.
    • The tests act as a live rehearsal of this complex cryptographic chain.

These test files are not just checking for typos.

They are the cryptographic and logical firewall for the node’s local database.

They ensure that Saif’s architectural rules—such as the maximum age of a pulse, the sliding window of rate limits, and the exact byte-layout of a signed identity payload—are correctly enforced in the living codebase.


How It Works

The test suites break down into two main domains:

  • Testing the internal state mutations of the store (handlers_tests.rs)
  • Testing the stateless cryptographic and logical boundary checks (verification_tests.rs).

We will explore both in deep, step-by-step detail.

1. Store Instantiation and Mocking (The Sandbox)

Before any tests can run that mutate state, the test environment must synthesize a fully functioning KineticRecordStore.

This involves wiring together several heavy dependencies that normally run in a live node.

They must be kept lightweight and isolated enough for a unit test.

-> See: crates/kinetic-network/src/store/handlers_tests.rs — Lines 30 to 51

The setup_store function acts as the central dependency injector for the test suite.

It constructs the sandbox with the following steps:

  • Ephemeral Storage:
    • It creates a temporary directory using tempfile::tempdir().
    • The SledStorage database is initialized here.
    • This is crucial because it ensures the database starts empty for every test.
    • It is automatically deleted from the operating system when the test variable goes out of scope.
    • No state bleeds between tests.
  • Identity Mocking:
    • It generates a random ed25519 PeerId.
    • This acts as the core identity of the mock store.
    • In a real node, this comes from the local keystore.
  • VDF Engine Injection:
    • It instantiates a ChiaVdfEngine.
    • Even though tests might use dummy proofs, the store’s constructor requires an object that implements the VdfEngine trait.
  • Initial State Seeding:
    • It sets the initial Drand pulse (the drand_kyn) to a baseline integer of 100.
    • This provides a known starting point for time-relative tests.
  • Limit Configuration:
    • It configures the maximum reveals allowed per window (passed in as an argument, e.g., 5).

This function returns both the initialized KineticRecordStore and an Arc pointer to the underlying storage engine.

This allows tests to manipulate the store exactly as the network handlers would.

2. Testing Rate Limiting (The Sliding Time Window)

To prevent network flooding, the store limits how many reveals a specific namespace can process within a rolling time window.

For example, a maximum of 5 reveals per rolling hour.

-> See: crates/kinetic-network/src/store/handlers_tests.rs — Lines 53 to 111

The test test_rate_limiting verifies the logic of this sliding window algorithm.

It bypasses the network layer and directly manipulates the store’s internal memory structures:

  • The store tracks reveal timestamps in a std::collections::VecDeque.
  • This is a double-ended queue.
  • It is mapped to each namespace string inside a hash map (accepted_reveals_timestamps).
  • The test creates a dummy namespace domain0.kinetic.
  • It artificially injects 5 timestamps representing recent reveals.
  • This maxes out the theoretical limit.
  • To simulate the passage of time without actually pausing the test thread for an hour, the test manually clears the queue.
  • It then manually injects specific historical timestamps.
  • It calculates now = web_time::Instant::now().
  • It injects one timestamp from now - 4000 seconds.
  • This is older than the 3600-second (1-hour) limit, so it should be considered expired.
  • It injects a second timestamp from now - 3000 seconds.
  • This is within the 1-hour window, so it is active.
  • It then injects 5 more timestamps that occurred exactly at now.
  • The core logic is then executed: the code iterates over the front of the VecDeque.
  • If the time elapsed since a timestamp is greater than 3600 seconds, it uses deque.pop_front() to discard it.
  • Finally, the assertion checks that after pruning, exactly 6 timestamps remain in the queue.
  • The 6 timestamps are the 1 active historical one from 3000 seconds ago, plus the 5 recent ones at now.
  • The 4000-second old timestamp is gone.
  • This confirms that expired records are evicted correctly.
  • The sliding window moves forward.
  • The node frees up memory over time.

3. Testing Heartbeat Monotonicity (Defeating Replay Attacks)

Heartbeats are how nodes prove they are currently online and actively participating in maintaining a name.

They are cryptographically bound to a specific Drand pulse (drand_kyn).

Time in Kinetic is measured in these pulses.

-> See: crates/kinetic-network/src/store/handlers_tests.rs — Lines 113 to 151

The test_heartbeat_monotonicity test ensures that a node will reject any heartbeat that attempts to rewind time.

This is a classic replay attack.

The test proceeds as follows:

  • First, it seeds the store with a dummy reveal for the namespace test.kinetic.
  • It manually generates a real ML-DSA signature keypair.
  • It attaches the public key bytes to the reveal.
  • It then sets the existing state: it inserts a record into last_heartbeats_by_name.
  • This indicates that the most recent valid heartbeat for this name occurred at Drand pulse 200.
  • Next, it constructs the malicious payload: a new Heartbeat struct for the same name.
  • It sets its latest_drand_kyn to 49.
  • Because 49 is less than 200, this heartbeat is in the past relative to what the node already knows.
  • To ensure the test evaluates the temporal logic and doesn’t just fail a signature check, the test actively signs this stale heartbeat.
  • It uses the correct ML-DSA private key, producing a valid cryptographic signature over a stale payload.
  • The heartbeat is passed to store.handle_heartbeat().
  • The handler analyzes the payload.
  • It looks up the existing pulse of 200.
  • It compares it to the incoming pulse of 49.
  • It immediately halts.
  • The test asserts that the result is an error specifically matching KineticStoreError::StaleHeartbeat.
  • The takeaway is absolute: a valid signature does not save a temporally stale payload.

4. Testing Host Routing Freshness (Topological Expiry)

Routing records in the DHT tell peers how to physically connect to a specific service.

Like heartbeats, they decay and must be continually refreshed by the host.

-> See: crates/kinetic-network/src/store/verification_tests.rs — Lines 8 to 28

The test_host_routing_freshness function rigorously checks the maximum age constraint of routing data:

  • It assumes a hypothetical current Drand round of 1000.
  • It creates a HostRoutingRecord and artificially sets its drand_kyn to 850.
  • This means the record is claiming to be from 150 pulses ago.
  • The Kinetic architecture enforces a strict maximum age for routing records (typically 100 rounds).
  • When the verify_host_routing_record function analyzes this record, it performs subtraction.
  • 1000 minus 850 equals 150.
  • Since 150 is greater than 100, the record is flagged as unacceptably old.
  • The assertion confirms the error.
  • Notably, the test expects it to return KineticStoreError::InvalidHostRouteSignature.
  • While this might seem misnamed initially, architecturally it implies that the freshness check is the very first gate inside the signature verification wrapper.
  • By failing the freshness check, the signature is deemed structurally invalid for the current network epoch.
  • This bypasses the expensive cryptographic math entirely.

5. Testing Peer ID Format Safeties (Defensive Parsing)

When working with distributed hash tables, nodes constantly receive raw byte arrays over the wire.

These arrays claim to be valid PeerId representations.

If a node panics (crashes) while trying to parse unexpectedly malformed bytes, a single malicious packet can take down the node.

-> See: crates/kinetic-network/src/store/verification_tests.rs — Lines 30 to 53

The test_peer_id_extraction_safeguard ensures robust, panic-free error handling when confronting invalid identity data:

  • It uses the libp2p::multihash module to deliberately construct a multihash using the SHA2-256 algorithm.
  • The Kinetic network specifically expects identity multihashes (typically ed25519 public keys embedded directly in the hash).
  • It embeds this intentionally incompatible multihash format into a HostRoutingRecord.
  • When the verify_host_routing_record logic executes, it attempts to extract the public key from the string representation of this PeerId.
  • The internal parsing logic recognizes that a SHA2-256 multihash does not contain an inline public key.
  • The critical part: the test asserts that the function gracefully returns a KineticStoreError::InvalidPublicKey enum variant.
  • It does not call .unwrap() on a None value.
  • A panic would trigger a thread panic and a node crash.
  • This is a vital demonstration of defensive programming at the network edge.

6. Testing ML-DSA Authorized KID Validation (The Post-Quantum Chain)

This is the most complex and critical test in the suite.

It ensures that the post-quantum signature schemes correctly bind decentralized identities (KIDs) to network namespaces.

-> See: crates/kinetic-network/src/store/verification_tests.rs — Lines 55 to 148

The test_mldsa_authorized_kid_validation test constructs an entire cryptographic chain of trust from scratch:

  • Key Generation:
    • It uses the ml_dsa crate to generate a fresh, correct ML-DSA-65 keypair.
  • DID Construction:
    • It simulates the identity generation process.
    • It takes the public key bytes.
    • It hashes them using SHA-256.
    • It converts the hash to a hex string.
    • It prepends the Kinetic DID prefix (did:kinetic:) to formulate a valid Decentralized Identifier string.
  • Document Creation:
    • It builds a comprehensive KidDocument struct.
    • Crucially, it populates the controller_keys vector.
    • It sets the key type to “MlDsa65”.
    • It provides the Base64-URL-encoded version of the ML-DSA public key.
  • Inner Signature:
    • The DID document itself requires a self-signature to prove ownership.
    • The test calls .sign() on the document using the private ML-DSA key.
  • Authorization Wrapper:
    • The signed DID document is then packaged inside an AuthorizedKid struct.
    • It targets a specific network name (test.kinetic).
    • This outer wrapper is then signed again by the ML-DSA key.
    • The signature is over a specific signable byte payload that includes the NETWORK_ID.
    • This inclusion prevents cross-network replay attacks.
  • Validation Execution:
    • It passes this nested, doubly-signed payload into the verify_authorized_kid function.
    • It also passes a mock existing Kademlia record.
  • The Assertion:
    • The assertion ensures that the result is either Ok or, at the very least, does not fail with a KineticStoreError::InvalidKidSignature.
    • This proves that the multi-layered ML-DSA signature verification interoperate correctly.
    • The parsing logic interoperates correctly.
    • Hex matching interoperates correctly.
    • Base64 decoding interoperates correctly without throwing false negatives.

Key Pieces

  • setup_store

    • What it does: Bootstraps an ephemeral environment with a temporary database, a mock peer identity, and a VDF engine.
    • Where it lives: crates/kinetic-network/src/store/handlers_tests.rs — Lines 30 to 51
    • Why it matters: Provides a clean, isolated state for state-mutating tests without polluting the developer’s local filesystem or causing test crosstalk.
  • test_rate_limiting

    • What it does: Verifies the VecDeque sliding window logic for incoming reveals, checking expiration thresholds.
    • Where it lives: crates/kinetic-network/src/store/handlers_tests.rs — Lines 53 to 111
    • Why it matters: Proves the node has an active defense against malicious peers attempting to overwhelm the local Sled database with rapid-fire reveal spam.
  • test_heartbeat_monotonicity

    • What it does: Rejects heartbeats containing a drand_kyn pulse that is lower than the previously stored pulse.
    • Where it lives: crates/kinetic-network/src/store/handlers_tests.rs — Lines 113 to 151
    • Why it matters: Defends the network against temporal replay attacks where old, validly-signed heartbeats are rebroadcast to manipulate the active state of a namespace.
  • test_host_routing_freshness

    • What it does: Ensures routing records cannot exceed a maximum age threshold relative to the current live Drand round.
    • Where it lives: crates/kinetic-network/src/store/verification_tests.rs — Lines 8 to 28
    • Why it matters: Keeps the network topology accurate and responsive by enforcing strict TTLs (Time To Live) on peer locations in the DHT.
  • test_peer_id_extraction_safeguard

    • What it does: Passes an unsupported multihash format (SHA-256 instead of an identity hash) as a Peer ID to verify the parser fails safely.
    • Where it lives: crates/kinetic-network/src/store/verification_tests.rs — Lines 30 to 53
    • Why it matters: Protects the node from panic-based denial-of-service attacks triggered by maliciously crafted, un-parsable routing payloads.
  • test_mldsa_authorized_kid_validation

    • What it does: Generates and validates a complete, accurate ML-DSA signed KID document and nested authorization payload.
    • Where it lives: crates/kinetic-network/src/store/verification_tests.rs — Lines 55 to 148
    • Why it matters: Unarguably proves that the integration between the complex post-quantum signature schemes and the decentralized identity layer works flawlessly in the compiled binary.

How This Connects to the Rest of Kinetic

  • CROSS-CRATE: The tests instantiate and utilize Reveal, VdfProof, Heartbeat, and HostRoutingRecord structs. These are defined and explained in detail within kinetic-core and kinetic-types.
  • CROSS-CRATE: The identity validation relies on KineticDid and KidDocument, which are the foundational primitives defined and explained in kinetic-kid.
  • Storage Layer Interface: The tests utilize SledStorage imported from kinetic-storage. This serves as the underlying, persistent state backend for the setup_store initialization, demonstrating how the network layer talks to the disk.
  • Drand Pulses as Clocks: The drand_kyn acts as the decentralized, universal clock. The tests demonstrate how the network uses these integer pulses to measure elapsed time, evaluate freshness, and define causality, ignoring the local hardware system clocks of individual machines.

Quick Reference

  • Rate Limiting Mechanism: Managed via a std::collections::VecDeque of timestamps. Expired timestamps are sequentially popped from the front of the queue to maintain the sliding window.
  • Temporal Monotonicity: An incoming pulse must always be greater than (>) the existing pulse to be accepted by the heartbeat handler.
  • Freshness Boundaries: The calculation current_drand_round - record.drand_kyn must yield a result less than the network’s allowed maximum age constant.
  • Safe Payload Parsing: Converting untrusted network strings into libp2p PeerId structs requires explicit error mapping to catch unsupported multihash formats safely, preventing unwrap() panics.
  • ML-DSA Signatures: ML-DSA keypairs are utilized to sign both the inner DID Document and the outer wrapping AuthorizedKid payload, creating a nested proof of authorization.
  • Wasm-Compatible Time Modules: The web_time::Instant and web_time::Duration modules are used instead of the standard std::time to guarantee that time calculations remain compatible if the node is compiled to WebAssembly.

Open Questions / Things to Revisit

  1. Error Type Clarity in Freshness Checks: In test_host_routing_freshness, providing a structurally stale pulse causes the assertion to expect a KineticStoreError::InvalidHostRouteSignature.

    • Architecturally, one might expect a distinct error variant like StaleRoutingRecord rather than overloading and falling back to a generic “invalid signature” error.
    • Saif should review if returning a signature error for a pure age violation obscures debugging logs in production.
  2. Hardcoded Genesis Assumptions: The setup_store function hardcodes the initial Drand pulse to 100.

    • If the global network configuration ever changes to require a higher genesis pulse (e.g., 500000), these tests might instantly fail due to violating genesis bounds constraints.
    • It may be wise to link this initial integer to a constant derived from the core crate.
  3. ML-DSA Test Code Density: The test_mldsa_authorized_kid_validation test is dense and procedural.

    • It manually implements the hashing and base64 encoding steps line-by-line.
    • Ideally, these steps belong inside a fluent builder pattern within the kinetic-kid crate itself, which would reduce the boilerplate required in network-level testing.
  4. Time Mocks vs. web_time: While web_time is used for Wasm compatibility, the tests still rely on sleeping or manually injecting historical timestamps.

    • A true deterministic time-mocking library might make the rate-limiting tests even more robust and isolated from the host machine’s execution speed.