forked from theQRL/qrysm
/
proposer_protection.go
211 lines (192 loc) · 7.81 KB
/
proposer_protection.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package kv
import (
"context"
"fmt"
fieldparams "github.com/cyyber/qrysm/v4/config/fieldparams"
"github.com/cyyber/qrysm/v4/config/params"
"github.com/cyyber/qrysm/v4/consensus-types/primitives"
"github.com/cyyber/qrysm/v4/encoding/bytesutil"
"github.com/cyyber/qrysm/v4/time/slots"
"github.com/pkg/errors"
dilithium2 "github.com/theQRL/go-qrllib/dilithium"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// ProposalHistoryForPubkey for a validator public key.
type ProposalHistoryForPubkey struct {
Proposals []Proposal
}
// Proposal representation for a validator public key.
type Proposal struct {
Slot primitives.Slot `json:"slot"`
SigningRoot []byte `json:"signing_root"`
}
// ProposedPublicKeys retrieves all public keys in our proposals history bucket.
func (s *Store) ProposedPublicKeys(ctx context.Context) ([][dilithium2.CryptoPublicKeyBytes]byte, error) {
ctx, span := trace.StartSpan(ctx, "Validator.ProposedPublicKeys")
defer span.End()
var err error
proposedPublicKeys := make([][dilithium2.CryptoPublicKeyBytes]byte, 0)
err = s.view(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicProposalsBucket)
return bucket.ForEach(func(key []byte, _ []byte) error {
var pubKeyBytes [dilithium2.CryptoPublicKeyBytes]byte
copy(pubKeyBytes[:], key)
proposedPublicKeys = append(proposedPublicKeys, pubKeyBytes)
return nil
})
})
return proposedPublicKeys, err
}
// ProposalHistoryForSlot accepts a validator public key and returns the corresponding signing root as well
// as a boolean that tells us if we have a proposal history stored at the slot. It is possible we have proposed
// a slot but stored a nil signing root, so the boolean helps give full information.
func (s *Store) ProposalHistoryForSlot(ctx context.Context, publicKey [dilithium2.CryptoPublicKeyBytes]byte, slot primitives.Slot) ([32]byte, bool, error) {
ctx, span := trace.StartSpan(ctx, "Validator.ProposalHistoryForSlot")
defer span.End()
var err error
var proposalExists bool
var signingRoot [32]byte
err = s.view(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicProposalsBucket)
valBucket := bucket.Bucket(publicKey[:])
if valBucket == nil {
return nil
}
signingRootBytes := valBucket.Get(bytesutil.SlotToBytesBigEndian(slot))
if signingRootBytes == nil {
return nil
}
proposalExists = true
copy(signingRoot[:], signingRootBytes)
return nil
})
return signingRoot, proposalExists, err
}
// ProposalHistoryForPubKey returns the entire proposal history for a given public key.
func (s *Store) ProposalHistoryForPubKey(ctx context.Context, publicKey [dilithium2.CryptoPublicKeyBytes]byte) ([]*Proposal, error) {
ctx, span := trace.StartSpan(ctx, "Validator.ProposalHistoryForPubKey")
defer span.End()
proposals := make([]*Proposal, 0)
err := s.view(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicProposalsBucket)
valBucket := bucket.Bucket(publicKey[:])
if valBucket == nil {
return nil
}
return valBucket.ForEach(func(slotKey, signingRootBytes []byte) error {
slot := bytesutil.BytesToSlotBigEndian(slotKey)
sr := make([]byte, fieldparams.RootLength)
copy(sr, signingRootBytes)
proposals = append(proposals, &Proposal{
Slot: slot,
SigningRoot: sr,
})
return nil
})
})
return proposals, err
}
// SaveProposalHistoryForSlot saves the proposal history for the requested validator public key.
// We also check if the incoming proposal slot is lower than the lowest signed proposal slot
// for the validator and override its value on disk.
func (s *Store) SaveProposalHistoryForSlot(ctx context.Context, pubKey [dilithium2.CryptoPublicKeyBytes]byte, slot primitives.Slot, signingRoot []byte) error {
ctx, span := trace.StartSpan(ctx, "Validator.SaveProposalHistoryForEpoch")
defer span.End()
err := s.update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(historicProposalsBucket)
valBucket, err := bucket.CreateBucketIfNotExists(pubKey[:])
if err != nil {
return fmt.Errorf("could not create bucket for public key %#x", pubKey)
}
// If the incoming slot is lower than the lowest signed proposal slot, override.
lowestSignedBkt := tx.Bucket(lowestSignedProposalsBucket)
lowestSignedProposalBytes := lowestSignedBkt.Get(pubKey[:])
var lowestSignedProposalSlot primitives.Slot
if len(lowestSignedProposalBytes) >= 8 {
lowestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(lowestSignedProposalBytes)
}
if len(lowestSignedProposalBytes) == 0 || slot < lowestSignedProposalSlot {
if err := lowestSignedBkt.Put(pubKey[:], bytesutil.SlotToBytesBigEndian(slot)); err != nil {
return err
}
}
// If the incoming slot is higher than the highest signed proposal slot, override.
highestSignedBkt := tx.Bucket(highestSignedProposalsBucket)
highestSignedProposalBytes := highestSignedBkt.Get(pubKey[:])
var highestSignedProposalSlot primitives.Slot
if len(highestSignedProposalBytes) >= 8 {
highestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(highestSignedProposalBytes)
}
if len(highestSignedProposalBytes) == 0 || slot > highestSignedProposalSlot {
if err := highestSignedBkt.Put(pubKey[:], bytesutil.SlotToBytesBigEndian(slot)); err != nil {
return err
}
}
if err := valBucket.Put(bytesutil.SlotToBytesBigEndian(slot), signingRoot); err != nil {
return err
}
return pruneProposalHistoryBySlot(valBucket, slot)
})
return err
}
// LowestSignedProposal returns the lowest signed proposal slot for a validator public key.
// If no data exists, a boolean of value false is returned.
func (s *Store) LowestSignedProposal(ctx context.Context, publicKey [dilithium2.CryptoPublicKeyBytes]byte) (primitives.Slot, bool, error) {
ctx, span := trace.StartSpan(ctx, "Validator.LowestSignedProposal")
defer span.End()
var err error
var lowestSignedProposalSlot primitives.Slot
var exists bool
err = s.view(func(tx *bolt.Tx) error {
bucket := tx.Bucket(lowestSignedProposalsBucket)
lowestSignedProposalBytes := bucket.Get(publicKey[:])
// 8 because bytesutil.BytesToUint64BigEndian will return 0 if input is less than 8 bytes.
if len(lowestSignedProposalBytes) < 8 {
return nil
}
exists = true
lowestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(lowestSignedProposalBytes)
return nil
})
return lowestSignedProposalSlot, exists, err
}
// HighestSignedProposal returns the highest signed proposal slot for a validator public key.
// If no data exists, a boolean of value false is returned.
func (s *Store) HighestSignedProposal(ctx context.Context, publicKey [dilithium2.CryptoPublicKeyBytes]byte) (primitives.Slot, bool, error) {
ctx, span := trace.StartSpan(ctx, "Validator.HighestSignedProposal")
defer span.End()
var err error
var highestSignedProposalSlot primitives.Slot
var exists bool
err = s.view(func(tx *bolt.Tx) error {
bucket := tx.Bucket(highestSignedProposalsBucket)
highestSignedProposalBytes := bucket.Get(publicKey[:])
// 8 because bytesutil.BytesToUint64BigEndian will return 0 if input is less than 8 bytes.
if len(highestSignedProposalBytes) < 8 {
return nil
}
exists = true
highestSignedProposalSlot = bytesutil.BytesToSlotBigEndian(highestSignedProposalBytes)
return nil
})
return highestSignedProposalSlot, exists, err
}
func pruneProposalHistoryBySlot(valBucket *bolt.Bucket, newestSlot primitives.Slot) error {
c := valBucket.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.First() {
slot := bytesutil.BytesToSlotBigEndian(k)
epoch := slots.ToEpoch(slot)
newestEpoch := slots.ToEpoch(newestSlot)
// Only delete epochs that are older than the weak subjectivity period.
if epoch+params.BeaconConfig().WeakSubjectivityPeriod <= newestEpoch {
if err := c.Delete(); err != nil {
return errors.Wrapf(err, "could not prune epoch %d in proposal history", epoch)
}
} else {
// If starting from the oldest, we don't find anything prunable, stop pruning.
break
}
}
return nil
}