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  }