Skip to content

kip-227/286: parent round, seal rules, violation passes, v3.0.0 text - #136

Open
2dvorak wants to merge 33 commits into
kaiachain:mainfrom
2dvorak:draft/kips-consensus-scoring
Open

2dvorak wants to merge 33 commits into
kaiachain:mainfrom
2dvorak:draft/kips-consensus-scoring

Conversation

@2dvorak

@2dvorak 2dvorak commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Proposed changes

  • KIP-227: header.VRank carries ParentRound and ParentCommittedSeal with the decoding rule and parent committee, absent before FORK_BLOCK; signature preimages, BLS key source and header field position; MAX_ROUND as a protocol constant with one-byte rounds, no candidate evaluation above it, invalid messages discarded; cfReport written by the block author for its own most recent proposal, delivery best effort; Example 1 rewritten; the downtime requirement marked as future work.
  • KIP-286: committed seals sign keccak256(blockHash || 0x02 || round); the block author must be a member of qualified(N); ceil(2n/3) seal quorum and single-mode header votes; Rule 2 acts on the pfReport that records the failure; the liveness floor and Rule 1 order; suspension as an address-keyed overlay; deadlines carried across states; the ranking stake and the two MinStake roles; the paused-time limit marked as future work; the CandTesting-below-MinStake row, maxSlotAvailable as the one value of both slot limits, the cap applying at the next epoch transition, kaia_getNodesByState in the sets note; reward eligibility follows the node states for block N, including its system transitions, while stake amounts and addresses remain sourced from N-1; under WeightedRandom, header.Rewardbase must match the block author's AddressBookV2 reward address, including on promotion blocks.

Types of changes

Please put an x in the boxes related to your change.

  • Bugfix
  • KIP Proposal
  • KIP Improvement

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • Used the suggested template: https://github.com/kaiachain/KIPs/blob/main/kip-template.md
  • I have read the CLA and signed by comment I have read the CLA Document and I hereby sign the CLA in first time contribution
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in downstream modules

Related issues

Further comments

2dvorak and others added 27 commits October 20, 2026 03:02
Word the cfReport rules in terms of Report now that header.VRank also
carries the parent round, so an empty cfReport no longer reads as an
empty header field. An epoch start sets Report to CandTesting.

Spell out the decoding rule (three items, canonical ParentRound) and the
parent committee and its quorum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the permissionless fork a committed seal signs the block hash, the
COMMIT code and the round byte, which the block hash leaves out. No KIP
stated this, yet KIP-227 verifies ParentCommittedSeal against it. Define
it in the committed-seals consensus rule and have KIP-227 refer to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pfReport(N) now lists the failed proposers of block N-1, so Rule 2's
trigger is a non-empty pfReport of the block, not a failure at the
block, and a PFS demotion lands one block later than before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A zero-length VRank encoded as an explicit empty RLP string decodes to a
present field and re-encodes as an extra trailing header element, so
nodes that know the field and nodes that do not would disagree on the
header. Clients therefore reject any present VRank before FORK_BLOCK,
not only a non-empty one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A block prepared in an earlier round is re-proposed unchanged at later
rounds, and only the round byte in header.Extra is rewritten, so the
header keeps the seal of the proposer that built it. Requiring the
author to equal GetProposer(N, round) rejects such a block on import.
Clients check membership in qualified(N) instead, which is also what
the pre-fork rule checked; the committed seals already bind the author
through the block hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A proposer does not send VRankPreprepare for a view above MAX_ROUND, a
candidate ignores such a solicitation, and MAX_ROUND is a protocol
constant. A block committed above MAX_ROUND follows a stall of over half
an hour, so skipping its evaluation is by design, and every round VRank
carries is one byte, which leaves rounds above 255 out of scope.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both VRank messages sign keccak256 over a VRANK_PREPREPARE_V1 or
VRANK_CANDIDATE_V1 domain, an 8-byte big-endian chain id and block
number, a one-byte round and the 32-byte block hash, with a 65-byte
ECDSA and a 96-byte BLS signature. The candidate's BLS key is read from
AddressBookV2 at the head after FORK_BLOCK and from KIP-113 before it,
with the proof of possession checked at lookup, and VRank is the
optional tail field after ExcessBlobGas. Without the byte layout, the
key source and the field position, a second client cannot verify either
signature or encode the header.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CANDIDATE_MSG_TIMEOUT is a 500 ms protocol constant, and VRank messages
go only to directly connected CN peers on kaia68 or later, where a full
per-peer queue may drop them. A candidate answers each view at most
once and only for a block that extends its head, the proposer evaluates
the first stored reply per candidate and view, and a proposer that lost
its timing data reports nothing, so cfReport can under-report but never
over-report.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A cfReport covers its writer's own most recent prior proposal in the
epoch, so a proposer's first block in the epoch reports nothing. The
example now spans blocks 10 to 16 with four distinct proposers, every
non-empty report follows an earlier proposal by the same proposer, and
the aggregated table is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The reporter credited for cfReport(N) and counted in proposerSet is the
author of block N, which a hash-locked re-proposal does not change. The
target T is the author's most recent proposal in the epoch, meaning the
block whose committing round it proposed, and an author reports on each
of its proposals at most once.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A VRankPreprepare or VRankCandidate with an unknown or unauthorized
sender, a bad ECDSA or BLS signature, a round outside [0, MAX_ROUND] or
a mismatched block is discarded, and the receiver may disconnect the
peer. Both nodes stay in CNPeers, so KIP-311 reconnection restores the
link after one dial cycle, while a reply for a view the receiver did not
propose is only ignored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The stability list has carried the downtime requirement since the
reputation design that preceded PFS and CFS, but nothing measures
downtime: the only scores are proposal and candidate failures, and the
violations list maps only PFS. Rather than drop the requirement, mark
it as future work so readers do not expect a penalty v3.0.0 does not
apply.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The pseudocode does not say which PFS Rule 2 reads. State that block
N+1's transitions use pfs(N), that a violator held back by a full
budget is judged again, and how this meets an epoch block, and pin the
address order behind sorted_validators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The transition table said PFS "exceeds" PFS_THRESHOLD, while the
pseudocode, the rationale and KIP-227 all use PFS >= PFS_THRESHOLD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eshold

The transition table said the score "exceeds" CFS_THRESHOLD, while the
parameter table and KIP-227 both use CFS >= CFS_THRESHOLD.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AddressBookV2's pause() and exit() from ValActive revert unless the
ValActive count exceeds minActiveCount, the same floor the system
transitions keep. The user-tx rows listed only the slot limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The violation transition runs every block, epoch blocks included, so
Rule 1 exits retained validators below MinStake in the same block, and
the pseudocode comment claiming it skips epoch blocks was wrong. The
slot limits now name epochVACount, defined as the ValActive count after
the epoch pass and before the violation pass, instead of "the count at
epoch start".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A second client needs the post-fork seal quorum as a number: ceil(2n/3)
over the committee, all n seals when n < 4, a duplicated seal rejecting
the block, and pre-fork blocks keeping 2f+1. The Committee row also
claimed every member may cast header votes, which does not hold in
single governance mode, the mode mainnet and Kairos run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The committee of N includes the system transitions applied at the start
of N, while the reward-eligible set and staking amounts come from the
state after N-1. A validator moved by a system transition therefore
joins or leaves the reward inputs one block after the committee, an
accepted limitation that a second client must reproduce.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ValActive row said a suspended validator earns no rewards, while
RewardEligible is {ValActive}; the reward rule holds and the row now
speaks of consensus only. Suspension is written down as an overlay that
leaves counts, guards and Rules 1-2 untouched, with the end of the
all-suspended fallback and the suspender's operating rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The KIP gave only durations, which leaves open when a deadline is set,
whether parameter updates move it, and which block applies the expiry.
It now fixes all three and names the user calls that must revert with
TimeoutExpired, so no user transaction can pre-empt the system
transition at the deadline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The KIP ranked by "staking amount" without saying which amount: it is
the node's CnStakingV4 staking() minus unstaking(), floored to whole
KAIA and read from the parent state, with Consensus Liquidity stake left
out. MinStake comes from the AddressBookV2 constant for user transitions
and from reward.minimumstake for system transitions and rewards, and the
two must match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The design rationale promised a per-epoch limit on total paused time
with a penalty "as defined in KIP-227", but KIP-227 defines none and
no client keeps a paused-time total: a pause arms a fresh timeout and
resume clears it. State the limit as future work and name the two
bounds that do apply, so the text no longer describes an enforcement
that does not exist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The table lists what happens to a candidate that passes the VRank test
and is in or out of the top 50, but a candidate whose stake fell below
MinStake during testing competes for nothing: the client demotes it to
ValInactive at the epoch and arms its idle deadline. Add the row so the
outcome is not left to inference from the top-50 rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ordering section binds the pseudocode names to the parameter
table, but a reader still has to compare the two table rows to see
that ValPausedSlotLimit and ValExitingSlotLimit are the same number,
which is why one symbol serves both in the pseudocode. Say it there.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The configurator can lower MaxValActivePausedCount below the current
ValActive and ValPaused count; nothing checks the new value against it,
and no transition between epochs demotes for exceeding it. The cut
happens at the next epoch, and if it would leave no ValActive the
liveness floor keeps the previous set, so the count can stay above the
cap for one more epoch. Say so next to the floor rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The note said only Council and Committee are exposed, but the client
has served kaia_getNodesByState in the public kaia namespace since
v3.0.0-rc.1, returning every node's state, staking amount, deadlines
and suspended flag, and the method is being added to the JSON-RPC
reference. Keep the consensus meaning with the two sets and name the
monitoring API, so the note matches what nodes answer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@2dvorak 2dvorak self-assigned this Oct 20, 2026
@2dvorak
2dvorak marked this pull request as ready for review October 20, 2026 11:44
@2dvorak
2dvorak requested a review from adnpark as a code owner October 20, 2026 11:44
@2dvorak
2dvorak requested a review from ian0371 as a code owner October 20, 2026 11:44
Comment thread KIPs/kip-227.md Resolved
Comment thread KIPs/kip-286.md Outdated
for validator in validators:
if validator.state in (ValReady, ValInactive):
if idle_duration >= ValIdleTimeout:
if parent.timestamp >= validator.idle_deadline:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

As written, parent.timestamp >= validator.idle_deadline (and the equivalent pause check) expires a validator whose deadline is zero. AddressBookV2 treats timeoutAt == 0 as ‘no timeout’ (_revertIfTimeoutExpired checks timeoutAt != 0), and KIP-290 documents the same meaning. This also matches #1100, which makes zero deadlines persist without expiring.

Please specify that zero deadlines are skipped, for example: if deadline != 0 and parent.timestamp >= deadline. Apply this to both idle and pause deadlines, and clarify that the corresponding user-action checks also skip zero deadlines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks. PTAL 94ea322

2dvorak and others added 3 commits October 22, 2026 01:08
The timeout pseudocode expired a validator once parent.timestamp >=
deadline, and the user-action rule reverted once block.timestamp >=
deadline, so a deadline of 0 read as expired at once. The client and
AddressBookV2 treat timeoutAt == 0 as no active timeout, and KIP-290
already says so. Require a nonzero deadline in both checks and define
the sentinel. Every timed-state entry through the transitions still
sets an absolute deadline; a zero one comes only from state written
outside them, such as an initial allocation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The invalid-message paragraph promised that the CN mesh re-establishes
the link within one dial cycle. Membership only keeps both nodes
eligible for discovery and dialing; whether and when the link comes
back depends on reachability, admission and the dial backoff, and a
peer that keeps sending invalid messages is dropped again each time.
Say that, so the sentence is not read as a fixed-duration guarantee.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Chihyun Song <ian0371@gmail.com>
2dvorak and others added 3 commits October 27, 2026 14:53
The reward paragraph said StakingInfo(N) includes every AddressBookV2
profile regardless of node state. The client builds it from
getAllProfiles(), which enumerates only nodes outside the Registered
state; a Registered node is kept in a separate list and has no entry.
Name that set, so a reader does not expect a Registered node in
kaia_getStakingInfo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StakingInfo(N) and kaia_getStakingInfo(N) list exactly RewardEligible(N),
with parent-state amounts, reward addresses and CL entries, instead of
every non-Registered profile. Follows kaia#1125.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants