Skip to content

fix(blockchain): clarify DeclarationInfo::withdraw_at and its behavior - #424

Open
youngjoon-lee wants to merge 1 commit into
masterfrom
fix/sdp-withdraw-at
Open

fix(blockchain): clarify DeclarationInfo::withdraw_at and its behavior#424
youngjoon-lee wants to merge 1 commit into
masterfrom
fix/sdp-withdraw-at

Conversation

@youngjoon-lee

@youngjoon-lee youngjoon-lee commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

The definition of DeclarationInfo::withdraw_at is written correctly:

withdraw_at refers to the epoch number for which the service declaration will be withdrawn

But, the description in the Withdraw section does not match with the definition. So, I polished some sentences in the section.

In short, withdraw_at should be set with e + 2, where e is the epoch of block containing WithdrawMessage.
I remember that we renamed withdrawn_at to ‘withdraw_at' for clarity.

@youngjoon-lee
youngjoon-lee requested a review from madxor August 27, 2026 11:14
@youngjoon-lee youngjoon-lee self-assigned this Aug 27, 2026
madxor added a commit that referenced this pull request Aug 27, 2026
…rved epoch

Two problems on the withdrawal side of the same seam, both about which epoch a
stored field names.

The first is the name. withdraw_at reads as "the epoch at which the withdrawal
is executed", which is not what it holds: 58f636f deliberately made it record
the epoch the withdrawal was requested in, and dropped the guard that the value
be higher than the current epoch. The name has been pulling readers back toward
the old meaning ever since, most recently in #424, which proposes setting it to
the current epoch plus two. Rename it to withdrawn, in the past tense, which
cannot be read as a future execution moment. That also lines the three lifecycle
fields up with the three event types the Indexing section already defines:
created, active, withdrawn. created and withdrawn record when a message landed,
active records what a message claimed.

Storing the requested epoch rather than the removal epoch is the right side of
that choice for the same reason active stores the attested epoch: it is the only
value observable when the field is written, and it does not bake the
finalization delay into ledger state, where a later change to that delay would
falsify every stored value.

The second is the service window, which #424 is right about. A withdrawal
included in epoch e reaches services only through the snapshot for epoch e+2, so
the declaration is still listed for e+1 and the node serves it. The previous
text claimed the node "provides the service through the withdrawal epoch e and
stops afterwards", which the snapshot rule contradicts.

Correcting that exposes an epoch the node serves but can never be paid for. The
report attesting to e+1 is submitted during e+2, and the declaration was removed
at the start of e+2. So the node sat in the active set for an epoch it had no
reason to serve, which for the Blend Network means peers connecting to it and
counting it toward the minimum network size. Move removal to e+3: the e+1 report
lands during e+2, its reward is paid in the first block of e+3, and removal
follows in that same block, exactly as the existing rule already promises when
it says a declaration is never removed before its final reward is paid. The
stake stays locked one epoch longer, across every epoch the node was in service.

Mantle carries the removal predicate and the write, so both move with it:
withdrawn <= current_epoch - 3, and declare_info.withdrawn = current_epoch.
#424 changed the field's meaning in the SDP document alone, which would have
left the Mantle predicate firing at e+4 while the prose still said e+2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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