Skip to content

Fix/deterministic cnight mock for multi node#1870

Open
LGLO wants to merge 13 commits into
mainfrom
fix/deterministic-cnight-mock-for-multi-node
Open

Fix/deterministic cnight mock for multi node#1870
LGLO wants to merge 13 commits into
mainfrom
fix/deterministic-cnight-mock-for-multi-node

Conversation

@LGLO

@LGLO LGLO commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Overview

PR #403 from branch moved to our remote, to let CI pass

Replace random UTXO generation with deterministic generation based on block number to ensure all nodes in a multi-node development network produce identical inherent data. This prevents block verification failures due to inherent data mismatches.

All nodes with the same block number now generate the same mock CNight observations (tx hashes, reward addresses, dust public keys), enabling multi-node development setups.

🗹 TODO before merging

  • Ready

📌 Submission Checklist

  • Changes are backward-compatible (or flagged if breaking)
  • Pull request description explains why the change is needed
  • Self-reviewed the diff
  • I have included a change file, or skipped for this reason:
  • If the changes introduce a new feature, I have bumped the node minor version
  • Update documentation (if relevant)
  • No new todos introduced

🧪 Testing Evidence

This fix enables running local multi-node networks in development mode:

  • All validators can produce and verify blocks without inherent mismatches
  • Nodes synchronize properly by emulating Cardano infrastructure
  • Works with both single-node and multi-node setups

Please describe any additional testing aside from CI:

  • Additional tests are provided.

🔱 Fork Strategy

  • Node Runtime Update
  • Node Client Update
  • Other:
  • N/A

riusricardo and others added 11 commits December 26, 2025 17:48
Replace random UTXO generation with deterministic generation based on block
number to ensure all nodes in a multi-node network produce identical inherent
data. This prevents block verification failures due to inherent data mismatches.

All nodes with the same block number now generate the same mock CNight
observations (tx hashes, reward addresses, dust public keys), enabling
multi-node development setups without Cardano infrastructure.
Signed-off-by: Lech Głowiak <LGLO@users.noreply.github.com>
@LGLO LGLO requested a review from a team as a code owner July 14, 2026 06:00
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@jina-simulation

jina-simulation Bot commented Jul 14, 2026

Copy link
Copy Markdown

Jina Review

Jina has completed this review.

Review: https://app.usejina.com/reviews/6a3f4746-ff10-42b9-bd31-ee1d3190ed03

Item Status
Review Completed
Findings Issues found

@datadog-official

This comment has been minimized.

@jina-simulation jina-simulation Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime Review - Merge Readiness 2/5

3 issues found - Do not merge until addressed

Do not merge until addressed.

The accepted findings are confined to development mode, but invalid keys can break the intended CNight-to-Dust workflow immediately, while reward-address collisions become reachable during normal local operation and permanently make mappings ambiguous.

Persistent per-block state growth adds a substantial long-running cost.

Final review: Three grounded registration-state issues are accepted.

They affect development networks through address ambiguity, invalid Dust keys, and unbounded storage growth.

Review Comments

  • Boundary cursor handling is not covered

    The mock increments block_number with unchecked u32 addition.

    The investigation did not promote this to a publishable issue because the boundary is unlikely in ordinary development, but the position-progression area explicitly includes boundary behavior and the added tests do not cover u32::MAX.

    A checked increment with an explicit data-source error would make this edge case safer.

    Evidence:

    • primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs:119 uses end.block_number += 1.
    • CardanoPosition.block_number is u32.
    • Focused Cargo execution was unavailable.

    Related files: primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs, pallets/cnight-observation/src/lib.rs

Issues

Inline comments posted

  • Deterministic reward addresses repeat every 256 blocks

    Location: primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs:76; Risk / impact: high; Evidence confidence: high; Production likelihood: high; Category: data; Validation: hybrid

    Reward-address bytes are generated modulo 256, so blocks n and n+256 produce the same address. Distinct UTXO hashes create multiple Mapping entries for that address, causing the pallet to mark it ambiguous. This violates the area expectation that per-block mock identities avoid unintended registration ambiguity.

  • Generated Dust keys are not guaranteed to be valid public keys

    Location: primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs:80; Risk / impact: high; Evidence confidence: medium; Production likelihood: high; Category: integration; Validation: hybrid

    The mock labels arbitrary 33-byte data with a 0x02 prefix as a compressed Dust public key. DustPublicKeyBytes validates only length. At genesis block 0 this produces 0x02 followed by 32 zero bytes, which downstream ledger deserialization rejects. This violates the area expectation that generated Dust keys satisfy downstream semantic requirements.

  • Every queried block permanently adds a Mapping row

    Location: primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs:123; Risk / impact: medium; Evidence confidence: high; Production likelihood: high; Category: performance; Validation: hybrid

    The cadence change now returns one registration on every query, and process_tokens permanently inserts each registration into Mapping. This violates the area expectation that registration storage and event growth remain intentional and sustainable.

Summary

  • Status: issues found
  • Areas investigated: 4
  • Tasks/probes performed: 3
  • Issues reported: 3
  • Publishable issues: 3
  • Inline comments: 3
  • File-level comments: 0
  • Review comments: 1
  • Unanchored publishable findings posted as file-level comments: 0
  • Blocked validations: 1
  • Dashboard: full runtime review
  • Commit: d1bd4a8
  • Changed files: 2
  • Tool calls: none

// Deterministic reward address (29 bytes)
let mut reward_addr = [0u8; 29];
for (i, byte) in reward_addr.iter_mut().enumerate() {
*byte = ((block_num as u64 * (i as u64 + 7) * 17) % 256) as u8;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deterministic reward addresses repeat every 256 blocks

Risk: high
Confidence: high
Likelihood: high
Grounding: runtime review, hybrid

What happens

Reward-address bytes are generated modulo 256, so blocks n and n+256 produce the same address. Distinct UTXO hashes create multiple Mapping entries for that address, causing the pallet to mark it ambiguous. This violates the area expectation that per-block mock identities avoid unintended registration ambiguity.

Root cause

The identity derivation has a 256-block period.

Why it matters

After approximately 25.6 minutes at six-second blocks, registrations become unusable because unique_dust_key returns None.

Evidence

  • Related runtime area cnight_registration_state: Mock registration cadence and persistent CNight mapping state
  • Area expectations: Emitting one registration every queried block has intentional, sustainable effects on Mapping storage and registration events.; Generated reward addresses and Dust keys satisfy downstream representation and semantic requirements.; Per-block mock identities vary sufficiently to avoid unintended registration ambiguity.; The mock remains within runtime UTXO capacity and weight assumptions.
  • Reward bytes use ((block_num * (i + 7) * 17) % 256).
  • Mapping is keyed by reward address and UTXO ID.
  • unique_dust_key returns None for multiple mappings.

Reproduction or trace

Blocks 0 and 256 generate identical reward addresses but distinct UTXO hashes; the second registration transitions the address to an ambiguous state.

Suggested fix

Derive identities from a collision-resistant hash of the block position.

View full runtime review audit trail


let (dust_pk, _) = dust_pk.split_at((0..33).choose(&mut rng).unwrap());
// Deterministic dust public key (33 bytes compressed)
let mut dust_pk = [0u8; 33];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated Dust keys are not guaranteed to be valid public keys

Risk: high
Confidence: medium
Likelihood: high
Grounding: runtime review, hybrid

What happens

The mock labels arbitrary 33-byte data with a 0x02 prefix as a compressed Dust public key. DustPublicKeyBytes validates only length. At genesis block 0 this produces 0x02 followed by 32 zero bytes, which downstream ledger deserialization rejects. This violates the area expectation that generated Dust keys satisfy downstream semantic requirements.

Root cause

The mock fabricates serialized key bytes instead of deriving and serializing a valid DustPublicKey.

Why it matters

Registrations can be stored successfully while CNightGeneratesDustEvent construction fails, particularly from the genesis starting position.

Evidence

  • Related runtime area cnight_registration_state: Mock registration cadence and persistent CNight mapping state
  • Area expectations: Emitting one registration every queried block has intentional, sustainable effects on Mapping storage and registration events.; Generated reward addresses and Dust keys satisfy downstream representation and semantic requirements.; Per-block mock identities vary sufficiently to avoid unintended registration ambiguity.; The mock remains within runtime UTXO capacity and weight assumptions.
  • DustPublicKeyBytes::try_from checks only the 33-byte bound.
  • Block 0 produces [0x02, 0, ..., 0].
  • construct_cnight_generates_dust_event deserializes the key.
  • handle_create suppresses the event when construction fails.

Reproduction or trace

Register the block-0 mock UTXO, then process an asset create for its reward address; registration succeeds but ledger owner deserialization rejects the fabricated key.

Suggested fix

Derive a deterministic valid Dust key and serialize the resulting public key.

View full runtime review audit trail


let utxos =
if start.block_number.is_multiple_of(5) { mock_utxos(start) } else { Vec::new() };
// Return deterministic UTXOs - same data on all nodes for the same block

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every queried block permanently adds a Mapping row

Risk: medium
Confidence: high
Likelihood: high
Grounding: runtime review, hybrid

What happens

The cadence change now returns one registration on every query, and process_tokens permanently inserts each registration into Mapping. This violates the area expectation that registration storage and event growth remain intentional and sustainable.

Root cause

The former every-fifth-block filter was removed without a bounded identity or cleanup strategy.

Why it matters

At six-second blocks, development chains accumulate about 14,400 Mapping rows and events per day, increasing database, snapshot, and indexing costs.

Evidence

  • Related runtime area cnight_registration_state: Mock registration cadence and persistent CNight mapping state
  • Area expectations: Emitting one registration every queried block has intentional, sustainable effects on Mapping storage and registration events.; Generated reward addresses and Dust keys satisfy downstream representation and semantic requirements.; Per-block mock identities vary sufficiently to avoid unintended registration ambiguity.; The mock remains within runtime UTXO capacity and weight assumptions.
  • The mock always returns one UTXO.
  • handle_registration inserts into Mapping and emits MappingAdded.
  • Mapping is an unbounded StorageDoubleMap with no mock cleanup.

Reproduction or trace

Repeatedly process successive mock observations; each block adds one persistent Mapping entry.

Suggested fix

Bound the synthetic registration set or document and intentionally manage the resulting state growth.

View full runtime review audit trail

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>
@jina-simulation

jina-simulation Bot commented Jul 14, 2026

Copy link
Copy Markdown

Jina Review

Jina skipped this review.

Review: https://app.usejina.com/reviews/c7bff964-2189-4e3b-a062-44813519f050

Item Status
Review Skipped
Findings Unavailable

Signed-off-by: Lech Głowiak <lech.glowiak@shielded.io>
@jina-simulation

jina-simulation Bot commented Jul 14, 2026

Copy link
Copy Markdown

Jina Review

Jina has completed this review.

Review: https://app.usejina.com/reviews/1b64b009-1e5a-4a36-a201-d83112c289a3

Item Status
Review Completed
Findings No issues found

@jina-simulation jina-simulation Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Runtime Review - Merge Readiness 5/5

0 issues found - Ready to merge

Based only on accepted issues, Jina found none in the current investigation.

Earlier PR-thread findings are not accepted again because they are not current investigation candidates and are already represented in PR history.

Final review: No publishable candidates were produced by the current runtime investigation.

Prior findings are already present in PR history and are not reissued.

Review Comments

  • Runtime investigation produced no adjudicable candidates

    All five investigation areas ended in warned status without tasks, tool calls, or final results.

    The current candidate issue set is empty, so no new blocking runtime findings can be accepted in this review.

    Evidence:

    • All files under /tmp/jina-runtime-review-hiCKUh/investigations report status warned and issues: [].
    • Candidate issues supplied for this review are [].

    Related files: primitives/mainchain-follower/src/data_source/cnight_observation_mock.rs

Issues

No medium/high-confidence runtime issues were found.

Summary

  • Status: warned
  • Areas investigated: 5
  • Tasks/probes performed: 0
  • Issues reported: 0
  • Publishable issues: 0
  • Inline comments: 0
  • File-level comments: 0
  • Review comments: 1
  • Unanchored publishable findings posted as file-level comments: 0
  • Blocked validations: 0
  • Dashboard: full runtime review
  • Commit: 9a61fd5
  • Changed files: 2
  • Tool calls: none

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants