github.com/dominant-strategies/go-quai@v0.28.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 18 19 import ( 20 "encoding/binary" 21 "errors" 22 "hash/crc32" 23 "math" 24 "math/big" 25 "reflect" 26 "strings" 27 28 "github.com/dominant-strategies/go-quai/common" 29 "github.com/dominant-strategies/go-quai/core/types" 30 "github.com/dominant-strategies/go-quai/log" 31 "github.com/dominant-strategies/go-quai/params" 32 ) 33 34 var ( 35 // ErrRemoteStale is returned by the validator if a remote fork checksum is a 36 // subset of our already applied forks, but the announced next fork block is 37 // not on our already passed chain. 38 ErrRemoteStale = errors.New("remote needs update") 39 40 // ErrLocalIncompatibleOrStale is returned by the validator if a remote fork 41 // checksum does not match any local checksum variation, signalling that the 42 // two chains have diverged in the past at some point (possibly at genesis). 43 ErrLocalIncompatibleOrStale = errors.New("local incompatible or needs update") 44 ) 45 46 // Blockchain defines all necessary method to build a forkID. 47 type Blockchain interface { 48 // Config retrieves the chain's fork configuration. 49 Config() *params.ChainConfig 50 51 // Genesis retrieves the chain's genesis block. 52 Genesis() *types.Block 53 54 // CurrentHeader retrieves the current head header of the canonical chain. 55 CurrentHeader() *types.Header 56 } 57 58 // ID is a fork identifier 59 type ID struct { 60 Hash [4]byte // CRC32 checksum of the genesis block and passed fork block numbers 61 Next uint64 // Block number of the next upcoming fork, or 0 if no forks are known 62 } 63 64 // Filter is a fork id filter to validate a remotely advertised ID. 65 type Filter func(id ID) error 66 67 // NewID calculates the Quai fork ID from the chain config, genesis hash, and head. 68 func NewID(config *params.ChainConfig, genesis common.Hash, head uint64) ID { 69 // Calculate the starting checksum from the genesis hash 70 hash := crc32.ChecksumIEEE(genesis[:]) 71 72 // Calculate the current fork checksum and the next fork block 73 var next uint64 74 for _, fork := range gatherForks(config) { 75 if fork <= head { 76 // Fork already passed, checksum the previous hash and the fork number 77 hash = checksumUpdate(hash, fork) 78 continue 79 } 80 next = fork 81 break 82 } 83 return ID{Hash: checksumToBytes(hash), Next: next} 84 } 85 86 // NewIDWithChain calculates the Quai fork ID from an existing chain instance. 87 func NewIDWithChain(chain Blockchain) ID { 88 return NewID( 89 chain.Config(), 90 chain.Genesis().Hash(), 91 chain.CurrentHeader().Number().Uint64(), 92 ) 93 } 94 95 // NewFilter creates a filter that returns if a fork ID should be rejected or not 96 // based on the local chain's status. 97 func NewFilter(chain Blockchain) Filter { 98 return newFilter( 99 chain.Config(), 100 chain.Genesis().Hash(), 101 func() uint64 { 102 return chain.CurrentHeader().Number().Uint64() 103 }, 104 ) 105 } 106 107 // NewStaticFilter creates a filter at block zero. 108 func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { 109 head := func() uint64 { return 0 } 110 return newFilter(config, genesis, head) 111 } 112 113 // newFilter is the internal version of NewFilter, taking closures as its arguments 114 // instead of a chain. The reason is to allow testing it without having to simulate 115 // an entire blockchain. 116 func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() uint64) Filter { 117 // Calculate the all the valid fork hash and fork next combos 118 var ( 119 forks = gatherForks(config) 120 sums = make([][4]byte, len(forks)+1) // 0th is the genesis 121 ) 122 hash := crc32.ChecksumIEEE(genesis[:]) 123 sums[0] = checksumToBytes(hash) 124 for i, fork := range forks { 125 hash = checksumUpdate(hash, fork) 126 sums[i+1] = checksumToBytes(hash) 127 } 128 // Add two sentries to simplify the fork checks and don't require special 129 // casing the last one. 130 forks = append(forks, math.MaxUint64) // Last fork will never be passed 131 132 // Create a validator that will filter out incompatible chains 133 return func(id ID) error { 134 // Run the fork checksum validation ruleset: 135 // 1. If local and remote FORK_CSUM matches, compare local head to FORK_NEXT. 136 // The two nodes are in the same fork state currently. They might know 137 // of differing future forks, but that's not relevant until the fork 138 // triggers (might be postponed, nodes might be updated to match). 139 // 1a. A remotely announced but remotely not passed block is already passed 140 // locally, disconnect, since the chains are incompatible. 141 // 1b. No remotely announced fork; or not yet passed locally, connect. 142 // 2. If the remote FORK_CSUM is a subset of the local past forks and the 143 // remote FORK_NEXT matches with the locally following fork block number, 144 // connect. 145 // Remote node is currently syncing. It might eventually diverge from 146 // us, but at this current point in time we don't have enough information. 147 // 3. If the remote FORK_CSUM is a superset of the local past forks and can 148 // be completed with locally known future forks, connect. 149 // Local node is currently syncing. It might eventually diverge from 150 // the remote, but at this current point in time we don't have enough 151 // information. 152 // 4. Reject in all other cases. 153 head := headfn() 154 for i, fork := range forks { 155 // If our head is beyond this fork, continue to the next (we have a dummy 156 // fork of maxuint64 as the last item to always fail this check eventually). 157 if head >= fork { 158 continue 159 } 160 // Found the first unpassed fork block, check if our current state matches 161 // the remote checksum (rule #1). 162 if sums[i] == id.Hash { 163 // Fork checksum matched, check if a remote future fork block already passed 164 // locally without the local node being aware of it (rule #1a). 165 if id.Next > 0 && head >= id.Next { 166 return ErrLocalIncompatibleOrStale 167 } 168 // Haven't passed locally a remote-only fork, accept the connection (rule #1b). 169 return nil 170 } 171 // The local and remote nodes are in different forks currently, check if the 172 // remote checksum is a subset of our local forks (rule #2). 173 for j := 0; j < i; j++ { 174 if sums[j] == id.Hash { 175 // Remote checksum is a subset, validate based on the announced next fork 176 if forks[j] != id.Next { 177 return ErrRemoteStale 178 } 179 return nil 180 } 181 } 182 // Remote chain is not a subset of our local one, check if it's a superset by 183 // any chance, signalling that we're simply out of sync (rule #3). 184 for j := i + 1; j < len(sums); j++ { 185 if sums[j] == id.Hash { 186 // Yay, remote checksum is a superset, ignore upcoming forks 187 return nil 188 } 189 } 190 // No exact, subset or superset match. We are on differing chains, reject. 191 return ErrLocalIncompatibleOrStale 192 } 193 log.Error("Impossible fork ID validation", "id", id) 194 return nil // Something's very wrong, accept rather than reject 195 } 196 } 197 198 // checksumUpdate calculates the next IEEE CRC32 checksum based on the previous 199 // one and a fork block number (equivalent to CRC32(original-blob || fork)). 200 func checksumUpdate(hash uint32, fork uint64) uint32 { 201 var blob [8]byte 202 binary.BigEndian.PutUint64(blob[:], fork) 203 return crc32.Update(hash, crc32.IEEETable, blob[:]) 204 } 205 206 // checksumToBytes converts a uint32 checksum into a [4]byte array. 207 func checksumToBytes(hash uint32) [4]byte { 208 var blob [4]byte 209 binary.BigEndian.PutUint32(blob[:], hash) 210 return blob 211 } 212 213 // gatherForks gathers all the known forks and creates a sorted list out of them. 214 func gatherForks(config *params.ChainConfig) []uint64 { 215 // Gather all the fork block numbers via reflection 216 kind := reflect.TypeOf(params.ChainConfig{}) 217 conf := reflect.ValueOf(config).Elem() 218 219 var forks []uint64 220 for i := 0; i < kind.NumField(); i++ { 221 // Fetch the next field and skip non-fork rules 222 field := kind.Field(i) 223 if !strings.HasSuffix(field.Name, "Block") { 224 continue 225 } 226 if field.Type != reflect.TypeOf(new(big.Int)) { 227 continue 228 } 229 // Extract the fork rule block number and aggregate it 230 rule := conf.Field(i).Interface().(*big.Int) 231 if rule != nil { 232 forks = append(forks, rule.Uint64()) 233 } 234 } 235 // Sort the fork block numbers to permit chronological XOR 236 for i := 0; i < len(forks); i++ { 237 for j := i + 1; j < len(forks); j++ { 238 if forks[i] > forks[j] { 239 forks[i], forks[j] = forks[j], forks[i] 240 } 241 } 242 } 243 // Deduplicate block numbers applying multiple forks 244 for i := 1; i < len(forks); i++ { 245 if forks[i] == forks[i-1] { 246 forks = append(forks[:i], forks[i+1:]...) 247 i-- 248 } 249 } 250 // Skip any forks in block 0, that's the genesis ruleset 251 if len(forks) > 0 && forks[0] == 0 { 252 forks = forks[1:] 253 } 254 return forks 255 }