mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
fix: XDC mainnet genesis hash compatibility
Fixes the genesis hash mismatch with official XDPoSChain: - Fixed corrupted contract bytecode in genesis file (2 extra hex chars) - Added XDPoS fields (Validators, Validator, Penalties) to RLP encoder - Initialize XDPoS fields to empty slices in genesis construction - Updated XDCMainnetGenesisHash to correct value - Added XDC mainnet bootnodes Genesis hash now matches official: 0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1 State root: 0x49be235b0098b048f9805aed38a279d8c189b469ff9ba307b39c7ad3a3bc55ae See docs/GENESIS_COMPATIBILITY_FIX.md for full technical details.
This commit is contained in:
parent
e40a3414cc
commit
9b24b71513
7 changed files with 265 additions and 9 deletions
|
|
@ -497,6 +497,10 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block {
|
|||
MixDigest: g.Mixhash,
|
||||
Coinbase: g.Coinbase,
|
||||
Root: root,
|
||||
// XDPoS fields - initialize to empty slices (not nil) for RLP compatibility
|
||||
Validators: []byte{},
|
||||
Validator: []byte{},
|
||||
Penalties: []byte{},
|
||||
}
|
||||
if g.GasLimit == 0 {
|
||||
head.GasLimit = params.GenesisGasLimit
|
||||
|
|
|
|||
|
|
@ -81,10 +81,10 @@ type Header struct {
|
|||
MixDigest common.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
|
||||
// XDPoS fields for validator consensus
|
||||
Validators []byte `json:"validators" rlp:"optional"`
|
||||
Validator []byte `json:"validator" rlp:"optional"`
|
||||
Penalties []byte `json:"penalties" rlp:"optional"`
|
||||
// XDPoS fields for validator consensus (always encoded, not optional)
|
||||
Validators []byte `json:"validators"`
|
||||
Validator []byte `json:"validator"`
|
||||
Penalties []byte `json:"penalties"`
|
||||
|
||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ func (obj *Header) EncodeRLP(_w io.Writer) error {
|
|||
w.WriteBytes(obj.Extra)
|
||||
w.WriteBytes(obj.MixDigest[:])
|
||||
w.WriteBytes(obj.Nonce[:])
|
||||
// XDPoS fields - always encode (not optional)
|
||||
w.WriteBytes(obj.Validators)
|
||||
w.WriteBytes(obj.Validator)
|
||||
w.WriteBytes(obj.Penalties)
|
||||
_tmp1 := obj.BaseFee != nil
|
||||
_tmp2 := obj.WithdrawalsHash != nil
|
||||
_tmp3 := obj.BlobGasUsed != nil
|
||||
|
|
|
|||
224
docs/GENESIS_COMPATIBILITY_FIX.md
Normal file
224
docs/GENESIS_COMPATIBILITY_FIX.md
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# XDC Genesis Hash Compatibility Fix
|
||||
|
||||
**Date:** 2026-01-29
|
||||
**Status:** ✅ RESOLVED
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The Part4 implementation (go-ethereum v1.15 based XDC port) was producing different genesis hashes than the official XDPoSChain implementation when initializing from the XDC mainnet genesis file.
|
||||
|
||||
- **Expected Hash:** `0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1`
|
||||
- **Part4 Produced:** `0x0683984f78dcbdd71f618cca7e14811b500b8f0f5ee1f1962d8fc44aa5a75eea` (before fix)
|
||||
|
||||
This would prevent Part4 nodes from syncing with the XDC mainnet.
|
||||
|
||||
---
|
||||
|
||||
## Root Causes Identified
|
||||
|
||||
### 1. Corrupted Genesis File
|
||||
|
||||
**Issue:** The Part4 genesis file (`genesis/xdc_mainnet.json`) had corrupted contract bytecode for the validator contract at address `0x88`.
|
||||
|
||||
| File | Code Length (bytes) |
|
||||
|------|-------------------|
|
||||
| Part4 (corrupted) | 14,453 |
|
||||
| Official | 14,452 |
|
||||
|
||||
The Part4 file had 2 extra hex characters (`00`) inserted at position 18745 in the bytecode.
|
||||
|
||||
**Impact:** Different code hash → different account hash → different state root.
|
||||
|
||||
**Fix:** Copied correct genesis from official repository and converted `xdc` address prefixes to `0x`.
|
||||
|
||||
### 2. Missing XDPoS Fields in RLP Encoder
|
||||
|
||||
**Issue:** The generated RLP encoder (`core/types/gen_header_rlp.go`) did not include the XDPoS-specific header fields.
|
||||
|
||||
XDC headers have three additional fields compared to standard Ethereum headers:
|
||||
```go
|
||||
Validators []byte // List of validators
|
||||
Validator []byte // Current validator
|
||||
Penalties []byte // Penalty data
|
||||
```
|
||||
|
||||
The generated code jumped from `Nonce` directly to `BaseFee`, skipping these fields entirely:
|
||||
|
||||
```go
|
||||
// BEFORE (incorrect)
|
||||
w.WriteBytes(obj.Nonce[:])
|
||||
_tmp1 := obj.BaseFee != nil // XDPoS fields skipped!
|
||||
```
|
||||
|
||||
**Impact:** Header RLP encoding was 3 bytes shorter (661 vs 664 bytes), producing different block hashes.
|
||||
|
||||
**Fix:** Added XDPoS field encoding to `gen_header_rlp.go`:
|
||||
```go
|
||||
// AFTER (correct)
|
||||
w.WriteBytes(obj.Nonce[:])
|
||||
// XDPoS fields - always encode (not optional)
|
||||
w.WriteBytes(obj.Validators)
|
||||
w.WriteBytes(obj.Validator)
|
||||
w.WriteBytes(obj.Penalties)
|
||||
_tmp1 := obj.BaseFee != nil
|
||||
```
|
||||
|
||||
### 3. Nil vs Empty Slice Initialization
|
||||
|
||||
**Issue:** Genesis block construction left XDPoS fields as `nil` instead of empty slices `[]byte{}`.
|
||||
|
||||
```go
|
||||
// BEFORE (incorrect)
|
||||
head := &types.Header{
|
||||
// ... other fields
|
||||
// Validators, Validator, Penalties not set (nil)
|
||||
}
|
||||
```
|
||||
|
||||
**Impact:** Even with proper RLP encoding, nil slices might be handled differently than empty slices.
|
||||
|
||||
**Fix:** Explicitly initialize to empty slices in `core/genesis.go`:
|
||||
```go
|
||||
// AFTER (correct)
|
||||
head := &types.Header{
|
||||
// ... other fields
|
||||
Validators: []byte{},
|
||||
Validator: []byte{},
|
||||
Penalties: []byte{},
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Incorrect Config Hash
|
||||
|
||||
**Issue:** `params/config.go` had wrong `XDCMainnetGenesisHash`.
|
||||
|
||||
```go
|
||||
// BEFORE (incorrect)
|
||||
XDCMainnetGenesisHash = common.HexToHash("0x81b02e6c24c0ed8383dd5f6c1e83e82b8f988af91f89f9b95c10dbd3e25cd025")
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
```go
|
||||
// AFTER (correct)
|
||||
XDCMainnetGenesisHash = common.HexToHash("0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `genesis/xdc_mainnet.json` | Replaced with correct genesis (fixed contract bytecode) |
|
||||
| `core/types/gen_header_rlp.go` | Added XDPoS field encoding |
|
||||
| `core/types/block.go` | Removed `rlp:"optional"` tags from XDPoS fields |
|
||||
| `core/genesis.go` | Initialize XDPoS fields to empty slices |
|
||||
| `params/config.go` | Fixed `XDCMainnetGenesisHash` value |
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
### State Root Match
|
||||
```
|
||||
Part4: 0x49be235b0098b048f9805aed38a279d8c189b469ff9ba307b39c7ad3a3bc55ae
|
||||
Official: 0x49be235b0098b048f9805aed38a279d8c189b469ff9ba307b39c7ad3a3bc55ae
|
||||
✅ MATCH
|
||||
```
|
||||
|
||||
### Header RLP Match
|
||||
```
|
||||
Part4: 664 bytes, ends with 808080 (three empty XDPoS fields)
|
||||
Official: 664 bytes, ends with 808080
|
||||
✅ MATCH
|
||||
```
|
||||
|
||||
### Genesis Block Hash Match
|
||||
```
|
||||
Part4: 0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1
|
||||
Official: 0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1
|
||||
✅ MATCH
|
||||
```
|
||||
|
||||
### Init Command
|
||||
```bash
|
||||
$ ./geth init --datadir /tmp/test genesis/xdc_mainnet.json
|
||||
INFO Successfully wrote genesis state database=chaindata hash=4a9d74..42d6b1
|
||||
✅ SUCCESS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Details
|
||||
|
||||
### XDC Header Structure (RLP Order)
|
||||
|
||||
```
|
||||
1. ParentHash [32]byte
|
||||
2. UncleHash [32]byte
|
||||
3. Coinbase [20]byte
|
||||
4. Root [32]byte (state root)
|
||||
5. TxHash [32]byte
|
||||
6. ReceiptHash [32]byte
|
||||
7. Bloom [256]byte
|
||||
8. Difficulty *big.Int
|
||||
9. Number *big.Int
|
||||
10. GasLimit uint64
|
||||
11. GasUsed uint64
|
||||
12. Time uint64
|
||||
13. Extra []byte
|
||||
14. MixDigest [32]byte
|
||||
15. Nonce [8]byte
|
||||
16. Validators []byte ← XDPoS field
|
||||
17. Validator []byte ← XDPoS field
|
||||
18. Penalties []byte ← XDPoS field
|
||||
19. BaseFee *big.Int (optional, EIP-1559)
|
||||
... (other optional fields)
|
||||
```
|
||||
|
||||
### RLP Encoding for Empty XDPoS Fields
|
||||
|
||||
Empty `[]byte{}` encodes as `0x80` in RLP (single byte representing empty string).
|
||||
|
||||
Genesis block has all three XDPoS fields empty, so the header ends with:
|
||||
```
|
||||
...880000000000000000 808080
|
||||
└── Nonce (8 bytes) └── Validators, Validator, Penalties (empty)
|
||||
```
|
||||
|
||||
### Why Generated Code Needed Manual Update
|
||||
|
||||
Go-ethereum uses code generation for RLP encoding:
|
||||
```go
|
||||
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
||||
```
|
||||
|
||||
When new fields are added to the Header struct, the generated code must be regenerated or manually updated. In this case, manual update was safer to avoid unintended changes.
|
||||
|
||||
---
|
||||
|
||||
## Debugging Methodology
|
||||
|
||||
1. **Isolated the issue** - Verified trie encoding works correctly with simple test cases
|
||||
2. **Binary comparison** - Compared RLP-encoded headers byte-by-byte
|
||||
3. **Field-by-field verification** - Confirmed each header field matches
|
||||
4. **Size discrepancy** - Found 3-byte difference (661 vs 664 bytes)
|
||||
5. **Traced to source** - Found generated RLP encoder missing XDPoS fields
|
||||
6. **Genesis file audit** - Found corrupted contract bytecode
|
||||
|
||||
---
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
1. **Always verify genesis files** - Even small corruption (2 bytes) breaks everything
|
||||
2. **Generated code needs updates** - When adding struct fields, check generated encoders
|
||||
3. **nil vs empty slice matters** - In RLP encoding, they can behave differently
|
||||
4. **Compare binary output** - When hashes don't match, compare the raw bytes
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- Official XDC genesis hash: `0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1`
|
||||
- Official XDC state root: `0x49be235b0098b048f9805aed38a279d8c189b469ff9ba307b39c7ad3a3bc55ae`
|
||||
- XDPoSChain repository: https://github.com/XinFinOrg/XDPoSChain
|
||||
File diff suppressed because one or more lines are too long
14
params/bootnodes_xdc.go
Normal file
14
params/bootnodes_xdc.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package params
|
||||
|
||||
// XDCMainnetBootnodes are the enode URLs of the P2P bootstrap nodes for XDC mainnet.
|
||||
var XDCMainnetBootnodes = []string{
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@209.126.0.250:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@209.126.4.150:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@144.126.150.58:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@162.250.190.246:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@162.250.191.5:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@162.250.191.14:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@162.250.189.149:30304",
|
||||
"enode://91e59fa1b034ae35e9f4e8a99cc6621f09d74e76a6220abb6c93b29ed41a9e1fc4e5b70e2c5fc43f883cffbdcd6f4f6cbc1d23af077f28c2aecc22403355d4b1@162.250.189.152:30304",
|
||||
"enode://7524db6718828c2c7663e6585a5b1e066457b8b0235034b69358b36e584fea776666d36ed4fc43d0f8bf2a5c3b2a960b5600689b6c8f0c207e5a76f8b0ca432d@157.173.120.219:30304",
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ var (
|
|||
HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4")
|
||||
SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9")
|
||||
HoodiGenesisHash = common.HexToHash("0xbbe312868b376a3001692a646dd2d7d1e4406380dfd86b98aa8a34d1557c971b")
|
||||
XDCMainnetGenesisHash = common.HexToHash("0x81b02e6c24c0ed8383dd5f6c1e83e82b8f988af91f89f9b95c10dbd3e25cd025")
|
||||
XDCMainnetGenesisHash = common.HexToHash("0x4a9d748bd78a8d0385b67788c2435dcdb914f98a96250b68863a1f8b7642d6b1")
|
||||
XDCApothemGenesisHash = common.HexToHash("0xcc97d2b9dcbce1b3d4a08c53c0f61c9c22c5c8c6c6f4eb4c5c5c5c5c5c5c5c5c5")
|
||||
)
|
||||
|
||||
|
|
@ -580,6 +580,16 @@ type XDPoSConfig struct {
|
|||
RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards
|
||||
Gap uint64 `json:"gap"` // Gap time preparing for the next epoch
|
||||
FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet
|
||||
V2 *XDPoSV2Config `json:"v2,omitempty"` // XDPoS 2.0 configuration (optional)
|
||||
}
|
||||
|
||||
// XDPoSV2Config holds XDPoS 2.0 specific configuration
|
||||
type XDPoSV2Config struct {
|
||||
SwitchBlock *big.Int `json:"switchBlock"` // Block number to switch to V2
|
||||
MinePeriod uint64 `json:"minePeriod"` // Mining period in seconds
|
||||
TimeoutPeriod uint64 `json:"timeoutPeriod"` // Timeout period in seconds
|
||||
TimeoutSyncThreshold uint64 `json:"timeoutSyncThreshold"` // Timeout sync threshold
|
||||
CertThreshold uint64 `json:"certThreshold"` // Certificate threshold (2/3 of validators)
|
||||
}
|
||||
|
||||
// String implements the stringer interface, returning the consensus engine details.
|
||||
|
|
|
|||
Loading…
Reference in a new issue