Repository navigation
feat(platform)!: elected moderation declares whether its seat is contestable (PV14) - #4969
Conversation
…estable (PV14) An elected moderation declaration now says whether its seat can be contested again once a team is seated: `seatContestable`, a boolean, required with no default and frozen with the rest of the declaration. `challengeCoolDown` is required when it is true and refused when it is false. In Rust the two are one field, `challenge_cool_down: Option<u32>`, so a declaration can not disagree with itself whatever its encoding. The moderation charters contract's `electedCharter.byTargetContract` index stays keyed by the target contract alone: a future challenge is a new contest on that index, allowed only against a contestable seat. Nothing reads the key in protocol version 14, where a seated charter keeps the seat either way (another charter for the target is refused with 40105). Protocol version 14 is unreleased, so the declaration's encoding changes in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedNext included review available in 19 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Repository: dashpay/platform/.coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (14)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-09-24T06:33:59.889Z |
|
🕓 Queued for automated review — 3rd in line, estimated start in ~1.0 h (commit 5077904)
|
Issue being fixed or feature implemented
Part of #4865 (decentralized moderation teams), resolving its open question on the contested key.
A contract that declares elected moderation now says in its declaration whether its moderation seat can be contested again once a team is seated. The moderation charters contract's
electedCharter.byTargetContractindex stays keyed by the target contract alone: a future challenge (after protocol version 14) will be a new contest on that same unique index, allowed only when the target declares its seat contestable. There is no round component in the key and no separate challenge document type.The elected declaration is frozen at contract creation, so the key has to exist in 4.2 even though nothing reads it until challenges ship.
What was done?
The declaration gains
seatContestable(ElectedModerators,packages/rs-dpp/src/data_contract/config/moderation/elected.rs), protocol version 14, edited in place since 14 is unreleased.The proposed details are implemented as proposed:
seatContestable, a boolean, required with no default. A default of false would make every team permanent, which removes the only remedy for a leader who lost its keys or went rogue (the leader can not change). A default of true would opt every contract into challenges without asking.challengeCoolDown(always required until now) is required whenseatContestableis true and refused when it is false, since a cool-down means nothing for a seat that can not be contested. Its bounds (two weeks to three years) still apply to a contestable seat.validate_config_update2 refuses a change of the key in either direction (DataContractConfigUpdateError, 40002).electedChartercreate for a target whose seat is taken is still refused, paid, withDuplicateUniqueIndexError(40105), whatever the key says.Rust shape. The proposal left this open. The flag and the cool-down are one field,
challenge_cool_down: Option<u32>.Some(x)isseatContestable: truewithchallengeCoolDown: x;NoneisseatContestable: falsewith no cool-down.ElectedModerators::seat_contestable()returnsis_some(). I chose this over aboolbeside au32(0 meaning none) or beside anOption<u32>because a separate flag and cool-down can disagree. The declaration reaches consensus through bincode, not serde, so a pair that disagreed would need a validation rule and an error of its own. With oneOption, a declaration can not disagree with itself, whatever the encoding. The change is no larger: each struct literal changeschallenge_cool_down: 1_209_600toSome(1_209_600). The wire rule is enforced where the wire is read: the serde ofContractModerators.Before, the only accepted shape:
{ "$type": "elected", "challengeCoolDown": 1209600, "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] }, "interim": { "$type": "notYetUsable" } }After, a contestable seat:
{ "$type": "elected", "seatContestable": true, "challengeCoolDown": 1209600, "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] }, "interim": { "$type": "notYetUsable" } }and a seat its first team keeps for good:
{ "$type": "elected", "seatContestable": false, "moderatedDocumentTypes": { "post": ["ban", "deleteDocuments"] }, "interim": { "$type": "notYetUsable" } }Each shape, before and after:
seatContestable)seatContestable"seatContestable": truewith"challengeCoolDown": 1209600seatContestable"seatContestable": truewithoutchallengeCoolDownchallengeCoolDownchallengeCoolDown"seatContestable": falsewithoutchallengeCoolDownchallengeCoolDown"seatContestable": falsewith"challengeCoolDown": 1209600seatContestablechallengeCoolDownis only valid withseatContestable: true"seatContestable": true, "challengeCoolDown": 1209599seatContestableseatContestableeither wayThe
Displayof a declaration now names the seat:an elected moderation team, in its interim moderated by the contract owner, its seat open to a challenge 1209600 seconds after each seat change, or..., its seat never contested once a team is seated.Encoding. The declaration's bincode changes, as it did for
electionDelayandmaxAddedModerators: the cool-down becomes anOption<u32>(one tag byte more). Protocol version 14 is unreleased, so the change is in place. No pinned length or fixture moves: the wasm-dpp contract transition lengths belong to a contract without moderation, whose config encodesNonefor it as before, and no JSON fixture underpackages/rs-drive-abci/tests/supporting_files/carries an elected declaration (the ones there only reference elected contracts). Every Rust fixture that builds a declaration (rs-dpp, rs-drive, rs-drive-abci) now builds a contestable seat.Clients and docs.
ContractModeratorsTypeScript type takes the rule as a type: the elected variant is intersected withElectedModerationSeat = { seatContestable: true; challengeCoolDown: number } | { seatContestable: false; challengeCoolDown?: never }.packages/rs-platform-version/src/version/v14.rs, the elected declaration item) is extended in place.book/src/data-model/contract-moderation.md) has a new "The seat" part, the struct and the wire example updated, and the frozen-seat note.docs/protocol/moderation-charters.md) has a new paragraph on the contested key: it stays the target alone, and a challenge will be a new contest on the same index, open only against a contestable seat.How Has This Been Tested?
New tests (rs-dpp,
config/moderation/elected.rsandconfig/methods/validate_update/v2):should_round_trip_each_value_of_seat_contestable_through_json_and_platform_value: true with the minimum and the maximum cool-down, and false;challengeCoolDownis on the wire exactly when the seat is contestable.should_refuse_a_declaration_without_seat_contestable, with and without a cool-down, through JSON and platform value.should_require_the_cool_down_of_a_contestable_seat_and_refuse_it_on_a_permanent_one, a zero cool-down on a permanent seat included.should_bound_the_cool_down_of_a_contestable_seat_only; the existingshould_accept_every_bound_and_refuse_one_second_outside_eachstill checks the cool-down one second outside each bound.should_freeze_every_field_of_an_elected_declarationnow refuses a contestable seat made permanent, and a permanent seat opened to challenges.Displaytests cover the new key (a non-booleanseatContestable, the key under another moderator kind).New test (rs-drive-abci,
contract_user_moderation/tests/seated_team.rs):should_keep_the_seat_of_a_seated_team_whether_or_not_it_is_contestable. For each seat kind, a contest is awarded, then a rival files a proposal and an elected charter for the same target. The charter is refused, paid, withDuplicateUniqueIndexError(40105), and the seated charter still holds the seat. The pipeline tests incontract_user_moderation/tests.rsalso accept a contract create with a permanent seat, and refuse an update that makes the seat permanent.Run locally:
cargo test -p dpp --lib -- data_contract::config data_contract::document_type::property: 573 passedcargo test -p drive-abci --lib -- contract_user_moderation charter_election_tests contract_moderation_gate election elected owner_protected: 91 passedcargo test -p drive --lib -- fetch_charter_election_windows: 4 passedcargo test -p platform-version --lib: 23 passedcargo check -p wasm-dpp2 --target wasm32-unknown-unknown: cleancargo clippy -p dpp -p drive -p drive-abci --all-targets --all-features -- --no-deps -D warnings: cleancargo fmt --all: cleanNot run locally: the full workspace suites (CI runs them) and the nightly-only wasm-dpp JS tests. Their pinned contract transition lengths do not move, since that contract declares no moderation.
Breaking Changes
Consensus-breaking for protocol version 14 (unreleased): the elected moderation declaration's bincode encoding changes (the cool-down becomes optional), and a declaration without
seatContestableno longer parses from JSON or a platform value. No released protocol version is affected, since elected moderation exists only from 14.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code
PR Hygiene ·
5077904/skip-botsproceeds without the ones not yet reported/self-reviewedonce the bots are doneWhen every box is checked the
PR Hygienecheck passes and this can merge.