github.com/newbtp/btp@v0.0.0-20190709081714-e4aafa07224e/core/forkid/forkid.go (about)

     1  // Copyright 2019 The go-btpereum Authors
     2  // This file is part of the go-btpereum library.
     3  //
     4  // The go-btpereum 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-btpereum 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-btpereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package forkid implements EIP-2124 (https://eips.btpereum.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/btpereum/go-btpereum/common"
    30  	"github.com/btpereum/go-btpereum/core"
    31  	"github.com/btpereum/go-btpereum/log"
    32  	"github.com/btpereum/go-btpereum/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 btpereum 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 // Sombtping'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  }