11 KiB
Developing and Activating CLP Implementations
This guide explains how developers prepare code for an approved Contractless Proposal and make that code activate at the block selected by on-chain governance.
Governance does not dynamically download or execute code. Developers must ship a candidate node release containing the proposed implementation before activation voting finishes.
That candidate release must remain compatible with the existing chain until the approved activation block is reached.
Development Sequence
The expected sequence is:
- A finalized CLP document is committed through a ProposalKey transaction.
- Eligible nodes approve the proposal with ProposalVote transactions.
- Developers implement the approved behavior.
- Developers publish an implementation document describing the exact candidate.
- Node operators install candidate software containing both the old and proposed rules.
- Eligible nodes vote on the implementation document.
- When approval reaches 85 percent, activation is scheduled 5,760 blocks later.
- Candidate nodes begin applying the new rules at the scheduled block.
Implementation development may begin before proposal approval, but activation votes are rejected until the proposal has been approved.
The Implementation Document
The implementation document is distinct from the proposal.
The proposal describes what the network should change. The implementation document identifies the code that claims to implement that decision.
It should include:
- The ProposalKey being implemented.
- Relevant repository and branch.
- Every commit included in the implementation.
- Files and consensus paths affected.
- New validation behavior.
- Historical behavior that must remain available.
- Database or record-format changes.
- Network-protocol compatibility.
- Required node, wallet, or application upgrades.
- Testing performed.
- Known limitations and migration instructions.
The exact raw bytes of this document are hashed when an activation vote is created. Once activation voting begins for that hash, changing the document creates a different implementation candidate.
Do not silently replace or edit a document that already has votes. Publish a new document and allow operators to vote on the new development hash.
Creating an Activation Candidate
There is no separate transaction that registers an implementation candidate.
The first activation vote for a development hash creates the candidate. Use:
./create_activation_vote_tx <proposal_key> <implementation_file> <development_location> <yes|no>
The tool:
- Reads the exact implementation-document bytes.
- Hashes those bytes with Skein-256.
- Associates that development hash with the approved ProposalKey.
- Records the document location.
- Creates and signs an activation vote.
All voters for the same candidate must use the exact same implementation document bytes and ProposalKey. They should also use the same canonical development location so the recorded location remains unambiguous.
Candidate Software Must Be Dormant
Do not release candidate software that immediately applies new consensus rules.
Before activation, upgraded and unupgraded nodes must continue validating the chain under the same existing rules. The new path must remain dormant until governance records its activation height.
A typical validation decision should follow this shape:
if implementation_is_active(db, proposal_key, expected_development_hash, block_height)? {
validate_with_new_rules(transaction, block_height)
} else {
validate_with_old_rules(transaction, block_height)
}
block_height must be the height of the block being validated or replayed. Do not use only the node's current tip height.
That distinction is essential during:
- Initial synchronization.
- Full historical validation.
- Orphan correction.
- Chain rollback and replay.
- Snapshot verification.
A block below the activation height must always use the old rules, even when being checked years after activation.
Reading the Activation Record
Approved activation schedules are stored in the Sled tree named:
consensus_activations
The key is the decoded 32-byte ProposalKey.
The value is 136 bytes:
| Offset | Length | Meaning |
|---|---|---|
0 |
32 | Approved implementation document hash |
32 |
4 | Activation block as little-endian u32 |
36 |
100 | Space-padded ASCII implementation-document location |
Code selecting consensus behavior must verify both:
- The ProposalKey matches the rule being activated.
- The stored development hash matches the implementation compiled into that release.
Conceptually:
fn implementation_is_active(
db: &sled::Db,
proposal_key_hex: &str,
expected_development_hash_hex: &str,
block_height: u32,
) -> Result<bool, String> {
let proposal_key = hex::decode(proposal_key_hex)
.map_err(|err| format!("Invalid proposal key: {err}"))?;
let expected_hash = hex::decode(expected_development_hash_hex)
.map_err(|err| format!("Invalid development hash: {err}"))?;
let tree = db
.open_tree("consensus_activations")
.map_err(|err| format!("Could not open activation schedules: {err}"))?;
let Some(value) = tree
.get(proposal_key)
.map_err(|err| format!("Could not read activation schedule: {err}"))?
else {
return Ok(false);
};
if value.len() != 136 {
return Err("Stored activation schedule has an invalid length.".to_string());
}
if value[0..32] != expected_hash {
return Ok(false);
}
let activation_height = u32::from_le_bytes(
value[32..36]
.try_into()
.map_err(|_| "Could not decode activation height.")?,
);
Ok(block_height >= activation_height)
}
This illustrates the current stored format. Shared production code should centralize this parsing rather than duplicating raw offsets throughout validation modules.
Choosing the Correct Boundary
If an implementation activates at block 50,000:
- Blocks
0through49,999use the old behavior. - Block
50,000and every later block use the approved behavior.
For a later proposal that revises the same rule at block 100,000:
- Blocks
0through49,999use the original behavior. - Blocks
50,000through99,999use the first approved implementation. - Blocks beginning at
100,000use the revised implementation.
Do not delete historical validation paths merely because a newer implementation activated. Nodes still need them to validate old blocks.
Apply the Boundary Everywhere
A consensus change is incomplete if its activation check is added to only the normal transaction-submission path.
Review every path that interprets or validates the affected rule:
- Mempool admission.
- Received-block validation.
- Locally created block saving.
- Initial chain synchronization.
- Orphan comparison and replay.
- Rollback and undo handling.
- Pending-effects calculation.
- Database reconstruction.
- Balance and ownership rebuilding.
- RPC validation tools.
- CLI validation tools.
The same block must produce the same result regardless of whether it was mined locally, downloaded during sync, received live, or replayed during orphan correction.
Data-Format Changes
Changing fixed transaction bytes, block bytes, database keys, or serialized records requires special care.
New code may need to parse both formats:
if block_height < activation_height {
parse_legacy_format(bytes)
} else {
parse_activated_format(bytes)
}
Do not reinterpret historical bytes using a new structure.
If a new transaction type is introduced, nodes may need to recognize its byte length before activation while rejecting its use until activation. Otherwise candidate nodes may disconnect from peers or fail to parse candidate traffic before the activation boundary.
Network-protocol changes should be designed so upgraded and unupgraded nodes can remain connected during the 5,760-block activation delay whenever possible.
Activation Voting
Node operators should verify:
- The proposal was approved.
- The implementation document matches the proposal.
- The listed commits match the reviewed source.
- Their built binary contains those commits.
- The implementation remains dormant before activation.
- Historical validation still passes.
- Sync, rollback, and orphan correction work across the boundary.
They can then create an activation vote:
./create_activation_vote_tx \
<proposal_key> \
<implementation_document> \
<repository_document_location> \
yes
Voting yes approves the exact development hash produced from that document. Voting no rejects that candidate, not every possible future implementation of the proposal.
Confirming the Schedule
Use:
./lookup_governance_proposal <proposal_key>
For each implementation candidate, the response identifies:
development_hashdevelopment_locationstatecurrent_votefinal_voteactivation_block
The implementation state is:
open: Activation voting has not finalized.scheduled: Voting finalized, but the activation block has not arrived.active: The local chain height has reached or passed the activation block.
The reported state is informational. Consensus code must still compare the height of the specific block being validated against the stored activation block.
Release Requirements
Before asking operators to vote for activation:
- Publish reproducible source and build instructions.
- Publish the implementation document.
- Identify the exact development hash operators should expect.
- Provide tests that cross the activation boundary.
- Test startup and sync from before the activation block.
- Test rollback from after activation to before activation.
- Test replay from before activation through activation.
- Test mixed candidate and existing nodes during the delay.
- Confirm every consensus path uses the same boundary.
Node operators need enough time to review, build, install, and test the candidate before voting completes.
Failure Behavior
Consensus activation checks must fail safely.
- A malformed activation record must produce an error, not silently activate.
- A different development hash must not activate the compiled implementation.
- A missing schedule must retain the old behavior.
- Arithmetic around activation heights must not wrap.
- Historical validation must never depend on wall-clock time.
- Repository availability must not determine whether an already-recorded activation applies.
The on-chain ProposalKey, approved development hash, and activation block are the authoritative consensus records. The repository explains those records and makes their source auditable, but it does not activate them by itself.