Skip to content

Genesis ProtocolConfig should bound consensusParams to uint16 before packing #320

Description

@Kewe63

Summary

The genesis ProtocolConfig schema accepts consensusParams values as arbitrary non-negative bigint values, but these values are later packed into uint16-sized lanes.

Because the schema does not enforce the uint16 range, values greater than 65535 can be accepted during genesis config parsing and then overflow into neighboring packed fields.

This can silently produce a different on-chain consensus configuration than the one provided in the genesis config.


Affected File

scripts/genesis/ProtocolConfig.ts


Observed Behavior

The consensusParams fields are validated with schemaBigInt, but not capped to the uint16 range.

The affected fields are:

  • timeoutProposeMs
  • timeoutProposeDeltaMs
  • timeoutPrevoteMs
  • timeoutPrevoteDeltaMs
  • timeoutPrecommitMs
  • timeoutPrecommitDeltaMs
  • timeoutCommitMs
  • targetBlockTimeMs

These fields are packed into a single storage slot as 8 x uint16 values.

A value like:

timeoutProposeMs: 65536n

is accepted by the schema, but when packed into uint16-sized storage, it becomes:

timeoutProposeMs: 0
timeoutProposeDeltaMs: 1

So the value does not simply fail or clamp; it spills into the neighboring lane.


Expected Behavior

Genesis validation should reject consensusParams values outside the uint16 range before packing.

Each packed consensusParams field should be constrained to:

0 <= value <= 65535

For example:

schemaBigInt.min(0n).max(65535n)


Reproduction

I verified this with a focused repro script against the current code.

The repro uses:

timeoutProposeMs: 65536n

The schema accepts the config, then the packed result shows that the value overflows into the next 16-bit lane.

Observed output:

zod parse accepted: true
packed slot low32: 0x00010000
decoded timeoutProposeMs uint16: 0
decoded timeoutProposeDeltaMs uint16: 1

This demonstrates that:

  • the invalid value is accepted by the genesis schema,
  • the packed representation no longer matches the intended input,
  • and the neighboring consensus parameter is modified by the overflow.

Why This Matters

Genesis configuration should either exactly represent the intended chain parameters or reject invalid values.

Accepting out-of-range bigint values before packing can result in a chain starting with consensus parameters different from those specified in the genesis file.

This is especially risky because the config appears valid at parse time, but the corruption happens during packing.


Suggested Fix

Apply uint16 bounds to every consensusParams field that is packed into a uint16 lane.

For example:

timeoutProposeMs: schemaBigInt.min(0n).max(65535n),
timeoutProposeDeltaMs: schemaBigInt.min(0n).max(65535n),
timeoutPrevoteMs: schemaBigInt.min(0n).max(65535n),
timeoutPrevoteDeltaMs: schemaBigInt.min(0n).max(65535n),
timeoutPrecommitMs: schemaBigInt.min(0n).max(65535n),
timeoutPrecommitDeltaMs: schemaBigInt.min(0n).max(65535n),
timeoutCommitMs: schemaBigInt.min(0n).max(65535n),
targetBlockTimeMs: schemaBigInt.min(0n).max(65535n),

Potential Regression Test

A regression test should verify that schemaProtocolConfig rejects consensusParams values above 65535.

Example test cases:

  • timeoutProposeMs: 65535n should be accepted
  • timeoutProposeMs: 65536n should be rejected
  • each consensusParams field should reject 65536n

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions