github.com/energicryptocurrency/go-energi@v1.1.7/core/energi_checkpoints.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core 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 Energi Core 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 Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package core
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"math"
    23  	"math/big"
    24  	"sort"
    25  	"sync"
    26  	"sync/atomic"
    27  
    28  	"github.com/energicryptocurrency/go-energi/common"
    29  	"github.com/energicryptocurrency/go-energi/core/types"
    30  	"github.com/energicryptocurrency/go-energi/crypto"
    31  	energi_params "github.com/energicryptocurrency/go-energi/energi/params"
    32  	"github.com/energicryptocurrency/go-energi/event"
    33  	"github.com/energicryptocurrency/go-energi/log"
    34  	"github.com/energicryptocurrency/go-energi/params"
    35  )
    36  
    37  // max number of checkpoints stored in validated checkpoint map
    38  const MaxCachedCheckpoints int = 10
    39  
    40  type CheckpointValidateChain interface {
    41  	GetHeaderByNumber(number uint64) *types.Header
    42  	CurrentHeader() *types.Header
    43  }
    44  
    45  type CheckpointChain interface {
    46  	CheckpointValidateChain
    47  
    48  	EnforceCheckpoint(cp Checkpoint) error
    49  	Config() *params.ChainConfig
    50  }
    51  
    52  type Checkpoint struct {
    53  	Since  uint64
    54  	Number uint64
    55  	Hash   common.Hash
    56  }
    57  
    58  // Format implements fmt.Formatter, forcing the Checkpoint to be formatted as is,
    59  // without going through the stringer interface used for logging.
    60  func (cp Checkpoint) Format(s fmt.State, c rune) {
    61  	cpStr := struct {
    62  		Number uint64
    63  		Hash   string
    64  	}{
    65  		cp.Number,
    66  		cp.Hash.String(),
    67  	}
    68  	fmt.Fprintf(s, "%+"+string(c), cpStr)
    69  }
    70  
    71  type CheckpointSignature []byte
    72  
    73  type CheckpointInfo struct {
    74  	Checkpoint
    75  	CppSignature CheckpointSignature
    76  	SigCount     uint64
    77  }
    78  
    79  type NewCheckpointEvent struct {
    80  	CheckpointInfo
    81  }
    82  
    83  type validCheckpoint struct {
    84  	Checkpoint
    85  	signatures []CheckpointSignature
    86  }
    87  
    88  type futureCheckpoint struct {
    89  	Checkpoint
    90  }
    91  
    92  type checkpointManager struct {
    93  	validated map[uint64]validCheckpoint
    94  	latest    uint64
    95  	future    map[uint64]futureCheckpoint
    96  	mtx       sync.RWMutex
    97  	newCpFeed event.Feed
    98  }
    99  
   100  func newCheckpointManager() *checkpointManager {
   101  	return &checkpointManager{
   102  		validated: make(map[uint64]validCheckpoint),
   103  		future:    make(map[uint64]futureCheckpoint),
   104  	}
   105  }
   106  
   107  func (cm *checkpointManager) setup(chain CheckpointChain) {
   108  	genesis_hash := chain.GetHeaderByNumber(0).Hash()
   109  	if checkpoints, ok := energi_params.EnergiCheckpoints[genesis_hash]; ok {
   110  		for k, v := range checkpoints {
   111  			cm.addCheckpoint(
   112  				chain,
   113  				Checkpoint{
   114  					Number: k,
   115  					Hash:   v,
   116  				},
   117  				[]CheckpointSignature{},
   118  				true,
   119  			)
   120  		}
   121  	}
   122  }
   123  
   124  func (cm *checkpointManager) validate(chain CheckpointValidateChain, num uint64, hash common.Hash) error {
   125  	cm.mtx.Lock()
   126  	defer cm.mtx.Unlock()
   127  
   128  	// Check against validated checkpoints & mismatch
   129  	if cp, ok := cm.validated[num]; ok {
   130  		if cp.Hash != hash {
   131  			return ErrCheckpointMismatch
   132  		}
   133  
   134  		cm.updateLatest(chain, &cp.Checkpoint)
   135  
   136  		return nil
   137  	}
   138  
   139  	// Check if before the latest checkpoint & mismatch
   140  	if num < cm.latest {
   141  		header := chain.GetHeaderByNumber(num)
   142  
   143  		if header != nil && header.Hash() != hash {
   144  			return ErrCheckpointMismatch
   145  		}
   146  
   147  		return nil
   148  	}
   149  
   150  	// TODO: proper future checkpoint processing
   151  	if cp, ok := cm.future[num]; ok {
   152  		if cp.Hash != hash {
   153  			return ErrCheckpointMismatch
   154  		}
   155  
   156  		return nil
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  // returns the smallest key (blockHeight)
   163  func oldestCheckpoint(validated map[uint64]validCheckpoint) uint64 {
   164  	minHeight := uint64(math.MaxUint64)
   165  	for k := range validated {
   166  		if k < minHeight {
   167  			minHeight = k
   168  		}
   169  	}
   170  	return minHeight
   171  }
   172  
   173  func (bc *BlockChain) AddCheckpoint(
   174  	cp Checkpoint,
   175  	sigs []CheckpointSignature,
   176  	local bool,
   177  ) error {
   178  	return bc.checkpoints.addCheckpoint(bc, cp, sigs, local)
   179  }
   180  
   181  func (cm *checkpointManager) addCheckpoint(
   182  	chain CheckpointChain,
   183  	cp Checkpoint,
   184  	sigs []CheckpointSignature,
   185  	local bool,
   186  ) (err error) {
   187  	cm.mtx.Lock()
   188  	defer cm.mtx.Unlock()
   189  
   190  	if curr, ok := cm.validated[cp.Number]; ok {
   191  		if curr.Checkpoint == cp {
   192  			return nil
   193  		}
   194  
   195  		if curr.Since > cp.Since {
   196  			return nil
   197  		}
   198  	}
   199  
   200  	if !local {
   201  		// ignore checkpoints which occur before the latest local checkpoint
   202  		var maxHardcodedCheckpoint uint64
   203  		genesis_hash := chain.GetHeaderByNumber(0).Hash()
   204  		for maxHardcodedCheckpoint = range energi_params.EnergiCheckpoints[genesis_hash] {
   205  			break
   206  		}
   207  		for n := range energi_params.EnergiCheckpoints[genesis_hash] {
   208  			if n > maxHardcodedCheckpoint {
   209  				maxHardcodedCheckpoint = n
   210  			}
   211  		}
   212  
   213  		if cp.Number <= maxHardcodedCheckpoint {
   214  			//log.Info("Ignoring checkpoint which occurs before latest checkpoint at", "block", maxHardcodedCheckpoint)
   215  			return nil
   216  		}
   217  
   218  		// TODO: proper validation and use of future checkpoints
   219  		if len(sigs) == 0 {
   220  			log.Warn("Checkpoint: missing signatures",
   221  				"num", cp.Number, "hash", cp.Hash)
   222  			return errors.New("missing checkpoint signatures")
   223  		}
   224  
   225  		// The first one must always be CPP_signer
   226  		pubkey, err := crypto.Ecrecover(cm.hashToSign(&cp), sigs[0][:])
   227  		if err != nil {
   228  			log.Warn("Checkpoint: failed to extract signature",
   229  				"num", cp.Number, "hash", cp.Hash, "err", err)
   230  			return err
   231  		}
   232  
   233  		// Check the primary signature
   234  		var signer common.Address
   235  		copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
   236  		if nrgconf := chain.Config().Energi; nrgconf == nil || signer != nrgconf.CPPSigner {
   237  			log.Warn("Checkpoint: invalid CPP signature", "num", cp.Number, "hash", cp.Hash)
   238  			return errors.New("invalid CPP signature")
   239  		}
   240  
   241  	}
   242  
   243  	//only received(non-hardcoded) checkpoints will be stored in validated map
   244  	if len(cm.validated) == MaxCachedCheckpoints {
   245  		oldestCheckpointHeight := oldestCheckpoint(cm.validated)
   246  		if cp.Number > oldestCheckpointHeight {
   247  			delete(cm.validated, oldestCheckpointHeight)
   248  			cm.validated[cp.Number] = validCheckpoint{
   249  				Checkpoint: cp,
   250  				signatures: append([]CheckpointSignature{}, sigs...),
   251  			}
   252  		}
   253  	} else {
   254  		cm.validated[cp.Number] = validCheckpoint{
   255  			Checkpoint: cp,
   256  			signatures: append([]CheckpointSignature{}, sigs...),
   257  		}
   258  	}
   259  
   260  	log.Info("Added new checkpoint", "checkpoint", cp, "local", local)
   261  
   262  	err = chain.EnforceCheckpoint(cp)
   263  
   264  	cm.updateLatest(chain, &cp)
   265  
   266  	if !local {
   267  		// Send regardless of enforcement success
   268  		cm.newCpFeed.Send(NewCheckpointEvent{CheckpointInfo{cp, sigs[0], uint64(len(sigs))}})
   269  	}
   270  
   271  	return err
   272  }
   273  
   274  func (cm *checkpointManager) hashToSign(cp *Checkpoint) []byte {
   275  	data := []byte("||Energi Blockchain Checkpoint||")
   276  	data = append(data, common.BigToHash(new(big.Int).SetUint64(cp.Number)).Bytes()...)
   277  	data = append(data, cp.Hash.Bytes()...)
   278  	return crypto.Keccak256(data)
   279  }
   280  
   281  func (cm *checkpointManager) updateLatest(chain CheckpointValidateChain, cp *Checkpoint) {
   282  	if cp.Number > cm.latest && cp.Number <= chain.CurrentHeader().Number.Uint64() {
   283  		cm.latest = cp.Number
   284  		log.Info("Latest checkpoint", "height", cp.Number, "hash", cp.Hash.Hex())
   285  	}
   286  }
   287  
   288  func (bc *BlockChain) EnforceCheckpoint(cp Checkpoint) error {
   289  	header := bc.GetHeaderByNumber(cp.Number)
   290  
   291  	if header != nil && header.Hash() != cp.Hash {
   292  		log.Error("Side chain is detected as canonical", "number", cp.Number, "hash", cp.Hash, "old", header.Hash())
   293  
   294  		if cp_block := bc.GetBlock(cp.Hash, cp.Number); cp_block != nil {
   295  			// Known block
   296  			bc.mu.Lock()
   297  			defer bc.mu.Unlock()
   298  
   299  			if err := bc.reorg(bc.GetBlock(header.Hash(), cp.Number), cp_block); err != nil {
   300  				log.Crit("Failed to reorg", "err", err)
   301  				// should terminate
   302  				return err
   303  			}
   304  
   305  			log.Warn("Chain reorg was successful, resuming normal operation")
   306  		} else {
   307  			// Unknown block
   308  			if err := bc.SetHead(cp.Number - 1); err != nil {
   309  				log.Crit("Failed to rewind before fork point", "err", err)
   310  				// should terminate
   311  				return err
   312  			}
   313  			log.Warn("Chain rewind was successful, resuming normal operation")
   314  		}
   315  	}
   316  
   317  	return nil
   318  }
   319  
   320  func (bc *BlockChain) ListCheckpoints() []CheckpointInfo {
   321  	cm := bc.checkpoints
   322  
   323  	cm.mtx.Lock()
   324  	defer cm.mtx.Unlock()
   325  
   326  	res := make([]CheckpointInfo, 0, len(cm.validated))
   327  
   328  	for _, v := range cm.validated {
   329  		if len(v.signatures) > 0 {
   330  			res = append(res, CheckpointInfo{v.Checkpoint, v.signatures[0], uint64(len(v.signatures))})
   331  		}
   332  	}
   333  
   334  	sort.Slice(res, func(i, j int) bool {
   335  		return res[i].Since > res[j].Since
   336  	})
   337  
   338  	return res
   339  }
   340  
   341  func (bc *BlockChain) CheckpointSignatures(cp Checkpoint) []CheckpointSignature {
   342  	cm := bc.checkpoints
   343  
   344  	cm.mtx.Lock()
   345  	defer cm.mtx.Unlock()
   346  
   347  	if vcp, ok := cm.validated[cp.Number]; ok && vcp.Hash == cp.Hash {
   348  		return append([]CheckpointSignature{}, vcp.signatures...)
   349  	}
   350  
   351  	return nil
   352  }
   353  
   354  func (bc *BlockChain) SubscribeNewCheckpointEvent(ch chan<- NewCheckpointEvent) event.Subscription {
   355  	return bc.scope.Track(bc.checkpoints.newCpFeed.Subscribe(ch))
   356  }
   357  
   358  func (bc *BlockChain) IsRunning() bool {
   359  	return atomic.LoadInt32(&bc.running) == 0
   360  }