Hardforks
HF16 / 4.2.0 is active since 2026-10-31 00:00 UTC. The rules in force are the 1-second slot, QC/TC and the 2-chain LIB — see HF16. The 3-second/14-confirmation descriptions below are the pre-HF16 rules; they survive in code as fallback paths, not as the HF16 quorum.
A hardfork is a network upgrade that changes consensus rules. All nodes must upgrade before the scheduled activation timestamp; nodes running old software will diverge from the network after activation.
Activation Mechanism
Each hardfork has:
- A unique number (N).
- A Unix timestamp — the earliest wall-clock time at which the hardfork can activate.
- A validator vote supermajority — >80% of the current validator set must signal the new hardfork version via block-header
hardfork_version_vote(normally emitted automatically by the validator plugin).
Both conditions must be satisfied simultaneously. Validators can block an unwanted hardfork by withholding their version vote even after the scheduled timestamp.
Hardfork History
| # | Version | Key changes |
|---|---|---|
| 1–10 | 1.x – 2.x | Foundation, social graph, energy system, committee, subscriptions |
| 11 | 3.0.0 | — |
| 12 | 3.1.0 | Fork collision metrics, vote-weighted fork comparison, emergency consensus mode, NTP improvements |
| 13 | 3.2.0 | Validator reward sharing with vote-proportional distribution |
HF12 Summary
HF12 (version 3.1.0) introduced:
- Fork collision counter —
fork_collision_countandlast_fork_collision_block_numadded todynamic_global_property_object. Observable viaget_dynamic_global_properties. - Vote-weighted fork comparison (
compare_fork_branches()) — fork selection uses total delegated SHARES per validator branch + 10% bonus for longer chain. - Emergency consensus mode — activates automatically after 20 minutes with no blocks; "committee" account takes all 21 slots. See Emergency Consensus.
- Minority fork auto-resync — validator plugin detects node isolation (21 consecutive own-blocks) and rolls back to LIB.
- NTP improvements — dedicated NTP client with configurable servers, interval, and round-trip threshold.
HF13 Summary
HF13 (version 3.2.0) introduced:
Validator reward sharing: part of each block's validator reward is redistributed proportionally to the accounts that voted for that validator (by their SHARES vote weight).
- New field on
validator_object:reward_percent— fraction of block reward shared with voters (0–10000 basis points). - New virtual operation:
validator_reward_virtual_operation— fired once per reward distribution. - Set via block-header
hardfork_version_vote(normally emitted automatically by the validator plugin).
Implementing a New Hardfork
Step 1: Create hardfork definition file
libraries/chain/hardfork.d/N.hf:
#ifndef CHAIN_HARDFORK_N
#define CHAIN_HARDFORK_N N
#define CHAIN_HARDFORK_N_TIME 1234567890 // Unix timestamp — must be in the future
#define CHAIN_HARDFORK_N_VERSION hardfork_version(3, N, 0)
#endifStep 2: Bump constants
libraries/chain/hardfork.d/0-preamble.hf:
#define CHAIN_NUM_HARDFORKS Nlibraries/protocol/include/graphene/protocol/config.hpp (if protocol-visible):
#define CHAIN_VERSION (version(3, N, 0))Step 3: Schema version
If any chainbase object layout changes (new fields, removed fields, resized types), increment CHAIN_SCHEMA_VERSION in config.hpp:
#define CHAIN_SCHEMA_VERSION uint32_t(N)The chain plugin checks this at startup. A mismatch wipes shared_memory.bin before opening, preventing corrupt reads from old layouts.
New fields should always have zero-value defaults to avoid migration code:
uint16_t my_new_field = 0;Step 4: Wire into database.cpp
init_hardforks():
FC_ASSERT(CHAIN_HARDFORK_N == N);
_hardfork_times[N] = fc::time_point_sec(CHAIN_HARDFORK_N_TIME);
_hardfork_versions[N] = hardfork_version(CHAIN_HARDFORK_N_VERSION);apply_hardfork() case:
case CHAIN_HARDFORK_N: {
// Migration if any. Leave empty with a comment if zero defaults cover it.
break;
}Step 5: Operation and evaluator (if new op)
- Add struct to
chain_operations.hppwithvalidate()and authority getters. - Add to the
static_variantinoperations.hpp. - Declare
DEFINE_EVALUATOR(my_new_op)inchain_evaluator.hpp. - Implement
do_apply()in a.cppevaluator file — always checkASSERT_REQ_HF(CHAIN_HARDFORK_N, ...)first. - Register in
initialize_evaluators()indatabase.cpp.
Step 6: Plugin updates
| Plugin | What to update |
|---|---|
account_history | Add impact extractor for any new virtual operation |
validator_api | Add new fields from validator_object to validator_api_object |
snapshot | Add new chainbase objects to serialize_state / load_snapshot |
Schema Version Lifecycle
Fresh node (no existing data):
stored = 0, compiled = N → mismatch
wipe shared_memory (no-op if absent)
write schema_version = N
genesis → normal startup
Upgrade (old binary had version M < N):
stored = M, compiled = N → mismatch
wipe shared_memory.bin
write schema_version = N
db.open() → revision mismatch exception
→ auto-recovery: snapshot import + dlt_block_log replay
Normal restart:
stored = N, compiled = N → match
db.open() proceeds normallyKey files:
config.hpp—CHAIN_SCHEMA_VERSIONplugins/chain/plugin.cpp— schema check and wipe logic<data_dir>/schema_version— plain text file with current version
Deployment Checklist
- [ ]
CHAIN_NUM_HARDFORKSincremented - [ ]
CHAIN_VERSIONbumped (if protocol-visible) - [ ]
CHAIN_SCHEMA_VERSIONincremented (if any chainbase object layout changed) - [ ] Hardfork
.hffile created with future activation timestamp - [ ] All new fields have zero defaults;
apply_hardforkcomment explains why no migration needed - [ ] New evaluator registered in
initialize_evaluators() - [ ] New virtual op registered in
account_historyplugin - [ ]
validator_api_objectupdated ifvalidator_objectchanged - [ ] Snapshot plugin updated if new chainbase objects added
See also: Fair-DPOS, Emergency Consensus, Snapshots.
Release 4.x
| HF | Version | Scope |
|---|---|---|
| 14 | 4.0.0 | Prediction markets and governance |
| 15 | 4.1.0 | PM audit fixes, agent access and permanent key history |
| 16 | 4.2.0 | QC/TC, 1-second slot, DAO ceiling and no-contest fee freeze; activation details |
| 17 | 4.3.0 | Emergency exit: threshold = quorum of the schedule size measured on entry (floored at half the maximum), decided before the hybrid override |
| 18 | 4.4.0 | Timeout votes name the block whose QC they claim: a certificate is built only from votes on one block and is usable only next to that block's parent QC |