github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/beacon/light/canonical.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package light
    18  
    19  import (
    20  	"encoding/binary"
    21  	"fmt"
    22  
    23  	"github.com/ethereum/go-ethereum/common/lru"
    24  	"github.com/ethereum/go-ethereum/ethdb"
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/rlp"
    27  )
    28  
    29  // canonicalStore stores instances of the given type in a database and caches
    30  // them in memory, associated with a continuous range of period numbers.
    31  // Note: canonicalStore is not thread safe and it is the caller's responsibility
    32  // to avoid concurrent access.
    33  type canonicalStore[T any] struct {
    34  	keyPrefix []byte
    35  	periods   periodRange
    36  	cache     *lru.Cache[uint64, T]
    37  }
    38  
    39  // newCanonicalStore creates a new canonicalStore and loads all keys associated
    40  // with the keyPrefix in order to determine the ranges available in the database.
    41  func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) {
    42  	cs := &canonicalStore[T]{
    43  		keyPrefix: keyPrefix,
    44  		cache:     lru.NewCache[uint64, T](100),
    45  	}
    46  	var (
    47  		iter  = db.NewIterator(keyPrefix, nil)
    48  		kl    = len(keyPrefix)
    49  		first = true
    50  	)
    51  	defer iter.Release()
    52  
    53  	for iter.Next() {
    54  		if len(iter.Key()) != kl+8 {
    55  			log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key()))
    56  			continue
    57  		}
    58  		period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8])
    59  		if first {
    60  			cs.periods.Start = period
    61  		} else if cs.periods.End != period {
    62  			return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1)
    63  		}
    64  		first = false
    65  		cs.periods.End = period + 1
    66  	}
    67  	return cs, nil
    68  }
    69  
    70  // databaseKey returns the database key belonging to the given period.
    71  func (cs *canonicalStore[T]) databaseKey(period uint64) []byte {
    72  	return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period)
    73  }
    74  
    75  // add adds the given item to the database. It also ensures that the range remains
    76  // continuous. Can be used either with a batch or database backend.
    77  func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error {
    78  	if !cs.periods.canExpand(period) {
    79  		return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period)
    80  	}
    81  	enc, err := rlp.EncodeToBytes(value)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	if err := backend.Put(cs.databaseKey(period), enc); err != nil {
    86  		return err
    87  	}
    88  	cs.cache.Add(period, value)
    89  	cs.periods.expand(period)
    90  	return nil
    91  }
    92  
    93  // deleteFrom removes items starting from the given period.
    94  func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) {
    95  	keepRange, deleteRange := cs.periods.split(fromPeriod)
    96  	deleteRange.each(func(period uint64) {
    97  		db.Delete(cs.databaseKey(period))
    98  		cs.cache.Remove(period)
    99  	})
   100  	cs.periods = keepRange
   101  	return deleteRange
   102  }
   103  
   104  // get returns the item at the given period or the null value of the given type
   105  // if no item is present.
   106  func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) {
   107  	var null, value T
   108  	if !cs.periods.contains(period) {
   109  		return null, false
   110  	}
   111  	if value, ok := cs.cache.Get(period); ok {
   112  		return value, true
   113  	}
   114  	enc, err := backend.Get(cs.databaseKey(period))
   115  	if err != nil {
   116  		log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End)
   117  		return null, false
   118  	}
   119  	if err := rlp.DecodeBytes(enc, &value); err != nil {
   120  		log.Error("Error decoding canonical store value", "error", err)
   121  		return null, false
   122  	}
   123  	cs.cache.Add(period, value)
   124  	return value, true
   125  }