github.com/wzbox/go-ethereum@v1.9.2/core/forkid/forkid.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package forkid implements EIP-2124 (https://eips.ethereum.org/EIPS/eip-2124). 18 package forkid 19 20 import ( 21 "encoding/binary" 22 "errors" 23 "hash/crc32" 24 "math" 25 "math/big" 26 "reflect" 27 "strings" 28 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/core" 31 "github.com/ethereum/go-ethereum/log" 32 "github.com/ethereum/go-ethereum/params" 33 ) 34 35 var ( 36 // ErrRemoteStale is returned by the validator if a remote fork checksum is a 37 // subset of our already applied forks, but the announced next fork block is 38 // not on our already passed chain. 39 ErrRemoteStale = errors.New("remote needs update") 40 41 // ErrLocalIncompatibleOrStale is returned by the validator if a remote fork 42 // checksum does not match any local checksum variation, signalling that the 43 // two chains have diverged in the past at some point (possibly at genesis). 44 ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update") 45 ) 46 47 // ID is a fork identifier as defined by EIP-2124. 48 type ID struct { 49 Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers 50 Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known 51 } 52 53 // NewID calculates the Ethereum fork ID from the chain config and head. 54 func NewID(chain *core.BlockChain) ID { 55 return newID( 56 chain.Config(), 57 chain.Genesis().Hash(), 58 chain.CurrentHeader().Number.Uint64(), 59 ) 60 } 61 62 // newID is the internal version of NewID, which takes extracted values as its 63 // arguments instead of a chain. The reason is to allow testing the IDs without 64 // having to simulate an entire blockchain. 65 func newID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { 66 // Calculate the starting checksum from the genesis hash 67 hash := crc32.ChecksumIEEE(genesis[:]) 68 69 // Calculate the current fork checksum and the next fork block 70 var next uint64 71 for _, fork := range gatherForks(config) { 72 if fork <= head { 73 // Fork already passed, checksum the previous hash and the fork number 74 hash = checksumUpdate(hash, fork) 75 continue 76 } 77 next = fork 78 break 79 } 80 return ID{Hash: checksumToBytes(hash), Next: next} 81 } 82 83 // NewFilter creates an filter that returns if a fork ID should be rejected or not 84 // based on the local chain's status. 85 func NewFilter(chain *core.BlockChain) func(id ID) error { 86 return newFilter( 87 chain.Config(), 88 chain.Genesis().Hash(), 89 func() uint64 { 90 return chain.CurrentHeader().Number.Uint64() 91 }, 92 ) 93 } 94 95 // newFilter is the internal version of NewFilter, taking closures as its arguments 96 // instead of a chain. The reason is to allow testing it without having to simulate 97 // an entire blockchain. 98 func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) func(id ID) error { 99 // Calculate the all the valid fork hash and fork next combos 100 var ( 101 forks = gatherForks(config) 102 sums = make([][4]byte, len(forks)+1) // 0th is the genesis 103 ) 104 hash := crc32.ChecksumIEEE(genesis[:]) 105 sums[0] = checksumToBytes(hash) 106 for i, fork := range forks { 107 hash = checksumUpdate(hash, fork) 108 sums[i+1] = checksumToBytes(hash) 109 } 110 // Add two sentries to simplify the fork checks and don't require special 111 // casing the last one. 112 forks = append(forks, math.MaxUint64) // Last fork will never be passed 113 114 // Create a validator that will filter out incompatible chains 115 return func(id ID) error { 116 // Run the fork checksum validation ruleset: 117 // 1. If local and remote FORK_CSUM matches, connect. 118 // The two nodes are in the same fork state currently. They might know 119 // of differing future forks, but that's not relevant until the fork 120 // triggers (might be postponed, nodes might be updated to match). 121 // 2. If the remote FORK_CSUM is a subset of the local past forks and the 122 // remote FORK_NEXT matches with the locally following fork block number, 123 // connect. 124 // Remote node is currently syncing. It might eventually diverge from 125 // us, but at this current point in time we don't have enough information. 126 // 3. If the remote FORK_CSUM is a superset of the local past forks and can 127 // be completed with locally known future forks, connect. 128 // Local node is currently syncing. It might eventually diverge from 129 // the remote, but at this current point in time we don't have enough 130 // information. 131 // 4. Reject in all other cases. 132 head := headfn() 133 for i, fork := range forks { 134 // If our head is beyond this fork, continue to the next (we have a dummy 135 // fork of maxuint64 as the last item to always fail this check eventually). 136 if head > fork { 137 continue 138 } 139 // Found the first unpassed fork block, check if our current state matches 140 // the remote checksum (rule #1). 141 if sums[i] == id.Hash { 142 // Yay, fork checksum matched, ignore any upcoming fork 143 return nil 144 } 145 // The local and remote nodes are in different forks currently, check if the 146 // remote checksum is a subset of our local forks (rule #2). 147 for j := 0; j < i; j++ { 148 if sums[j] == id.Hash { 149 // Remote checksum is a subset, validate based on the announced next fork 150 if forks[j] != id.Next { 151 return ErrRemoteStale 152 } 153 return nil 154 } 155 } 156 // Remote chain is not a subset of our local one, check if it's a superset by 157 // any chance, signalling that we're simply out of sync (rule #3). 158 for j := i + 1; j < len(sums); j++ { 159 if sums[j] == id.Hash { 160 // Yay, remote checksum is a superset, ignore upcoming forks 161 return nil 162 } 163 } 164 // No exact, subset or superset match. We are on differing chains, reject. 165 return ErrLocalIncompatibleOrStale 166 } 167 log.Error("Impossible fork ID validation", "id", id) 168 return nil // Something's very wrong, accept rather than reject 169 } 170 } 171 172 // checksum calculates the IEEE CRC32 checksum of a block number. 173 func checksum(fork uint64) uint32 { 174 var blob [8]byte 175 binary.BigEndian.PutUint64(blob[:], fork) 176 return crc32.ChecksumIEEE(blob[:]) 177 } 178 179 // checksumUpdate calculates the next IEEE CRC32 checksum based on the previous 180 // one and a fork block number (equivalent to CRC32(original-blob || fork)). 181 func checksumUpdate(hash uint32, fork uint64) uint32 { 182 var blob [8]byte 183 binary.BigEndian.PutUint64(blob[:], fork) 184 return crc32.Update(hash, crc32.IEEETable, blob[:]) 185 } 186 187 // checksumToBytes converts a uint32 checksum into a [4]byte array. 188 func checksumToBytes(hash uint32) [4]byte { 189 var blob [4]byte 190 binary.BigEndian.PutUint32(blob[:], hash) 191 return blob 192 } 193 194 // gatherForks gathers all the known forks and creates a sorted list out of them. 195 func gatherForks(config *params.ChainConfig) []uint64 { 196 // Gather all the fork block numbers via reflection 197 kind := reflect.TypeOf(params.ChainConfig{}) 198 conf := reflect.ValueOf(config).Elem() 199 200 var forks []uint64 201 for i := 0; i < kind.NumField(); i++ { 202 // Fetch the next field and skip non-fork rules 203 field := kind.Field(i) 204 if !strings.HasSuffix(field.Name, "Block") { 205 continue 206 } 207 if field.Type != reflect.TypeOf(new(big.Int)) { 208 continue 209 } 210 // Extract the fork rule block number and aggregate it 211 rule := conf.Field(i).Interface().(*big.Int) 212 if rule != nil { 213 forks = append(forks, rule.Uint64()) 214 } 215 } 216 // Sort the fork block numbers to permit chronologival XOR 217 for i := 0; i < len(forks); i++ { 218 for j := i + 1; j < len(forks); j++ { 219 if forks[i] > forks[j] { 220 forks[i], forks[j] = forks[j], forks[i] 221 } 222 } 223 } 224 // Deduplicate block numbers applying multiple forks 225 for i := 1; i < len(forks); i++ { 226 if forks[i] == forks[i-1] { 227 forks = append(forks[:i], forks[i+1:]...) 228 i-- 229 } 230 } 231 // Skip any forks in block 0, that's the genesis ruleset 232 if len(forks) > 0 && forks[0] == 0 { 233 forks = forks[1:] 234 } 235 return forks 236 }