9.0 KiB
Developing and Activating CLP Implementations
This guide explains how developers make code changes that activate only after an implementation has been approved by Contractless governance.
Governance does not download or execute source code. Developers release candidate software containing the proposed changes before activation. That software keeps the new behavior disabled until the approved activation block.
Development Process
The complete process is:
- A CLP is submitted through a ProposalKey transaction.
- Eligible node operators approve the proposal.
- Developers implement the approved proposal.
- Developers publish an implementation document identifying the exact code and commits.
- Node operators install and test candidate software containing both the original and proposed behavior.
- Eligible node operators vote on that implementation.
- An implementation reaching 85 percent approval is scheduled to activate 5,760 blocks later.
- Candidate software begins using the new behavior at the activation block.
Candidate software must be available before activation voting finishes. Otherwise node operators cannot verify what they are being asked to activate.
Implementation Document
The implementation document identifies the exact code presented for activation. It is separate from the original proposal.
It should include:
- The ProposalKey being implemented.
- The source repository and branch.
- Every relevant commit.
- The files and consensus paths changed.
- The original and proposed behavior.
- Transaction, block, or database format changes.
- Compatibility and migration requirements.
- Tests performed.
Activation votes hash the exact bytes of this document. Editing it creates a different implementation hash and therefore a different activation candidate.
Do not alter an implementation document after voting begins. Publish a new document for revised code.
Activation Status
All consensus changes use one shared function:
use crate::common::governance::implementation_activation_status;
let (implementation_approved, activation_block) =
implementation_activation_status(
db,
PROPOSAL_KEY,
DEVELOPMENT_HASH,
)?;
The function returns:
(false, 0)
when that proposal has no approved implementation.
It returns:
(true, activation_block)
when the exact development hash was approved and scheduled.
It returns an error when the governance record is malformed or the proposal scheduled a different implementation than the software expects.
PROPOSAL_KEY is the ProposalKey transaction hash from the CLP .id file.
DEVELOPMENT_HASH is the hash of the exact implementation document approved by node operators.
Standard Activation Check
Every affected code path uses the same condition:
if implementation_approved && block_height >= activation_block {
// New behavior
} else {
// Original behavior
}
The comparison must be:
block_height >= activation_block
The original rules remain active below the activation block. The new rules begin at the activation block.
If activation is scheduled for block 50,000:
- Block
49,999uses the original behavior. - Block
50,000uses the new behavior. - Block
50,001uses the new behavior.
Which Height to Use
block_height must be the height of the specific block being processed.
Do not automatically use the node's currently saved chain height.
For example, a node at height 100,000 may be replaying block 40,000. Block 40,000 must use the rules that applied at block 40,000, not the rules active at the current chain tip.
| Operation | Height to compare |
|---|---|
| Validating a received block | Height of the received block |
| Saving a block | Height of the block being saved |
| Parsing historical transactions | Height containing those transactions |
| Initial synchronization | Height of each downloaded block |
| Orphan replay | Height of each block being replayed |
| Undo or rewind | Height of the block being undone |
| Creating the next local block | Height of the block being created |
Changing Existing Behavior
Use an if and else when replacing an existing rule:
if implementation_approved && block_height >= activation_block {
validate_with_new_rules(transaction)?;
} else {
validate_with_original_rules(transaction)?;
}
Both paths must remain in the software. Historical blocks still require the original rules after activation.
Adding New Behavior
Reject new functionality before activation:
if !implementation_approved || block_height < activation_block {
return Err("This feature has not been activated.".to_string());
}
validate_new_feature(transaction)?;
This prevents miners or users from introducing the new behavior before the approved block.
Changing a Structure
Rust structure definitions cannot appear or disappear at runtime. Candidate software contains both formats and chooses the correct parser:
if implementation_approved && block_height >= activation_block {
NewTransaction::from_bytes(bytes)
} else {
OriginalTransaction::from_bytes(bytes)
}
Never parse historical bytes using a structure that did not apply when the block was created.
Saving Records
Saving follows the same condition:
if implementation_approved && block_height >= activation_block {
save_with_new_rules(transaction, db)?;
} else {
save_with_original_rules(transaction, db)?;
}
The locally mined, live downloaded, initial-sync, and orphan-replay paths must produce the same records for the same block.
Undoing and Rewinding
Undo logic must use the height of the block being undone:
let block_height = height_of_block_being_undone;
if implementation_approved && block_height >= activation_block {
undo_new_behavior(transaction, db)?;
} else {
undo_original_behavior(transaction, db)?;
}
If activation occurred at block 50,000:
- Undoing block
50,000uses the new undo behavior. - Undoing block
49,999uses the original undo behavior.
This allows orphan correction to move backward and forward across an activation boundary without corrupting chain state.
Later Revisions
A later CLP may revise an earlier consensus change.
For example:
- Original rules apply through block
49,999. - The first implementation applies from block
50,000through99,999. - A revised implementation applies beginning at block
100,000.
That code checks both activation boundaries:
if second_approved && block_height >= second_activation_block {
apply_second_rules(...)
} else if first_approved && block_height >= first_activation_block {
apply_first_rules(...)
} else {
apply_original_rules(...)
}
Do not remove older rule paths. Nodes require them for complete historical validation.
Paths Developers Must Review
Apply the activation condition everywhere the changed consensus rule is interpreted:
- Transaction parsing.
- Mempool validation.
- Received-block validation.
- Local block saving.
- Initial synchronization.
- Record and balance updates.
- Pending effects.
- Orphan replay.
- Block undo and chain rewinds.
- Database reconstruction.
- Validation CLI tools.
A change is incomplete if one path applies the new rule while another still applies the original rule at the same block height.
Creating an Activation Vote
After reviewing and installing the candidate implementation, eligible node operators use:
./create_activation_vote_tx \
<proposal_key> \
<implementation_document> \
<repository_document_location> \
yes
The first vote for a ProposalKey and development hash creates that implementation candidate.
Voting yes approves the exact implementation-document hash. Voting no rejects that candidate without rejecting every future implementation of the proposal.
Checking Activation
Use:
./lookup_governance_proposal <proposal_key>
The response shows each implementation candidate, its development hash, vote totals, activation state, and activation block.
The possible states are:
open: The implementation has not reached approval.scheduled: It was approved, but its activation block has not arrived.active: The local chain tip has reached or passed its activation block.
The displayed state is informational. Consensus code uses implementation_activation_status and compares the specific block being processed with the returned activation block.
Testing Requirements
Before asking node operators to approve an implementation, test:
- Blocks immediately before and at activation.
- Blocks created after activation.
- Synchronization from before the activation block.
- Historical validation across the activation block.
- Undo from after activation to before activation.
- Replay from before activation through activation.
- Orphan correction across the boundary.
- Candidate nodes operating before activation.
- Malformed or mismatched activation records.
The same block must produce the same result whether it was mined locally, received live, downloaded during sync, or replayed during orphan correction.