Skip to content

Commit dead6c8

Browse files
zsfelfoldiGrapeBaBa
authored andcommitted
beacon/light: add CommitteeChain (ethereum#27766)
This change implements CommitteeChain which is a key component of the beacon light client. It is a passive data structure that can validate, hold and update a chain of beacon light sync committees and updates, starting from a checkpoint that proves the starting committee through a beacon block hash, header and corresponding state. Once synced to the current sync period, CommitteeChain can also validate signed beacon headers.
1 parent 6424e87 commit dead6c8

File tree

7 files changed

+1247
-0
lines changed

7 files changed

+1247
-0
lines changed

beacon/light/canonical.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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

Comments
 (0)