Skip to content

Commit caac08d

Browse files
authored
Add Batch Method for Reading Validator Proposing Histories (#8378)
* add in batch method * add in new proposal history methods for efficiency and progress bars * tests fixed to use the new methods * add back get slot proposing history method * add gaz
1 parent 3fd8c4c commit caac08d

File tree

7 files changed

+121
-73
lines changed

7 files changed

+121
-73
lines changed

validator/db/iface/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type ValidatorDB interface {
3030
// Proposer protection related methods.
3131
HighestSignedProposal(ctx context.Context, publicKey [48]byte) (uint64, bool, error)
3232
LowestSignedProposal(ctx context.Context, publicKey [48]byte) (uint64, bool, error)
33+
ProposalHistoryForPubKey(ctx context.Context, publicKey [48]byte) ([]*kv.Proposal, error)
3334
ProposalHistoryForSlot(ctx context.Context, publicKey [48]byte, slot uint64) ([32]byte, bool, error)
3435
SaveProposalHistoryForSlot(ctx context.Context, pubKey [48]byte, slot uint64, signingRoot []byte) error
3536
ProposedPublicKeys(ctx context.Context) ([][48]byte, error)

validator/db/kv/proposer_protection.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,32 @@ func (s *Store) ProposalHistoryForSlot(ctx context.Context, publicKey [48]byte,
6868
return signingRoot, proposalExists, err
6969
}
7070

71+
// ProposalHistoryForPubKey returns the entire proposal history for a given public key.
72+
func (s *Store) ProposalHistoryForPubKey(ctx context.Context, publicKey [48]byte) ([]*Proposal, error) {
73+
ctx, span := trace.StartSpan(ctx, "Validator.ProposalHistoryForPubKey")
74+
defer span.End()
75+
76+
proposals := make([]*Proposal, 0)
77+
err := s.view(func(tx *bolt.Tx) error {
78+
bucket := tx.Bucket(historicProposalsBucket)
79+
valBucket := bucket.Bucket(publicKey[:])
80+
if valBucket == nil {
81+
return nil
82+
}
83+
return valBucket.ForEach(func(slotKey, signingRootBytes []byte) error {
84+
slot := bytesutil.BytesToUint64BigEndian(slotKey)
85+
sr := make([]byte, 32)
86+
copy(sr, signingRootBytes)
87+
proposals = append(proposals, &Proposal{
88+
Slot: slot,
89+
SigningRoot: sr,
90+
})
91+
return nil
92+
})
93+
})
94+
return proposals, err
95+
}
96+
7197
// SaveProposalHistoryForSlot saves the proposal history for the requested validator public key.
7298
// We also check if the incoming proposal slot is lower than the lowest signed proposal slot
7399
// for the validator and override its value on disk.

validator/db/kv/proposer_protection_test.go

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,37 +46,49 @@ func TestSaveProposalHistoryForSlot_OK(t *testing.T) {
4646
require.DeepEqual(t, bytesutil.PadTo([]byte{1}, 32), signingRoot[:], "Expected DB to keep object the same")
4747
}
4848

49-
func TestSaveProposalHistoryForSlot_Empty(t *testing.T) {
49+
func TestNewProposalHistoryForPubKey_ReturnsEmptyIfNoHistory(t *testing.T) {
50+
valPubkey := [48]byte{1, 2, 3}
51+
db := setupDB(t, [][48]byte{})
52+
53+
proposalHistory, err := db.ProposalHistoryForPubKey(context.Background(), valPubkey)
54+
require.NoError(t, err)
55+
assert.DeepEqual(t, make([]*Proposal, 0), proposalHistory)
56+
}
57+
58+
func TestSaveProposalHistoryForPubKey_OK(t *testing.T) {
5059
pubkey := [48]byte{3}
5160
db := setupDB(t, [][48]byte{pubkey})
5261

5362
slot := uint64(2)
54-
emptySlot := uint64(120)
55-
err := db.SaveProposalHistoryForSlot(context.Background(), pubkey, slot, []byte{1})
63+
64+
root := [32]byte{1}
65+
err := db.SaveProposalHistoryForSlot(context.Background(), pubkey, slot, root[:])
5666
require.NoError(t, err, "Saving proposal history failed: %v")
57-
signingRoot, _, err := db.ProposalHistoryForSlot(context.Background(), pubkey, emptySlot)
67+
proposalHistory, err := db.ProposalHistoryForPubKey(context.Background(), pubkey)
5868
require.NoError(t, err, "Failed to get proposal history")
5969

60-
require.NotNil(t, signingRoot)
61-
require.DeepEqual(t, bytesutil.PadTo([]byte{}, 32), signingRoot[:], "Expected DB to keep object the same")
70+
require.NotNil(t, proposalHistory)
71+
want := []*Proposal{
72+
{
73+
Slot: slot,
74+
SigningRoot: root[:],
75+
},
76+
}
77+
require.DeepEqual(t, want[0], proposalHistory[0])
6278
}
6379

6480
func TestSaveProposalHistoryForSlot_Overwrites(t *testing.T) {
6581
pubkey := [48]byte{0}
6682
tests := []struct {
67-
slot uint64
6883
signingRoot []byte
6984
}{
7085
{
71-
slot: uint64(1),
7286
signingRoot: bytesutil.PadTo([]byte{1}, 32),
7387
},
7488
{
75-
slot: uint64(2),
7689
signingRoot: bytesutil.PadTo([]byte{2}, 32),
7790
},
7891
{
79-
slot: uint64(1),
8092
signingRoot: bytesutil.PadTo([]byte{3}, 32),
8193
},
8294
}
@@ -85,11 +97,11 @@ func TestSaveProposalHistoryForSlot_Overwrites(t *testing.T) {
8597
db := setupDB(t, [][48]byte{pubkey})
8698
err := db.SaveProposalHistoryForSlot(context.Background(), pubkey, 0, tt.signingRoot)
8799
require.NoError(t, err, "Saving proposal history failed")
88-
signingRoot, _, err := db.ProposalHistoryForSlot(context.Background(), pubkey, 0)
100+
proposalHistory, err := db.ProposalHistoryForPubKey(context.Background(), pubkey)
89101
require.NoError(t, err, "Failed to get proposal history")
90102

91-
require.NotNil(t, signingRoot)
92-
require.DeepEqual(t, tt.signingRoot, signingRoot[:], "Expected DB to keep object the same")
103+
require.NotNil(t, proposalHistory)
104+
require.DeepEqual(t, tt.signingRoot, proposalHistory[0].SigningRoot, "Expected DB to keep object the same")
93105
require.NoError(t, db.Close(), "Failed to close database")
94106
}
95107
}
@@ -143,15 +155,22 @@ func TestPruneProposalHistoryBySlot_OK(t *testing.T) {
143155
require.NoError(t, err, "Saving proposal history failed")
144156
}
145157

158+
signingRootsBySlot := make(map[uint64][]byte)
159+
proposalHistory, err := db.ProposalHistoryForPubKey(context.Background(), pubKey)
160+
require.NoError(t, err)
161+
162+
for _, hist := range proposalHistory {
163+
signingRootsBySlot[hist.Slot] = hist.SigningRoot
164+
}
165+
146166
for _, slot := range tt.removedSlots {
147-
sr, _, err := db.ProposalHistoryForSlot(context.Background(), pubKey, slot)
148-
require.NoError(t, err, "Failed to get proposal history")
149-
require.DeepEqual(t, bytesutil.PadTo([]byte{}, 32), sr[:], "Unexpected difference in bytes for epoch %d", slot)
167+
_, ok := signingRootsBySlot[slot]
168+
require.Equal(t, false, ok)
150169
}
151170
for _, slot := range tt.storedSlots {
152-
sr, _, err := db.ProposalHistoryForSlot(context.Background(), pubKey, slot)
153-
require.NoError(t, err, "Failed to get proposal history")
154-
require.DeepEqual(t, signedRoot, sr[:], "Unexpected difference in bytes for epoch %d", slot)
171+
root, ok := signingRootsBySlot[slot]
172+
require.Equal(t, true, ok)
173+
require.DeepEqual(t, signedRoot, root, "Unexpected difference in bytes for epoch %d", slot)
155174
}
156175
require.NoError(t, db.Close(), "Failed to close database")
157176
}

validator/slashing-protection/local/standard-protection-format/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_library(
1414
deps = [
1515
"//shared/bytesutil:go_default_library",
1616
"//shared/params:go_default_library",
17+
"//shared/progressutil:go_default_library",
1718
"//shared/slashutil:go_default_library",
1819
"//validator/db:go_default_library",
1920
"//validator/db/kv:go_default_library",
@@ -36,7 +37,6 @@ go_test(
3637
],
3738
embed = [":go_default_library"],
3839
deps = [
39-
"//shared/params:go_default_library",
4040
"//shared/testutil/assert:go_default_library",
4141
"//shared/testutil/require:go_default_library",
4242
"//validator/db/kv:go_default_library",

validator/slashing-protection/local/standard-protection-format/export.go

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/pkg/errors"
1111
"github.com/prysmaticlabs/prysm/shared/params"
12+
"github.com/prysmaticlabs/prysm/shared/progressutil"
1213
"github.com/prysmaticlabs/prysm/validator/db"
1314
"github.com/prysmaticlabs/prysm/validator/slashing-protection/local/standard-protection-format/format"
1415
)
@@ -40,6 +41,9 @@ func ExportStandardProtectionJSON(ctx context.Context, validatorDB db.Database)
4041
dataByPubKey := make(map[[48]byte]*format.ProtectionData)
4142

4243
// Extract the signed proposals by public key.
44+
progress := progressutil.InitializeProgressBar(
45+
len(proposedPublicKeys), "Extracting signed blocks by validator public key",
46+
)
4347
for _, pubKey := range proposedPublicKeys {
4448
pubKeyHex, err := pubKeyToHexString(pubKey[:])
4549
if err != nil {
@@ -54,9 +58,15 @@ func ExportStandardProtectionJSON(ctx context.Context, validatorDB db.Database)
5458
SignedBlocks: signedBlocks,
5559
SignedAttestations: nil,
5660
}
61+
if err := progress.Add(1); err != nil {
62+
return nil, err
63+
}
5764
}
5865

5966
// Extract the signed attestations by public key.
67+
progress = progressutil.InitializeProgressBar(
68+
len(proposedPublicKeys), "Extracting signed attestations by validator public key",
69+
)
6070
for _, pubKey := range attestedPublicKeys {
6171
pubKeyHex, err := pubKeyToHexString(pubKey[:])
6272
if err != nil {
@@ -75,6 +85,9 @@ func ExportStandardProtectionJSON(ctx context.Context, validatorDB db.Database)
7585
SignedAttestations: signedAttestations,
7686
}
7787
}
88+
if err := progress.Add(1); err != nil {
89+
return nil, err
90+
}
7891
}
7992

8093
// Next we turn our map into a slice as expected by the EIP-3076 JSON standard.
@@ -129,39 +142,23 @@ func signedBlocksByPubKey(ctx context.Context, validatorDB db.Database, pubKey [
129142
// in our database, we return nil. This way, a user will be able to export their
130143
// slashing protection history even if one of their keys does not have a history
131144
// of signed blocks.
132-
lowestSignedSlot, exists, err := validatorDB.LowestSignedProposal(ctx, pubKey)
133-
if err != nil {
134-
return nil, err
135-
}
136-
if !exists {
137-
return nil, nil
138-
}
139-
highestSignedSlot, exists, err := validatorDB.HighestSignedProposal(ctx, pubKey)
145+
proposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, pubKey)
140146
if err != nil {
141147
return nil, err
142148
}
143-
if !exists {
144-
return nil, nil
145-
}
146149
signedBlocks := make([]*format.SignedBlock, 0)
147-
for i := lowestSignedSlot; i <= highestSignedSlot; i++ {
150+
for _, proposal := range proposalHistory {
148151
if ctx.Err() != nil {
149152
return nil, ctx.Err()
150153
}
151-
signingRoot, exists, err := validatorDB.ProposalHistoryForSlot(ctx, pubKey, i)
154+
signingRootHex, err := rootToHexString(proposal.SigningRoot)
152155
if err != nil {
153156
return nil, err
154157
}
155-
if exists {
156-
signingRootHex, err := rootToHexString(signingRoot[:])
157-
if err != nil {
158-
return nil, err
159-
}
160-
signedBlocks = append(signedBlocks, &format.SignedBlock{
161-
Slot: fmt.Sprintf("%d", i),
162-
SigningRoot: signingRootHex,
163-
})
164-
}
158+
signedBlocks = append(signedBlocks, &format.SignedBlock{
159+
Slot: fmt.Sprintf("%d", proposal.Slot),
160+
SigningRoot: signingRootHex,
161+
})
165162
}
166163
return signedBlocks, nil
167164
}

validator/slashing-protection/local/standard-protection-format/import_test.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"testing"
1111

1212
ethpb "github.com/prysmaticlabs/ethereumapis/eth/v1alpha1"
13-
"github.com/prysmaticlabs/prysm/shared/params"
1413
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
1514
"github.com/prysmaticlabs/prysm/shared/testutil/require"
1615
"github.com/prysmaticlabs/prysm/validator/db/kv"
@@ -91,17 +90,14 @@ func TestStore_ImportInterchangeData_BadFormat_PreventsDBWrites(t *testing.T) {
9190
require.NoError(t, err)
9291
require.Equal(t, kv.NotSlashable, slashingKind)
9392
}
94-
proposals := proposalHistory[i].Proposals
95-
for _, proposal := range proposals {
96-
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
97-
require.NoError(t, err)
98-
require.DeepEqual(
99-
t,
100-
params.BeaconConfig().ZeroHash,
101-
receivedProposalSigningRoot,
102-
"Imported proposal signing root is different than the empty default",
103-
)
104-
}
93+
receivedHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
94+
require.NoError(t, err)
95+
require.DeepEqual(
96+
t,
97+
make([]*kv.Proposal, 0),
98+
receivedHistory,
99+
"Imported proposal signing root is different than the empty default",
100+
)
105101
}
106102
}
107103

@@ -150,12 +146,19 @@ func TestStore_ImportInterchangeData_OK(t *testing.T) {
150146
}
151147

152148
proposals := proposalHistory[i].Proposals
149+
150+
receivedProposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
151+
require.NoError(t, err)
152+
rootsBySlot := make(map[uint64][]byte)
153+
for _, proposal := range receivedProposalHistory {
154+
rootsBySlot[proposal.Slot] = proposal.SigningRoot
155+
}
153156
for _, proposal := range proposals {
154-
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
155-
require.NoError(t, err)
157+
receivedRoot, ok := rootsBySlot[proposal.Slot]
158+
require.DeepEqual(t, true, ok)
156159
require.DeepEqual(
157160
t,
158-
receivedProposalSigningRoot[:],
161+
receivedRoot,
159162
proposal.SigningRoot,
160163
"Imported proposals are different then the generated ones",
161164
)

validator/slashing-protection/local/standard-protection-format/round_trip_test.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"testing"
99

10-
"github.com/prysmaticlabs/prysm/shared/params"
1110
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
1211
"github.com/prysmaticlabs/prysm/shared/testutil/require"
1312
"github.com/prysmaticlabs/prysm/validator/db/kv"
@@ -168,12 +167,18 @@ func TestImportInterchangeData_OK(t *testing.T) {
168167
}
169168

170169
proposals := proposalHistory[i].Proposals
170+
receivedProposalHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
171+
require.NoError(t, err)
172+
rootsBySlot := make(map[uint64][]byte)
173+
for _, proposal := range receivedProposalHistory {
174+
rootsBySlot[proposal.Slot] = proposal.SigningRoot
175+
}
171176
for _, proposal := range proposals {
172-
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
173-
require.NoError(t, err)
177+
receivedRoot, ok := rootsBySlot[proposal.Slot]
178+
require.DeepEqual(t, true, ok)
174179
require.DeepEqual(
175180
t,
176-
receivedProposalSigningRoot[:],
181+
receivedRoot,
177182
proposal.SigningRoot,
178183
"Imported proposals are different then the generated ones",
179184
)
@@ -309,16 +314,13 @@ func TestStore_ImportInterchangeData_BadFormat_PreventsDBWrites(t *testing.T) {
309314
len(receivedAttestingHistory),
310315
"Imported attestation protection history is different than the empty default",
311316
)
312-
proposals := proposalHistory[i].Proposals
313-
for _, proposal := range proposals {
314-
receivedProposalSigningRoot, _, err := validatorDB.ProposalHistoryForSlot(ctx, publicKeys[i], proposal.Slot)
315-
require.NoError(t, err)
316-
require.DeepEqual(
317-
t,
318-
params.BeaconConfig().ZeroHash,
319-
receivedProposalSigningRoot,
320-
"Imported proposal signing root is different than the empty default",
321-
)
322-
}
317+
receivedHistory, err := validatorDB.ProposalHistoryForPubKey(ctx, publicKeys[i])
318+
require.NoError(t, err)
319+
require.DeepEqual(
320+
t,
321+
make([]*kv.Proposal, 0),
322+
receivedHistory,
323+
"Imported proposal signing root is different than the empty default",
324+
)
323325
}
324326
}

0 commit comments

Comments
 (0)