Skip to content

feat(validator-set)!: minimum author node version (spec 18, node/runtime 0.4.0) - #159

Merged
nol4lej merged 1 commit into
mainfrom
feat/min-author-version
Oct 8, 2026
Merged

nol4lej merged 1 commit into
mainfrom
feat/min-author-version

Conversation

@nol4lej

@nol4lej nol4lej commented Oct 8, 2026

Copy link
Copy Markdown
Member

Summary

Root can now require block authors to run a minimum node version. Each node declares its version in every block it authors, and the runtime rejects blocks from authors below the minimum. A lagging validator loses its slots but keeps importing and voting on finality.

While no minimum is set nothing is enforced, so every existing binary keeps authoring.

How it works

  1. Declaration. The node parses its crate version at compile time and adds it to every block it authors through a mandatory inherent (note_author_version, identifier *b"nodevers"). A crate version that does not fit major.minor.patch in u16 fails the build.
  2. Record. LastAuthorVersion keeps each approved validator's last declared version and the block it declared in. An entry is dropped when:
    • the validator leaves the set, or
    • it authors a block without declaring (it rolled back to an older binary).
  3. Minimum. Root calls set_min_author_version(Some(v)). The call is refused with VersionQuorumNotMet unless 2/3 of the approved set declared v or newer within QuorumWindow (one session). An empty set is always refused, so a minimum can never halt Aura. None lifts the minimum with no quorum.
  4. Enforcement. While a minimum is set:
    • a block with no declaration is invalid (on_finalize rejects it);
    • a declaration below the minimum is refused, and the node logs why instead of the proposer's generic error.
  5. Startup check. A validator whose version is below the chain's minimum logs a clear error at startup. It still starts, imports and votes; it just cannot author.

Changes

Area Change
pallet-validator-set New author_version/ module:
- version.rs: NodeVersion with a const fn parse and Display
- inherent.rs: the identifier and the node's inherent provider
- declarations.rs: what the runtime accepts, records and rejects, plus the quorum

Calls 6 (set_min_author_version) and 7 (note_author_version), storage, event, errors, benchmarks and estimated weights.
Breaking: Config gains FindAuthor and QuorumWindow.
Runtime spec_version 18; transaction_version stays 5 (calls added, none changed). FindAuthor = FindAuthorAccountId, QuorumWindow = Period.
Node New author_version.rs module: the compile-time version, the inherent provider wired into every inherent-provider tuple, and the startup check.
Tests Pallet tests split into tests/mod.rs and tests/author_version.rs.
Versions pallet-validator-set, orbinum-runtime and orbinum-node bumped to 0.4.0. Node 0.4.0 is the first binary that declares its version.
Docs docs/min-author-version.md (operator guide), the pallet CHANGELOG [0.4.0], and the spec 18 row in RUNTIME_VERSIONS.md.

Rollout

  1. Tag v0.4.0 (it must match the node crate version).
  2. setCode spec 18. Old binaries keep authoring; no new host function.
  3. Operators upgrade to 0.4.0.
  4. Once 2/3 of the set declare 0.4.0, Root calls validatorSet.setMinAuthorVersion(0.4.0).

Security

  • The version is self-declared. It coordinates operators of an invitation-only set; it is not a security boundary. Declarations are public chain state.
  • A pallet scan found no raw arithmetic or casts. The only panic is the intended block rejection in on_finalize, and AddRemoveOrigin is EnsureRoot.
  • An attack pass on a local 4-validator network found one flaw, which is fixed:
    • Flaw: a validator that rolled back to an old binary kept counting toward the quorum for a whole session.
    • Fix: authoring a block without a declaration now drops the author's entry.

Testing

Check Result
cargo test -p pallet-validator-set 60 passed (64 with runtime-benchmarks)
cargo test -p orbinum-runtime 46 passed
cargo fmt --check, taplo fmt --check, make clippy (CI flags) Clean

Local 4-validator network: genesis spec 17, then setCode to spec 18, mixing old and new binaries.

# Scenario Result
T1–T2 Fake noteAuthorVersion, unsigned or signed Rejected by the pool: Transaction dispatch is mandatory
T3 setMinAuthorVersion from a non-root account BadOrigin
T4 Minimum that no validator runs VersionQuorumNotMet
T5 Quorum with a validator rolled back to an old binary Refused, after the fix above
T6 Minimum set with 3/4 ready Accepted; the old binary loses its slots and keeps importing; finality advances
T7 Node declaring 0.3.0 under a 0.4.0 minimum No slots; the node logs the reason
T8 Root removes a ready validator Its entry is dropped and not re-added
T9 Minimum lifted The old binary authors again
T10 Validator restarts with the minimum set Resumes authoring with no errors
Startup Node 0.3.9 against a 0.4.0 minimum Clear error at startup; the node keeps importing and voting

Follow-ups

  • Benchmark set_min_author_version and note_author_version on the reference machine; their weights are estimated for now.
  • Re-benchmark remove_validator and deregister_validator: each now does one more storage removal.

@nol4lej
nol4lej merged commit 6ec1085 into main Oct 8, 2026
6 checks passed
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.

1 participant