fix: avoid livelock after mining delay - #7489
Conversation
If, somehow, we arrive in a scenario where the miner's proposals are not reaching signers (or at least not 70% of them) for `block_proposal_max_age_secs`, then finally, the proposals arrive at the signers, they would previously silently drop this proposal, neither approving or rejecting it. The miner, continuing to wait for approval or rejection would be permanently stuck in the `propose_block` loop, only ever reproposing the same block. This commit changes the signer behavior so that instead of silently ignoring the block, the reject it with a new reason, `ProposalTooOld`, which, when it receives >= 30% of these rejections, will trigger the miner to exit that loop and mine a new block.
Coverage Report for CI Build 31036439263Coverage increased (+0.06%) to 86.642%Details
Uncovered Changes
Coverage Regressions387 previously-covered lines in 36 files lost coverage.
Coverage Stats
💛 - Coveralls |
federico-stacks
left a comment
There was a problem hiding this comment.
Implementation looks fine. There is a conflict that need to be addressed.
I also noticed 3 things that are pre-existing behaviour, so flagging for possible follow-ups/investigation:
-
BlockInfo::validis set at pre-commit, one phase before a signature is owed.mark_pre_committed()setsvalid = Some(true), anddetermine_responsekeys onvalidalone. It therefore can't distinguish "I intend to sign" (PreCommitted) from "I signed" (LocallyAccepted), and returns a full acceptance for both. Net effect: a re-proposal makes a pre-committed signer release its signature even though the 70% pre-commit threshold was never reached. Maybe not a big issue considering it is only reachable when pre-commits aren't circulating to 70% while the miner is already re-proposing. A check onstatealongsidevalidshould close it. -
should_reevaluate_blockhas a predicate name but broadcasts.. It sounds a bit measleading. A better naming (respond_to_known_proposal?!), or even producing a return type (Responded/Reevaluate) or even producing return type + splitting the re-evaluation behavior and the broadcasting could improve readability and maintainability. -
determine_responsedropsfailed_txidwhen re-sending a rejection. It substitutesRejectReason::RejectedInPriorRound, andBlockRejection::newpassesNoneforfailed_txid. The miner's exclusion logic requires both afailed_txidandRejectCode::ValidationFailed(BadTransaction|ProblematicTransaction), so a re-sent rejection contributes reject weight but no txid (probably relevant in case the miner didn't receive the previous rejection).
If, somehow, we arrive in a scenario where the miner's proposals are not reaching signers (or at least not 70% of them) for
block_proposal_max_age_secs, then finally, the proposals arrive at the signers, they would previously silently drop this proposal, neither approving or rejecting it. The miner, continuing to wait for approval or rejection would be permanently stuck in thepropose_blockloop, only ever reproposing the same block. This commit changes the signer behavior so that instead of silently ignoring the block, the reject it with a new reason,ProposalTooOld, which, when it receives >= 30% of these rejections, will trigger the miner to exit that loop and mine a new block.Checklist
docs/property-testing.md)changelog.d/README.md)rpc/openapi.yamlfor RPC endpoints,event-dispatcher.mdfor new events)clarity-benchmarkingrepo