EIP-2124 ForkID Implementation Analysis¶
Overview¶
This document analyzes Fukuii's implementation of EIP-2124 (ForkID) and compares it with the reference implementation in Core-Geth to ensure compatibility for peer-to-peer network communications.
Date: 2025-12-04
Issue: #710 - run 010
Reference Spec: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md
Core-Geth Reference: https://github.com/etclabscore/core-geth
Executive Summary¶
✅ Fukuii's ForkID implementation is CORRECT and COMPATIBLE with Core-Geth
Our implementation correctly follows the EIP-2124 specification and produces identical ForkID values as Core-Geth for ETC mainnet. The peer connection issues mentioned in the issue are NOT caused by ForkID implementation differences.
EIP-2124 Specification Summary¶
Purpose¶
EIP-2124 defines a Fork Identifier scheme to: - Precisely identify which chain a node is on (ETH vs ETC, etc.) - Reject incompatible peers before establishing expensive TCP connections - Distinguish between stale nodes vs nodes on different chains
Fork Identifier Structure¶
- FORK_HASH: 4-byte CRC32 checksum of genesis hash + all passed fork block numbers
- FORK_NEXT: Block number of next upcoming fork, or 0 if no forks are known
Calculation Algorithm¶
1. Start with CRC32(genesis_hash)
2. For each passed fork: CRC32_update(previous_hash, fork_block_number_as_uint64_BE)
3. FORK_NEXT = first fork block > current_head, or 0 if none
Validation Rules¶
- Matching hashes: If local and remote FORK_HASH match:
- 1a) If remote FORK_NEXT already passed locally → REJECT (incompatible)
-
1b) Otherwise → ACCEPT
-
Subset: If remote FORK_HASH is a subset of local past forks AND remote FORK_NEXT matches the next local fork → ACCEPT (remote syncing)
-
Superset: If remote FORK_HASH is a superset of local past forks → ACCEPT (local syncing)
-
Otherwise: REJECT (incompatible chains)
Implementation Comparison¶
Fukuii Implementation¶
Location: src/main/scala/com/chipprbots/ethereum/forkid/
ForkId.scala¶
def create(genesisHash: ByteString, config: BlockchainConfig)(head: BigInt): ForkId = {
val crc = new CRC32()
crc.update(genesisHash.asByteBuffer)
val forks = gatherForks(config)
val next = forks.find { fork =>
if (fork <= head) {
crc.update(bigIntToBytes(fork, 8)) // uint64 as 8 bytes, big endian
}
fork > head
}
new ForkId(crc.getValue(), next)
}
ForkIdValidator.scala¶
Implements all 4 validation rules:
- checkMatchingHashes() - Rule 1
- checkSubset() - Rule 2
- checkSuperset() - Rule 3
- Default reject - Rule 4
Core-Geth Implementation¶
Location: core/forkid/forkid.go
func NewID(config ctypes.ChainConfigurator, genesis *types.Block, head, time uint64) ID {
hash := crc32.ChecksumIEEE(genesis.Hash().Bytes())
forksByBlock, forksByTime := gatherForks(config, genesis.Time())
for _, fork := range forksByBlock {
if fork <= head {
hash = checksumUpdate(hash, fork)
continue
}
return ID{Hash: checksumToBytes(hash), Next: fork}
}
// ... time-based forks handling ...
return ID{Hash: checksumToBytes(hash), Next: 0}
}
func checksumUpdate(hash uint32, fork uint64) uint32 {
var blob [8]byte
binary.BigEndian.PutUint64(blob[:], fork) // uint64 as 8 bytes, big endian
return crc32.Update(hash, crc32.IEEETable, blob[:])
}
Fork Numbers Comparison¶
ETC Mainnet Forks¶
| Fork Name | Block Number | Fukuii | Core-Geth | Match |
|---|---|---|---|---|
| Homestead (EIP-2/7) | 1,150,000 | ✓ | ✓ | ✅ |
| Gas Reprice (EIP-150) | 2,500,000 | ✓ | ✓ | ✅ |
| Diehard (EIP-155/160, ECIP-1010) | 3,000,000 | ✓ | ✓ | ✅ |
| ECIP-1017 (Monetary Policy) | 5,000,000 | ✓ | ✓ | ✅ |
| Defuse Difficulty Bomb | 5,900,000 | ✓ | ✓ | ✅ |
| Atlantis (Byzantium) | 8,772,000 | ✓ | ✓ | ✅ |
| Agharta (Constantinople) | 9,573,000 | ✓ | ✓ | ✅ |
| Phoenix (Istanbul) | 10,500,839 | ✓ | ✓ | ✅ |
| Thanos (ECIP-1099) | 11,700,000 | ✓ | ✓ | ✅ |
| Magneto (Berlin) | 13,189,133 | ✓ | ✓ | ✅ |
| Mystique (London partial) | 14,525,000 | ✓ | ✓ | ✅ |
| Spiral (Shanghai partial) | 19,250,000 | ✓ | ✓ | ✅ |
Result: All 12 fork blocks match exactly ✅
Test Case Verification¶
Both implementations include identical test cases for ETC mainnet. Here are key test points:
| Block Height | Expected FORK_HASH | Expected FORK_NEXT | Fukuii | Core-Geth |
|---|---|---|---|---|
| 0 | 0xfc64ec04 | 1,150,000 | ✅ | ✅ |
| 1,150,000 | 0x97c2c34c | 2,500,000 | ✅ | ✅ |
| 2,500,000 | 0xdb06803f | 3,000,000 | ✅ | ✅ |
| 3,000,000 | 0xaff4bed4 | 5,000,000 | ✅ | ✅ |
| 5,000,000 | 0xf79a63c0 | 5,900,000 | ✅ | ✅ |
| 5,900,000 | 0x744899d6 | 8,772,000 | ✅ | ✅ |
| 8,772,000 | 0x518b59c6 | 9,573,000 | ✅ | ✅ |
| 9,573,000 | 0x7ba22882 | 10,500,839 | ✅ | ✅ |
| 10,500,839 | 0x9007bfcc | 11,700,000 | ✅ | ✅ |
| 11,700,000 | 0xdb63a1ca | 13,189,133 | ✅ | ✅ |
| 13,189,133 | 0x0f6bf187 | 14,525,000 | ✅ | ✅ |
| 14,525,000 | 0x7fd1bb25 | 19,250,000 | ✅ | ✅ |
| 19,250,000 | 0xbe46d57c | 0 | ✅ | ✅ |
Source:
- Fukuii: src/test/scala/com/chipprbots/ethereum/forkid/ForkIdSpec.scala
- Core-Geth: core/forkid/forkid_test.go
Mordor Testnet Forks¶
| Block Height | Expected FORK_HASH | Expected FORK_NEXT | Fukuii | Core-Geth |
|---|---|---|---|---|
| 0 | 0x175782aa | 301,243 | ✅ | ✅ |
| 301,243 | 0x604f6ee1 | 999,983 | ✅ | ✅ |
| 999,983 | 0xf42f5539 | 2,520,000 | ✅ | ✅ |
| 2,520,000 | 0x66b5c286 | 3,985,893 | ✅ | ✅ |
| 3,985,893 | 0x92b323e0 | 5,520,000 | ✅ | ✅ |
| 5,520,000 | 0x8c9b1797 | 9,957,000 | ✅ | ✅ |
| 9,957,000 | 0x3a6b00d7 | 0 | ✅ | ✅ |
Result: All Mordor fork IDs match exactly ✅
RLP Encoding Verification¶
Both implementations use the same RLP encoding format:
Where: - FORK_HASH: 4 bytes (big-endian uint32) - FORK_NEXT: 8 bytes (big-endian uint64), or empty if 0
Test Cases¶
| ForkID | Expected RLP Hex | Fukuii | Core-Geth |
|---|---|---|---|
| ForkId(0, None) | c6840000000080 | ✅ | ✅ |
| ForkId(0xdeadbeef, Some(0xbaddcafe)) | ca84deadbeef84baddcafe | ✅ | ✅ |
| ForkId(0xffffffff, Some(0xffffffffffffffff)) | ce84ffffffff88ffffffffffffffff | ✅ | ✅ |
Key Differences (None Affecting Compatibility)¶
1. Language¶
- Fukuii: Scala 3
- Core-Geth: Go
2. Code Organization¶
- Fukuii: Separates ForkId creation and validation into two files
- Core-Geth: Combines both in one file
3. Time-Based Forks¶
- Core-Geth: Supports both block-based and timestamp-based forks (for Ethereum Merge)
- Fukuii: Currently only supports block-based forks (appropriate for ETC which is PoW-only)
Note: ETC does not use timestamp-based forks, so this difference does not affect ETC compatibility.
Validation Logic Comparison¶
Both implementations follow the exact same 4-step validation algorithm from EIP-2124:
Rule 1: Matching Hashes¶
// Fukuii
case ForkId(hash, _) if checksum != hash => None
case ForkId(_, Some(next)) if currentHeight >= next => Some(ErrLocalIncompatibleOrStale)
case _ => Some(Connect)
// Core-Geth
if sums[i] == id.Hash {
if id.Next > 0 && (head >= id.Next || ...) {
return ErrLocalIncompatibleOrStale
}
return nil
}
Rule 2: Subset Check¶
Both check if remote hash appears in past local checksums and remote.Next matches the corresponding local fork.
Rule 3: Superset Check¶
Both check if remote hash appears in future local checksums.
Rule 4: Default Reject¶
Both reject if none of the above rules match.
Conclusion¶
Implementation Status: ✅ CORRECT¶
- Fork Numbers: Fukuii uses identical fork block numbers as Core-Geth for ETC mainnet
- Hash Calculation: Identical CRC32 algorithm with big-endian uint64 encoding
- RLP Encoding: Identical RLP encoding format
- Validation Logic: Identical 4-step validation algorithm
- Test Coverage: Comprehensive test suite matching Core-Geth test cases
Peer Connection Issues¶
Since the ForkID implementation is correct and compatible, the peer connection issues mentioned in the original issue are NOT caused by ForkID incompatibility. Potential other causes to investigate:
- Network Layer: RLPx handshake issues unrelated to ForkID
- Capability Negotiation: Issues with eth/6x protocol version negotiation
- Message Encoding: Problems with Snappy compression or message framing
- Peer Discovery: Issues finding and connecting to peers
- Configuration: Incorrect network ID or genesis hash settings
Recommendations¶
- ✅ Continue using current ForkID implementation - it is correct
- 🔍 Investigate RLPx handshake and message encoding for peer connection issues
- 📝 Document that Fukuii's ForkID is verified compatible with Core-Geth
- ✅ Keep test suite aligned with Core-Geth for future fork updates
References¶
- EIP-2124: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2124.md
- Core-Geth Implementation: https://github.com/etclabscore/core-geth/tree/master/core/forkid
- Fukuii Implementation:
src/main/scala/com/chipprbots/ethereum/forkid/ - Test Suites:
- Fukuii:
src/test/scala/com/chipprbots/ethereum/forkid/ForkIdSpec.scala - Core-Geth:
core/forkid/forkid_test.go
Appendix: Fork List Source Code¶
Fukuii Fork Gathering¶
def gatherForks(config: BlockchainConfig): List[BigInt] = {
val maybeDaoBlock: Option[BigInt] = config.daoForkConfig.flatMap { daoConf =>
if (daoConf.includeOnForkIdList) Some(daoConf.forkBlockNumber)
else None
}
(maybeDaoBlock.toList ++ config.forkBlockNumbers.all)
.filterNot(v => v == 0 || v == noFork)
.distinct
.sorted
}
Core-Geth Fork Gathering¶
Core-Geth uses a reflective approach via confp.BlockForks() which extracts all non-zero, non-compatibility fork blocks from the chain configuration and sorts them.
Both approaches produce identical fork lists for ETC mainnet.