Establish mainnet-launch: adopt DDC work onto the Mainnet line, preserving master's pallet indices - #734
Open
jaxter03 wants to merge 8 commits into
Open
Establish mainnet-launch: adopt DDC work onto the Mainnet line, preserving master's pallet indices#734jaxter03 wants to merge 8 commits into
jaxter03 wants to merge 8 commits into
Conversation
`staging` moved four things out of this repo. This commit follows, and does
nothing else — the tree does not build until the pallets and runtime catch up
in the next two commits.
primitives/ -> ddc-primitives (external)
pallets/ddc-payouts/ -> pallet-ddc-payouts (external)
(new) -> ddc-api, ddc-dac-host,
pallet-ddc-verification
All five reference `branch = "mainnet-launch"`, never "staging". Cargo keys a
git source by URL *and* branch, so one reference left on "staging" resolves the
same commit as a second, distinct crate and breaks trait bounds with errors
that do not name the cause. This was hit for real in ddc-payouts.
Also adds the workspace deps the external crates need (prost, serde_with,
scopeguard) and `ink` for contracts/customer-deposit.
Workspace version 7.5.0 — continues master's 7.4.0. Staging's 8.0.1 belongs to
a separate lineage.
Rejected from staging's version of this file: the members-list reordering and
the clap/codec line swap, both pure churn.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
Code only; these pallets stay in this repo. No
Cerebellum-Network/ddc-{customers,clusters,nodes,staking,clusters-gov}
repositories exist — only ddc-payouts and ddc-verification were extracted.
An API-surface diff across all 11 pallets found exactly one genuine removal
(fee-handler, below). Everything else flagged was a rename:
AccountsLedger->CustomerLedger, sub_account_id->cluster_vault_id,
OldCluster-> versioned v0/v1/v2::Cluster.
DELIBERATE DIVERGENCE — ddc-customers call indices.
Staging inserts `deposit_for` at index 3, shifting four extrinsics down. A
caller targeting `unlock_deposit` at index 3 would instead invoke a *deposit*.
Renumbered so Mainnet's existing encoding is preserved:
0 create_bucket 1 deposit 2 deposit_extra 3 unlock_deposit
4 withdraw_unlocked_deposit 5 set_bucket_params 6 remove_bucket
7 deposit_for <- appended
ddc-clusters needed no such fix; it appends `join_cluster` at 5 correctly.
fee-handler: staging's version taken, which drops the `fee_distribution_config`
extrinsic and its storage, and renumbers `burn_native_tokens` 2 -> 1. This is a
deliberate rework on staging's side, not an accident — it also adds a WeightInfo
associated type, benchmarks, tests and a mock that master never had. Mainnet has
never set the config (storage reads None) and master never wires FeeHandler into
anything, so its splitting logic has never run in production. See
docs/MAINNET_LAUNCH_PORT_RECORD.md for the open question this leaves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
New top-level directory and workspace member, absent from master entirely. Its Cargo.toml carries its own `ddc-primitives` git reference, repointed to mainnet-launch along with the rest — easy to miss, and a single reference left on "staging" would produce a duplicate crate. Wired in both runtimes by `CereChainExtension` (next commit): the contract calls func_id 1 to fetch the DdcPayouts pallet account so it can authorise incoming `charge` calls. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
Of 93 hunks in runtime/cere/src/lib.rs, 49 were pure import-path churn
(staging drops the `polkadot_sdk::` prefix) and were rejected. cere-dev: 46 of
85. Result is +276/-86 and +190/-36 where a bulk copy would be +474/-304 and
+360/-236.
DELIBERATE DIVERGENCE — DdcVerification appended at index 54, not inserted at 40.
Staging inserts it at 40, shifting every pallet below. Three storage items
encode a pallet index in their SCALE-encoded *values*, and none of them change
storage version, so a version sweep cannot see the damage:
Balances::Holds (RuntimeHoldReason) 24 accounts, 17,164,730.5167 CERE
TechComm::ProposalOf (RuntimeCall) 1 proposal, would become un-closeable
Scheduler::Agenda (OriginCaller) 1 of 3 slots, would silently read empty
Appending leaves every existing index untouched, so none of those need a
migration. Verified programmatically: zero master indices moved.
MASTER-ONLY CONTENT PRESERVED. Each would have been lost by a bulk copy:
StateMachine::Polkadot(3367) staging targets Kusama(4009), the Gargantua
TESTNET coprocessor — wrong for Mainnet's bridge
TechComm ismp origins staging collapses EitherOfDiverse<EnsureRoot,
EnsureMembers<TechCommCollective,3>> to EnsureRoot
Babe DisabledValidators bypass #[cfg(feature = "try-runtime")] type
DisabledValidators = (); absent on staging. This
is what lets try-runtime drive multi-block
migrations — its absence stalled evaluation round 1
pallet-ismp/ismp-grandpa
runtime-benchmarks forwards dropped by staging in both runtime Cargo.tomls
pallet-ddc-clusters-gov/std staging drops the forward while keeping the
dependency — appears to be a staging bug
Versions: spec_version 73159 (master's 73158 + 1). transaction_version 26 — this
MUST bump, because ddc-customers gained an extrinsic and fee-handler lost one.
Migrations: staging's `migrations` module is included and wired to NOTHING —
`migrations::Unreleased` has zero references, `pallet_migrations::Config::
Migrations` is `()`. The "no migrations run" property is enforced by the type, so
a dry run can prove it. Executive's tuple keeps master's two self-gating FRAME
migrations; master's two RemovePallet cleanups are dropped, as master's own
comment instructed — they shipped with 73158 and both prefixes read empty on
Mainnet.
API changes that surfaced as compile errors, not silent breakage:
pallet-pool-withdrawal-fix gained RuntimeEvent; pallet-ddc-staking merged
NodeVisitor + NodeCreator into NodeManager.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
An API-surface diff over node/ found nothing on master that staging lacks, so
staging's sources were taken directly.
Two exceptions, hand-merged rather than taken:
node/cli/Cargo.toml keeps master's `sc-cli = { default-features = true }`
rocksdb workaround alongside staging's ddc-dac-host dep.
Also takes default = ["cli", "cere-native",
"cere-dev-native"], which builds the Mainnet native
runtime by default.
node/rpc/Cargo.toml left at master's — its entire diff was comment
relabelling.
What staging brings: `ocw_heap_pages` / `--ocw-heap-pages N`, giving off-chain
workers a dynamic heap up to 512 MB separate from the on-chain 128 MB. It exists
because DdcVerification runs OCWs, so it belongs with that pallet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
MAINNET_LAUNCH_PORT_INVENTORY.md — why this is a manual port rather than a merge (merge base ffd39b4; 201 commits on master, 1,058 on staging; both migrated to polkadot-sdk 2512.3.2 independently, so a trial merge produced 72 conflicts), what must not move, and the branch/PR plan. MAINNET_LAUNCH_PORT_RECORD.md — what was actually taken from staging, what was kept from master and why, the deliberate divergences, and the verification. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
All five Cere external crates resolve through `branch = "mainnet-launch"`. No `branch = "staging"` reference remains, so there is no duplicate-crate hazard — cargo keys a git source by URL *and* branch, and a mixed label would resolve the same commit as two distinct crates. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
`pallet-ddc-payouts::Config` requires an `InspectorAuthority`. Testnet binds it
to `DdcVerification` -- but bringing that pallet in also forces a fifth
`SessionKeys` entry, `UpgradeSessionKeys` (a flat 50% of `max_block`), and
starts its off-chain worker on Mainnet every tenth block.
DDC payouts have not run on Mainnet since 2024-06-30. All 58 billing reports
predate that; 53 are Finalized and 5 have been stuck mid-charge for two years.
Freezing a facility that has been dormant for 781 days costs nothing, and buys
a materially smaller first release.
So `NoInspectorAuthority` denies everything: payout extrinsics reject with
`Unauthorized`. No migration calls this trait -- all 21 call sites are in the
payout processing path -- so the storage migrations are unaffected.
Removed with it:
- DdcVerification pallet (was index 54) and its Config
- OldSessionKeys, the ddc_verification key, transform_session_keys.
SessionKeys is now byte-identical to master's, so validators' existing keys
decode unchanged and no session-key migration is needed.
- UpgradeSessionKeys, and with it the 50% of max_block that made the upgrade
block exceed the weight limit (evaluation round 2 measured 131.20%)
- pallet-ddc-verification from both runtime Cargo.tomls; it was only a
dev-dependency of ddc-payouts, so nothing else required it
- the ddc_verification session key from node/service/src/chain_spec.rs
REPLACE the stub with DdcVerification in the release that re-enables payouts.
`add_inspector` returns an error under runtime-benchmarks rather than Ok(()),
so a benchmark cannot silently succeed against an authority that denies all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdHHPhppLJhGdBfySiJiJY
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The Mainnet-targeted port of the DDC work from
staging. Base ismainnet-launch, which ismasterunchanged.They arrive present and provably inert:
migrations::Unreleasedhas zero references andpallet_migrations::Config::Migrationsis(), so a dry run can demonstrate nothing runs. Wiring and fixing them is a separate PR.That separation matters, because the storage-migration evaluation found the migration set is not safe to apply to Mainnet today. Dry-run against a Mainnet fork at block 26,666,018 found six blockers, including:
DdcPayoutsv2skips all 58 Mainnet billing reports (decode fails,translateskips rather than deletes);v3then reads the same bytes under a misaligned layout that is also 167 bytes, succeeds, and writes values inflated ~4.2e12× intoPayoutReceipts. try-runtime's pre/post checks pass vacuously — both count 0.DdcCustomersv5_mbmemptied 443 of 535 ledgers into free balances with no deposit registered — Mainnet has zero contracts deployed, and the deposit contract cannot exist until the clusters chain reaches v4.v4_mbmalso copies rather than moves, stranding all 535 under the old prefix.DdcClustersv3's guard iscurrent_version == 3against an in-code version of 6 — unsatisfiable, so the whole 2→6 chain stalls at 2.DdcNodesv0_v2) is commented out.This PR removes three further blockers by construction — the
Balances::Holds(17.16M CERE),TechComm::ProposalOfandScheduler::Agendavalue-shape breakage — by appendingDdcVerificationat index 54 rather than inserting at 40. Those needed no migration once the indices stop moving.One thing this PR improves for the evaluation itself: keeping master's
#[cfg(feature = "try-runtime")] type DisabledValidators = ()means a runtime built from this line can drive multi-block migrations to completion. Building fromstagingcould not — that is what stalled evaluation round 1.Full analysis:
docs/MAINNET_STORAGE_MIGRATION_DRYRUN.md, and the live memory-bank page02-cere-protocol/blockchain/mainnet-storage-migration-evaluation.md.Why it is a port and not a merge
masterandstagingare two long-lived lines, not one ahead of the other:ffd39b4cmasternot instagingstagingnot inmasterBoth migrated to
polkadot-sdk = 2512.3.2independently, so a trialgit merge origin/stagingproduced 72 conflicted files in code neither side solely owns. Every change here was adopted deliberately, file by file and hunk by hunk, withmasteras the default.runtime/cere/src/lib.rsruntime/cere-dev/src/lib.rsOf 93 hunks in
runtime/cere/src/lib.rs, 49 were pure import-path churn (stagingdrops thepolkadot_sdk::prefix) and were rejected.cere-dev: 46 of 85.The part that matters most — master-only content preserved
Each of these would have been silently replaced by a bulk copy. The first three are Mainnet-correctness issues.
StateMachine::Polkadot(3367)StateMachine::Kusama(4009)EitherOfDiverse<EnsureRoot, EnsureMembers<TechCommCollective, 3>>EnsureRootonly#[cfg(feature = "try-runtime")] type DisabledValidators = ()pallet-ismp/ismp-grandpabenchmark forwardspallet-ddc-clusters-gov/stdforwardsc-cli = { default-features = true }Deliberate divergence from both branches
DdcVerificationappended at index 54, not inserted at 40Staging inserts it at 40, shifting every pallet below. Three storage items encode a pallet index in their SCALE-encoded values, and none change storage version — so a version sweep cannot see the damage:
Balances::Holds(RuntimeHoldReason)TechComm::ProposalOf(RuntimeCall)Scheduler::Agenda(OriginCaller)Appending leaves every existing index untouched, so none of those need a migration. Verified programmatically: zero master indices moved.
ddc-customerscall indicesStaging inserts
deposit_forat index 3, shifting four extrinsics — a caller targetingunlock_depositat 3 would instead invoke a deposit. Renumbered sodeposit_forlands at 7 and Mainnet's encoding is preserved.Versions
spec_version73159 (master + 1),transaction_version26 — the latter must bump, sinceddc-customersgained an extrinsic andfee-handlerlost one.One genuine feature loss:
fee-handlerStaging's version is taken, which drops
fee_distribution_configand its storage, and movesburn_native_tokensfrom index 2 to 1.This is a deliberate rework on staging's side, not an accident — it also adds a
WeightInfotype, benchmarks, tests and a mock thatmasternever had. Buthandle_feediffers in behaviour: master splits fees treasury/fee-pot per stored config; staging sends everything to the fee pot.Relevant facts: the config has never been set on Mainnet (
None), andmasternever wiresFeeHandlerinto anything — so its splitting logic has never run in production. Staging does wire it, so this PR makes fee handling active for the first time.Open question for whoever owns fee handling: is the configurable treasury split still wanted? If yes it is ~40 lines on top of staging's version. Where payout fees go is an economic decision, not a porting one.
Verification
One test failure, in
pallet-ddc-clusters-gov's own mock runtime:Our
pallets/ddc-clusters-govis byte-identical to staging's (empty diff), and the test exercises the pallet's own mock, not the assembled runtime — so it is very likely pre-existing. I could not confirm that directly: building that pallet standalone on astagingworktree fails earlier, on an unrelatedpallet-ddc-stakingunresolved module stderror. Treat as unconfirmed rather than dismissed.Review guidance
The commits are ordered and each is self-contained: dependency swap → pallets → contract → runtimes → node → docs → lock. The runtime commit (
7e45a665) is where the judgement calls are and is worth the most attention.docs/MAINNET_LAUNCH_PORT_RECORD.mdrecords every decision with its reasoning.Follow-ups
ddc-payouts' PR is held pending itspallet-ddc-customersdev-dep being repointed at this branchddc-clusters-govintegrity test🤖 Generated with Claude Code