Hardforks in Neo: How the Hardfork Enum Tracks Protocol Upgrades

Hardforks in Neo are deterministic, block-height-based protocol upgrades that modify validation rules, fee schedules, and VM semantics, tracked symbolically through the Hardfork enum and activated via ProtocolSettings.

The neo-project/neo repository implements a structured hardfork system that allows the blockchain to evolve without breaking historical consensus. By mapping symbolic fork identifiers to specific activation heights, the protocol can conditionally enable new features while maintaining backward compatibility for legacy blocks.

The Hardfork Enum: Symbolic Protocol Versions

At the core of Neo's upgrade mechanism is the Hardfork enum defined in src/Neo/Hardfork.cs. This byte-backed enumeration provides a compact, canonical list of every named protocol upgrade.

Enum Structure and Naming Convention

The enum uses a consistent naming pattern combining the HF_ prefix with mythological creature names:

public enum Hardfork : byte
{
    HF_Aspidochelone,
    HF_Basilisk,
    HF_Cockatrice,
    HF_Domovoi,
    HF_Echidna,
    HF_Faun,
    HF_Gorgon
}

Each entry represents a specific set of consensus rule changes. By using a byte backing, the enum minimizes storage overhead when serialized in block headers or configuration files.

Configuring Activation Heights with ProtocolSettings

While the enum defines what forks exist, ProtocolSettings determines when they activate. Located in src/Neo/ProtocolSettings.cs, this class maintains the immutable configuration that governs node behavior.

The Hardforks Dictionary

The activation mapping is stored as an immutable dictionary:

public required ImmutableDictionary<Hardfork, uint> Hardforks { get; init; }

This structure maps each Hardfork enum value to a specific block height (as a uint). For example, { HF_Echidna → 1_000_000 } indicates that the Echidna fork rules take effect at block 1,000,000.

Validation and Default Behavior

The ProtocolSettings constructor enforces data integrity through several mechanisms:

  • EnsureOmmitedHardforks (lines 38-50): Automatically populates missing fork entries with height 0, treating them as always enabled for backward compatibility.
  • CheckingHardfork (lines 55-80): Validates that the fork sequence is continuous and that activation heights are monotonically increasing, preventing configuration errors.

When initializing default settings, the constructor calls EnsureOmmitedHardforks(new Dictionary<Hardfork, uint>()), ensuring all forks default to height 0 unless explicitly overridden in protocol.json.

Runtime Detection: IsHardforkEnabled

To conditionally execute logic based on the current block height, Neo provides the IsHardforkEnabled method in two flavors.

ProtocolSettings Implementation

The base implementation in src/Neo/ProtocolSettings.cs (lines 89-99) provides a pure function for height comparison:

public bool IsHardforkEnabled(Hardfork hardfork, uint index)
{
    if (Hardforks.TryGetValue(hardfork, out uint height))
        return index >= height;
    return false;
}

This method returns true if the specified block index has reached or exceeded the configured activation height for the given fork.

ApplicationEngine Wrapper

For smart contract execution contexts, src/Neo/SmartContract/ApplicationEngine.cs (lines 41-51) provides a convenience wrapper:

public bool IsHardforkEnabled(Hardfork hardfork)
{
    if (ProtocolSettings == null) return false;
    if (PersistingBlock is null) return ProtocolSettings.Hardforks.ContainsKey(hardfork);
    return ProtocolSettings.IsHardforkEnabled(hardfork, PersistingBlock.Index);
}

This version automatically uses the current PersistingBlock.Index when available, or checks for the fork's existence in configuration when executing outside of block persistence (such as during verification).

Practical Implementation Examples

Querying Protocol Settings Directly

When configuring a node or analyzing chain history, you can check fork status against any block height:

// Load custom settings from protocol.json
var settings = ProtocolSettings.Load("protocol.json");

// Check if Echidna fork is active at block 1,200,000
bool echidnaActive = settings.IsHardforkEnabled(Hardfork.HF_Echidna, 1_200_000);
Console.WriteLine(echidnaActive);   // → true if configured height <= 1,200,000

Conditional Logic in Smart Contract Execution

Native contracts use ApplicationEngine.IsHardforkEnabled to branch behavior:

// Inside a native contract method
if (engine.IsHardforkEnabled(Hardfork.HF_Faun))
{
    // Apply new fee schedule introduced in Faun fork
    fee = ApplicationEngine.FeeFactor * MaxExecFeeFactor;
}
else
{
    // Maintain legacy behavior for pre-Faun blocks
    fee = MaxExecFeeFactor;
}

Declaring Fork-Dependent Contract Methods

New native methods can be annotated to only appear after specific forks:

// In a native contract class (e.g., StdLib.cs)
[ContractMethod(Hardfork.HF_Echidna, CpuFee = 1 << 5)]
public static void NewCryptographicFeature()
{
    // Implementation exposed only when HF_Echidna is active
}

The ContractMethod attribute ensures the VM only exposes this method when the specified hardfork is enabled, preventing premature usage.

Summary

  • Hardforks in Neo are deterministic protocol upgrades activated at specific block heights, allowing the blockchain to evolve without breaking historical consensus.
  • The Hardfork enum in src/Neo/Hardfork.cs provides a byte-backed, symbolic identifier for each upgrade (e.g., HF_Echidna, HF_Faun).
  • ProtocolSettings.Hardforks stores the immutable mapping of forks to activation heights, with automatic validation ensuring monotonic block numbers and continuous sequences.
  • IsHardforkEnabled serves as the single source of truth for runtime checks, available both as a pure function in ProtocolSettings and as a context-aware wrapper in ApplicationEngine.
  • Native contracts and the VM use these mechanisms to conditionally enable new opcodes, fee schedules, and methods only after their respective fork heights.

Frequently Asked Questions

What happens if a hardfork configuration is missing from protocol.json?

When a hardfork is omitted from the configuration file, the EnsureOmmitedHardforks method in ProtocolSettings automatically assigns it a height of 0. This treats the fork as always enabled, ensuring backward compatibility for nodes that haven't updated their configuration files to include newer fork definitions.

How does Neo prevent hardforks from activating out of order?

The CheckingHardfork validation method in ProtocolSettings (lines 55-80) enforces two critical constraints: first, that the sequence of forks is continuous without gaps in the enum order, and second, that activation heights are monotonically increasing. If a configuration attempts to set a later fork at a lower block height than an earlier fork, the node throws a configuration exception during startup.

Can smart contracts directly query hardfork status during execution?

Yes, smart contracts running in the ApplicationEngine can query hardfork status through the IsHardforkEnabled method. This method automatically references the current PersistingBlock.Index to determine if a specific fork is active. Native contracts use this mechanism extensively to branch logic for fee calculations, cryptographic operations, and feature availability without requiring manual block height comparisons.

What is the difference between ProtocolSettings and ApplicationEngine hardfork checks?

ProtocolSettings.IsHardforkEnabled is a pure function that accepts any block index as a parameter, making it suitable for configuration validation, historical analysis, and offline tools. In contrast, ApplicationEngine.IsHardforkEnabled is a context-aware wrapper that automatically uses the current execution block height (or checks configuration presence when no block is being persisted), making it ideal for smart contract execution and VM operations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →