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