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 }