docs(blockchain) Channel Participation in PoS#364
Conversation
davidrusu
left a comment
There was a problem hiding this comment.
My main reservation is that I don't think we need ChannelStakeTransfer, It seems that ChannelStakeAssignation is all we need
Co-authored-by: davidrusu <davidrusu.me@gmail.com>
madxor
left a comment
There was a problem hiding this comment.
Looks good I left a couple of small comments. However, I think it might be better to extract locked->service rename into a separate PR, and bump the minor version of all updated documents (in that PR).
| | Holder of the note's `ZkPublicKey` (by default, the depositor) | Use the note to create a PoL and earn its leader rewards | Spend the note, withdraw it, reassign it, or use it as service stake | | ||
| | Channel sequencers (owner of the note) | Reassign the note to a different `ZkPublicKey` (`CHANNEL_TRANSFER`) and spend it to fund withdrawals (`CHANNEL_WITHDRAW`), both without `ZkSignature` verification | Use the note as service stake, or earn PoL rewards without first assigning the note to their own key | | ||
|
|
||
| This makes delegated staking explicit. Sequencers can assign a channel note to their own `ZkPublicKey` and earn the Proof of Leadership rewards it produces, but those rewards always follow the assigned key, so the channel earns nothing merely by owning the note. Conversely, ownership never leaving the channel is exactly what lets sequencers redelegate value or cover withdrawals at any time without a user signature. |
There was a problem hiding this comment.
Maybe we could emphasize (again) at the end (as a warning paragraph) that the channel note handling logic is fully defined by the channel and the depositor of the funds must be aware of that fact. I think it should be VERY clear for the reader that delegated funds are not his own and there is no return path if the funds are delegated to a scammy channel.
There was a problem hiding this comment.
added another paragraph:
Warning: a deposit is a transfer of custody. Depositors must understand that channel note handling is fully defined by the channel. Once a CHANNEL_DEPOSIT is executed the note belongs to the channel, and its sequencers can reassign it to any ZkPublicKey with CHANNEL_TRANSFER or release it to whoever they choose with CHANNEL_WITHDRAW, at any time and without any signature from the depositor. The ledger enforces no return path to the original depositor. Holding the note's ZkPublicKey grants PoS participation power only and never a claim on the value, so it confers no ability to recover the funds. A user who deposits into a dishonest or faulty channel has no on-chain recourse. Deposit only into channels you trust to honour their own withdrawal policy.
| ``` | ||
|
|
||
| Note that the Zone may wait for the deposit to be finalized before interpreting the deposit in order to guarantee that the deposit will occur on-chain and won't be removed due to reorganization of the chain. | ||
| Note that the Zone may wait for the deposit to be finalized before interpreting the deposit in order to guarantee that the deposit will occur on-chain and won't be removed due to reorganization of the chain. |
There was a problem hiding this comment.
Maybe we should make this limitation clearer. That in order to execute channel transfers a channel note must be finalized, and then the channel transfer operation can be done, which also needs to be finalized. So the waiting time will be approximately 2 epochs until channel note can be delegated to the sequencer. This is fine as the stake continues to protect the network by the original delegator.
There was a problem hiding this comment.
I reworked this paragraph as:
A Zone that credits a deposit in its own state must be sure the deposit really lands on-chain. If the Zone reflects the deposit through a CHANNEL_INSCRIBE posted in a separate Mantle Transaction, a reorganization can reorder the two so that the inscription is included while the deposit is not, leaving the Zone crediting funds it never received. Two options avoid this:
- Wait for the deposit to be finalized before interpreting it, at the cost of the finalization delay.
- Make the inscription conditional on the deposit, by including a
CHANNEL_TRANSFERthat consumes the deposited note in the same Mantle Transaction as the inscription. Mantle Transactions execute atomically, so the inscription is included only if the deposited note exists and is consumed. This removes the waiting period entirely.
The second option resets the ageing of the value. A CHANNEL_TRANSFER consumes its inputs and creates new notes, so the resulting note starts the ageing process again and must age before it can create a PoL. CHANNEL_DEPOSIT and CHANNEL_WITHDRAW keep the NoteId of the notes they touch and therefore never reset ageing.
82a85e9 to
b5a2902
Compare
[RFC] Channel Participation in PoS
Authors: Thomas Lavaur thomas@logos.co
Reviewer Orientation
Prerequisite: read Motivation and Proposal first. The whole PR hangs off one idea — bridged funds stop being an opaque per-channel scalar and become channel notes, on-ledger notes whose ownership stays with the channel while their PoS participation power is delegated through a
ZkPublicKey. Read that ownership-vs-participation split before the individual operations.channel_notesset in the Ledger; channel notes are PoL-only and can never back a service stake;assert_spendable/execute_spending/execute_addinggain achannel_idargumentCHANNEL_DEPOSITmarks inputs instead of re-minting (same spec)NoteId, value andZkPublicKeyand are registered inchannel_notesCHANNEL_TRANSFER(new op0x14) (same spec)ZkPublicKeys under a signature threshold, withoutZkSignature; opcode/reserved-range changeCHANNEL_WITHDRAWreleases channel notes (same spec)inputsonly, no outputs, no nonce; execution just unregisters the notes fromchannel_notes, turning them back into regular notes under their currentZkPublicKeywithdrawal_nonceremoved — UTXO determinism (same spec)op_iduniqueness is guaranteed by the UTXO chain; confirm noop_idcollision path remainstransfer_thresholdreplaceswithdraw_threshold(same spec)CHANNEL_WITHDRAWandCHANNEL_TRANSFERChannelTransferproduction;ChannelWithdrawbecomesChannelId Inputs;ChannelConfigrenames the threshold fieldEXECUTION_CHANNEL_TRANSFER_GAS; withdraw/transfer costs scale linearly with the threshold — flag: the gas formulas still namestake_manipulation_threshold, nottransfer_thresholdblock_root/block_idtest vectors onlyStatus tracker
Change log
sequencer_zk_idset now reset the stake of every one and not only the removed sequencers guaranteeing perpetual equality of stake. - The number of note minted per sequencer and epoch is increased from 0 to 10 (see Number of notes per epoch and sequencer)CHANNEL_STAKE_TRANSFERoperationCHANNEL_STAKE_ASSIGNATIONtoCHANNEL_TRANSFERand the channel threshold totransfer_threshold; simplifiedCHANNEL_WITHDRAWto release notes back to regular notes (inputs only).Motivation
The blockchain token currently serves two distinct purposes:
Today these uses are mutually exclusive. Once bridged, funds are represented as a per-channel scalar balance rather than as ledger notes, and therefore cannot participate in PoS. This shrinks the amount of stake that can contribute to consensus, weakening network security, and forces token holders and channels into an unnecessary trade-off between security and application use.
This RFC removes that trade-off by letting bridged funds retain PoS eligibility while remaining owned and spend-controlled by the channel. Rewards earned from PoS participation can be redistributed by channel sequencers, in whole or in part, to the users of the channel.
Proposal
Channel bridge funds are currently stored as a per-channel scalar token amount. Because a scalar is not a note, it cannot participate in Proof-of-Stake. This RFC represents channel bridge funds as on-ledger channel notes so they stay PoS-eligible while bridged.
The design rests on a strict separation between two rights that a normal note bundles together:
ZkSignature.ZkPublicKeygrants that key the right to create PoLs from the note, and nothing more. It confers no right to transfer the note or to lock it as a service stake.Concretely, a
CHANNEL_DEPOSITmarks the input notes as channel notes owned by the depositing channel, so the depositor keeps the PoS participation power they had before bridging. A newCHANNEL_TRANSFERoperation lets the sequencers consume and re-create channel notes under anyZkPublicKey(redelegating the participation power) under a signature threshold, andCHANNEL_WITHDRAWreleases channel notes back into regular notes on the channel's authority alone. The full normative changes are specified in Details.Discussion
Yield redistribution (out of scope)
This RFC does not specify what channels do with the PoS yield earned on bridged funds. The choice — fee rebates, user rewards, sequencer compensation, retention policies, PoS delegation to users — is a channel-level policy decision that can vary across channels. Because leadership and PoS participation are private, external observers cannot reliably attribute yield to specific sequencers, so any transparency or redistribution mechanism is expected to live at the application layer.
Decentralization vs security trade-off
Letting bridged funds participate in PoS increases total effective stake and improves financial security. The risk is that if most tokens concentrate in a few channels, PoS participation could concentrate around those channels' sequencers. The working assumption is that a healthy ecosystem of many distinct channels distributes bridged stake across independent users, mitigating this risk. Stake distribution should be monitored as channel adoption evolves.
A step toward Native Zones
This RFC introduces a unified ledger for Channels and Mantles that partitions notes by the channel they belong to, moving a step closer to Native Zones. The two new/updated operations let users move notes into an isolated channel and let sequencers agree on a new representation of their ledger, relocating notes without user authorization. Once Native Zones are in place and sequencers lose that control through zk integration, the ledger will be nearly ready to hand full control over notes back to users, provided they meet the zone requirements (enforced via zk).
Details
Ordered from highest to lowest review impact. All changes below are normative and land in the Bedrock v1.1 Mantle Specification unless noted otherwise.
1. Channel notes: a new, owned-by-channel note type (Mantle spec)
The ledger now tracks three kinds of notes: regular notes, locked notes (collateral for service declarations), and channel notes (channel bridge funds, PoS-eligible only). The Ledger gains a
channel_notesset mapping every active channelNoteIdto itsChannelId:class Ledger: # ... unchanged fields elided + channel_notes: dict[NoteId, ChannelId]Ownership and PoS participation power are split. Ownership of a channel note stays with the channel: the channel is recorded as the note's owner in
channel_notesand keeps full control over it. TheZkPublicKeycarried by the note does not confer ownership — it only delegates the note's value for PoL creation, so whoever controls that key is the one allowed to turn the note into a PoL. A channel note can be used for PoL creation only and can never back a service stake. Any mechanism that locks notes as service stake must reject channel notes.Spending and note bookkeeping become channel-aware — the helpers take an optional
channel_idso a channel note can only be spent in the context of its owning channel, and so marking/spending keepschannel_notesin sync:execute_spendingandexecute_addinggain the samechannel_idargument to pop or register notes inchannel_notes. The Bridging section of the spec now states this ownership/participation boundary explicitly, including what the delegated-key holder may do (create a PoL, earn its leader rewards) versus what the channel sequencers may do (reassign or withdraw the note without a user signature, but never use it as a service stake or earn PoL rewards without first assigning the note to their own key).2.
CHANNEL_DEPOSITmarks inputs as channel notes (Mantle spec)Before this change, a deposit increased an opaque per-channel scalar balance and left no ledger notes behind. The bridged value now lives on-ledger: a deposit marks its input notes as channel notes in place, without creating any new note. Each input keeps its
NoteId, value andZkPublicKey, and is registered inchannel_notesunder the depositing channel.Because the input notes keep their
ZkPublicKey, the depositor retains the PoS participation power over the deposited value until the sequencers reassign it.3.
CHANNEL_TRANSFER— new operation (Mantle spec)A new operation (opcode
0x14, formerly drafted asCHANNEL_STAKE_ASSIGNATION) lets the sequencers consume and re-create channel notes under anyZkPublicKey, redelegating their participation power without ownership ever leaving the channel. It is authorized by a signature threshold of the channel's accredited keys and requires noZkSignaturefrom the current key holder.Execution spends the input channel notes and adds the output notes as channel notes under the same channel, after checking the threshold signatures against the channel's accredited keys. This lets the sequencers freely re-partition and re-key the channel's bridged value.
4.
CHANNEL_WITHDRAWreleases channel notes (Mantle spec)The withdraw mechanism is reworked. The payload is now just the channel notes to release (
inputs), with no outputs and no nonce. Execution unregisters those notes fromchannel_notes: they stay in the ledger with the sameNoteId, value andZkPublicKey, but are no longer channel notes, so they become regular notes controlled by their currentZkPublicKeyholder.class ChannelWithdraw: channel: ChannelId + inputs: list[NoteId] - outputs: list[Note] - withdrawal_nonce: u32 # Nonce used to derive a channel OpIdLike
CHANNEL_TRANSFER, it is gated by atransfer_thresholdsignature set and needs noZkSignature. The intended user-exit flow is therefore two steps: sequencersCHANNEL_TRANSFERthe value onto the user'sZkPublicKey, thenCHANNEL_WITHDRAWreleases it so the user controls an ordinary, spendable note.5. Remove
withdrawal_nonce(Mantle spec)Every channel operation now consumes inputs, so the UTXO chain is never broken and each operation's payload is unique. That guarantees
op_iduniqueness on its own, making the withdrawal nonce redundant. It is removed from the payload, fromCHANNEL_CONFIGinitialization, and from withdraw validation:6.
transfer_thresholdreplaceswithdraw_threshold(Mantle spec)CHANNEL_CONFIGcarries a singletransfer_thresholdin place ofwithdraw_threshold. It serves the same role but now gates bothCHANNEL_WITHDRAWand the newCHANNEL_TRANSFER.7. Encoding updates (Mantle Transaction Encoding)
The encoding spec (version
1.5.0) adds the new operation toOpPayloadand reworks the channel productions:OpPayload = Transfer / ChannelConfig / ChannelDeposit / ChannelWithdraw / + ChannelTransfer / SDPDeclare / ...Reviewer flag: the encoding version-history row still describes the operation as
CHANNEL_STAKE_ASSIGNATION; the grammar itself usesChannelTransfer.8. Gas costs (Analysis: Gas Cost Determination)
The gas spec (version
1.5.0) addsEXECUTION_CHANNEL_TRANSFER_GAS(56) and expresses the withdraw and transfer costs as linear in the threshold:CHANNEL_WITHDRAW:EXECUTION_CHANNEL_WITHDRAW_GAS * transfer_thresholdCHANNEL_TRANSFER:EXECUTION_CHANNEL_TRANSFER_GAS * transfer_thresholdReviewer flag: in the gas spec the two formulas are still written against
stake_manipulation_threshold, and the version-history row still namesCHANNEL_STAKE_ASSIGNATION. These should be reconciled with the Mantle spec'stransfer_threshold/CHANNEL_TRANSFERbefore merge.PoS participation (aging, unchanged rule)
Channel notes follow the same aging rule as ordinary notes: a note first appears in the snapshot of the epoch following the one in which it was marked, and becomes PoS-eligible when the epoch after that snapshot starts. Once aged past the threshold, the note can be used by its current
ZkPublicKeyholder to create a PoL.Chores
block_rootandblock_idtest vectors (empty block, one-transaction-per-operation-kind, and a fullHeader) in the Cryptarchia Test Vectors annex. The Cryptarchia version table is not bumped on this branch (still1.0.2).OpIdper operation and for the Mantle Transaction Hash.Implementation
Grouped by area, in implementation order. Each task is independently checkable.
Channel notes (ledger state)
channel_notes: dict[NoteId, ChannelId]set to the Ledger.channel_idargument throughassert_spendable,execute_spending, andexecute_addingso a channel note is only spendable in the context of its owning channel, and so marking/spending keepschannel_notesin sync.Channel operations
CHANNEL_DEPOSIT: mark the input notes as channel notes in place (keepNoteId, value,ZkPublicKey), replacing the old per-channel scalar balance increment.CHANNEL_TRANSFER(new, opcode0x14): add the payload, the threshold-signature proof, and execution that spends the input channel notes and adds the output notes as channel notes under newZkPublicKeys.CHANNEL_WITHDRAW: takeinputs: list[NoteId]only and release the notes by popping them fromchannel_notes, on the channel's authority, withoutZkSignatureverification.Config and cleanup
withdraw_thresholdtotransfer_thresholdinCHANNEL_CONFIGand gate bothCHANNEL_WITHDRAWandCHANNEL_TRANSFERon it.withdrawal_noncefrom the withdraw payload, config initialization, and withdraw validation.Encoding and gas
ChannelTransfer, changeChannelWithdrawtoChannelId Inputs, rename the config threshold field, and share theInputs/Outputsproductions.EXECUTION_CHANNEL_TRANSFER_GASand make the withdraw and transfer costs scale linearly withtransfer_threshold(reconcile the lingeringstake_manipulation_thresholdnaming in the gas spec).CHANNEL_STAKE_ASSIGNATIONnaming in the encoding and gas spec version-history rows.Tests and verification
OpIdper operation, Mantle Transaction Hash, and the Cryptarchiablock_root/block_idvectors.op_iduniqueness without the nonce.Affected Specifications
channel_notesset;CHANNEL_DEPOSITmarks inputs in place; newCHANNEL_TRANSFER;CHANNEL_WITHDRAWreleases notes (inputs only);withdrawal_nonceremoved;withdraw_threshold→transfer_thresholdChannelTransfer; reworksChannelWithdraw/ChannelConfig. Version-history row still names the oldCHANNEL_STAKE_ASSIGNATIONEXECUTION_CHANNEL_TRANSFER_GAS; withdraw/transfer costs linear in the threshold. Formulas still referencestake_manipulation_thresholdand the old operation name — reconcile before mergeblock_root/block_idtest vectors only (non-normative). Version table not bumped (still 1.0.2); confirm whether a bump is expected