github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/eth/downloader/whitelist/service.go (about)

     1  package whitelist
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/ethereum/go-ethereum/common"
     9  	"github.com/ethereum/go-ethereum/core/types"
    10  	"github.com/ethereum/go-ethereum/log"
    11  )
    12  
    13  // Checkpoint whitelist
    14  type Service struct {
    15  	m                   sync.Mutex
    16  	checkpointWhitelist map[uint64]common.Hash // Checkpoint whitelist, populated by reaching out to heimdall
    17  	checkpointOrder     []uint64               // Checkpoint order, populated by reaching out to heimdall
    18  	maxCapacity         uint                   // Max capacity of the whitelist
    19  	checkpointInterval  uint64                 // Checkpoint interval, until which we can allow importing
    20  }
    21  
    22  func NewService(maxCapacity uint) *Service {
    23  	return &Service{
    24  		checkpointWhitelist: make(map[uint64]common.Hash),
    25  		checkpointOrder:     []uint64{},
    26  		maxCapacity:         maxCapacity,
    27  		checkpointInterval:  256, // TODO: make it configurable through params?
    28  	}
    29  }
    30  
    31  var (
    32  	ErrCheckpointMismatch = errors.New("checkpoint mismatch")
    33  	ErrLongFutureChain    = errors.New("received future chain of unacceptable length")
    34  	ErrNoRemoteCheckpoint = errors.New("remote peer doesn't have a checkpoint")
    35  )
    36  
    37  // IsValidPeer checks if the chain we're about to receive from a peer is valid or not
    38  // in terms of reorgs. We won't reorg beyond the last bor checkpoint submitted to mainchain.
    39  func (w *Service) IsValidPeer(remoteHeader *types.Header, fetchHeadersByNumber func(number uint64, amount int, skip int, reverse bool) ([]*types.Header, []common.Hash, error)) (bool, error) {
    40  	// We want to validate the chain by comparing the last checkpointed block
    41  	// we're storing in `checkpointWhitelist` with the peer's block.
    42  	//
    43  	// Check for availaibility of the last checkpointed block.
    44  	// This can be also be empty if our heimdall is not responding
    45  	// or we're running without it.
    46  	if len(w.checkpointWhitelist) == 0 {
    47  		// worst case, we don't have the checkpoints in memory
    48  		return true, nil
    49  	}
    50  
    51  	// Fetch the last checkpoint entry
    52  	lastCheckpointBlockNum := w.checkpointOrder[len(w.checkpointOrder)-1]
    53  	lastCheckpointBlockHash := w.checkpointWhitelist[lastCheckpointBlockNum]
    54  
    55  	// todo: we can extract this as an interface and mock as well or just test IsValidChain in isolation from downloader passing fake fetchHeadersByNumber functions
    56  	headers, hashes, err := fetchHeadersByNumber(lastCheckpointBlockNum, 1, 0, false)
    57  	if err != nil {
    58  		return false, fmt.Errorf("%w: last checkpoint %d, err %v", ErrNoRemoteCheckpoint, lastCheckpointBlockNum, err)
    59  	}
    60  
    61  	if len(headers) == 0 {
    62  		return false, fmt.Errorf("%w: last checkpoint %d", ErrNoRemoteCheckpoint, lastCheckpointBlockNum)
    63  	}
    64  
    65  	reqBlockNum := headers[0].Number.Uint64()
    66  	reqBlockHash := hashes[0]
    67  
    68  	// Check against the checkpointed blocks
    69  	if reqBlockNum == lastCheckpointBlockNum && reqBlockHash == lastCheckpointBlockHash {
    70  		return true, nil
    71  	}
    72  
    73  	return false, ErrCheckpointMismatch
    74  }
    75  
    76  // IsValidChain checks the validity of chain by comparing it
    77  // against the local checkpoint entries
    78  func (w *Service) IsValidChain(currentHeader *types.Header, chain []*types.Header) (bool, error) {
    79  	// Check if we have checkpoints to validate incoming chain in memory
    80  	if len(w.checkpointWhitelist) == 0 {
    81  		// We don't have any entries, no additional validation will be possible
    82  		return true, nil
    83  	}
    84  
    85  	// Return if we've received empty chain
    86  	if len(chain) == 0 {
    87  		return false, nil
    88  	}
    89  
    90  	var (
    91  		oldestCheckpointNumber uint64 = w.checkpointOrder[0]
    92  		current                uint64 = currentHeader.Number.Uint64()
    93  	)
    94  
    95  	// Check if we have whitelist entries in required range
    96  	if chain[len(chain)-1].Number.Uint64() < oldestCheckpointNumber {
    97  		// We have future whitelisted entries, so no additional validation will be possible
    98  		// This case will occur when bor is in middle of sync, but heimdall is ahead/fully synced.
    99  		return true, nil
   100  	}
   101  
   102  	// Split the chain into past and future chain
   103  	pastChain, _ := splitChain(current, chain)
   104  
   105  	// Note: Do not act on future chain and allow importing all kinds of future chains.
   106  
   107  	// Add an offset to future chain if it's not in continuity
   108  	// offset := 0
   109  	// if len(futureChain) != 0 {
   110  	// 	offset += int(futureChain[0].Number.Uint64()-currentHeader.Number.Uint64()) - 1
   111  	// }
   112  
   113  	// Don't accept future chain of unacceptable length (from current block)
   114  	// if len(futureChain)+offset > int(w.checkpointInterval) {
   115  	// 	return false, ErrLongFutureChain
   116  	// }
   117  
   118  	// Iterate over the chain and validate against the last checkpoint
   119  	// It will handle all cases where the incoming chain has atleast one checkpoint
   120  	for i := len(pastChain) - 1; i >= 0; i-- {
   121  		if _, ok := w.checkpointWhitelist[pastChain[i].Number.Uint64()]; ok {
   122  			return pastChain[i].Hash() == w.checkpointWhitelist[pastChain[i].Number.Uint64()], nil
   123  		}
   124  	}
   125  
   126  	return true, nil
   127  }
   128  
   129  func splitChain(current uint64, chain []*types.Header) ([]*types.Header, []*types.Header) {
   130  	var (
   131  		pastChain   []*types.Header
   132  		futureChain []*types.Header
   133  		first       uint64 = chain[0].Number.Uint64()
   134  		last        uint64 = chain[len(chain)-1].Number.Uint64()
   135  	)
   136  
   137  	if current >= first {
   138  		if len(chain) == 1 || current >= last {
   139  			pastChain = chain
   140  		} else {
   141  			pastChain = chain[:current-first+1]
   142  		}
   143  	}
   144  
   145  	if current < last {
   146  		if len(chain) == 1 || current < first {
   147  			futureChain = chain
   148  		} else {
   149  			futureChain = chain[current-first+1:]
   150  		}
   151  	}
   152  
   153  	return pastChain, futureChain
   154  }
   155  
   156  func (w *Service) ProcessCheckpoint(endBlockNum uint64, endBlockHash common.Hash) {
   157  	w.m.Lock()
   158  	defer w.m.Unlock()
   159  
   160  	w.enqueueCheckpointWhitelist(endBlockNum, endBlockHash)
   161  	// If size of checkpoint whitelist map is greater than 10, remove the oldest entry.
   162  
   163  	if w.length() > int(w.maxCapacity) {
   164  		w.dequeueCheckpointWhitelist()
   165  	}
   166  }
   167  
   168  // GetCheckpointWhitelist returns the existing whitelisted
   169  // entries of checkpoint of the form block number -> block hash.
   170  func (w *Service) GetCheckpointWhitelist() map[uint64]common.Hash {
   171  	w.m.Lock()
   172  	defer w.m.Unlock()
   173  
   174  	return w.checkpointWhitelist
   175  }
   176  
   177  // PurgeCheckpointWhitelist purges data from checkpoint whitelist map
   178  func (w *Service) PurgeCheckpointWhitelist() {
   179  	w.m.Lock()
   180  	defer w.m.Unlock()
   181  
   182  	w.checkpointWhitelist = make(map[uint64]common.Hash)
   183  	w.checkpointOrder = make([]uint64, 0)
   184  }
   185  
   186  // EnqueueWhitelistBlock enqueues blockNumber, blockHash to the checkpoint whitelist map
   187  func (w *Service) enqueueCheckpointWhitelist(key uint64, val common.Hash) {
   188  	if _, ok := w.checkpointWhitelist[key]; !ok {
   189  		log.Debug("Enqueing new checkpoint whitelist", "block number", key, "block hash", val)
   190  
   191  		w.checkpointWhitelist[key] = val
   192  		w.checkpointOrder = append(w.checkpointOrder, key)
   193  	}
   194  }
   195  
   196  // DequeueWhitelistBlock dequeues block, blockhash from the checkpoint whitelist map
   197  func (w *Service) dequeueCheckpointWhitelist() {
   198  	if len(w.checkpointOrder) > 0 {
   199  		log.Debug("Dequeing checkpoint whitelist", "block number", w.checkpointOrder[0], "block hash", w.checkpointWhitelist[w.checkpointOrder[0]])
   200  
   201  		delete(w.checkpointWhitelist, w.checkpointOrder[0])
   202  		w.checkpointOrder = w.checkpointOrder[1:] // fixme: this slice is growing infinitely and never will be released. also a panic is possible if the last element is going to be removed
   203  	}
   204  }
   205  
   206  // length returns the len of the whitelist.
   207  func (w *Service) length() int {
   208  	return len(w.checkpointWhitelist)
   209  }