|
| 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 | +} |
0 commit comments