diff --git a/validator/client/validator.go b/validator/client/validator.go index 2220d263b7..ed087f3f2a 100644 --- a/validator/client/validator.go +++ b/validator/client/validator.go @@ -134,6 +134,9 @@ func (v *validator) WaitForChainStart(ctx context.Context) error { return errors.Wrap(err, "could not receive ChainStart from stream") } v.genesisTime = chainStartRes.GenesisTime + if err := v.db.SaveGenesisValidatorsRoot(ctx, chainStartRes.GenesisValidatorsRoot); err != nil { + return errors.Wrap(err, "could not save genesis validator root") + } } // Once the ChainStart log is received, we update the genesis time of the validator client diff --git a/validator/client/validator_test.go b/validator/client/validator_test.go index 82f3b79fa6..2eef64ef1e 100644 --- a/validator/client/validator_test.go +++ b/validator/client/validator_test.go @@ -100,9 +100,11 @@ func TestWaitForChainStart_SetsChainStartGenesisTime(t *testing.T) { defer ctrl.Finish() client := mock.NewMockBeaconNodeValidatorClient(ctrl) + db := dbTest.SetupDB(t, [][48]byte{}) v := validator{ //keyManager: testKeyManager, validatorClient: client, + db: db, } genesis := uint64(time.Unix(1, 0).Unix()) genesisValidatorsRoot := bytesutil.ToBytes32([]byte("validators")) @@ -120,6 +122,10 @@ func TestWaitForChainStart_SetsChainStartGenesisTime(t *testing.T) { nil, ) require.NoError(t, v.WaitForChainStart(context.Background())) + savedGenValRoot, err := db.GenesisValidatorsRoot(context.Background()) + require.NoError(t, err) + + assert.DeepEqual(t, genesisValidatorsRoot[:], savedGenValRoot, "Unexpected saved genesis validator root") assert.Equal(t, genesis, v.genesisTime, "Unexpected chain start time") assert.NotNil(t, v.ticker, "Expected ticker to be set, received nil") } diff --git a/validator/db/iface/interface.go b/validator/db/iface/interface.go index 558d1b46f1..52f3ec038b 100644 --- a/validator/db/iface/interface.go +++ b/validator/db/iface/interface.go @@ -16,10 +16,16 @@ type ValidatorDB interface { DatabasePath() string ClearDB() error UpdatePublicKeysBuckets(publicKeys [][48]byte) error + + // Genesis information related methods. + GenesisValidatorsRoot(ctx context.Context) ([]byte, error) + SaveGenesisValidatorsRoot(ctx context.Context, genValRoot []byte) error + // Proposer protection related methods. ProposalHistoryForEpoch(ctx context.Context, publicKey []byte, epoch uint64) (bitfield.Bitlist, error) SaveProposalHistoryForEpoch(ctx context.Context, publicKey []byte, epoch uint64, history bitfield.Bitlist) error - //new data structure methods + + // New data structure methods ProposalHistoryForSlot(ctx context.Context, publicKey []byte, slot uint64) ([]byte, error) SaveProposalHistoryForSlot(ctx context.Context, pubKey []byte, slot uint64, signingRoot []byte) error SaveProposalHistoryForPubKeysV2(ctx context.Context, proposals map[[48]byte]kv.ProposalHistoryForPubkey) error diff --git a/validator/db/kv/BUILD.bazel b/validator/db/kv/BUILD.bazel index c4c7a01f39..54a4d5dd04 100644 --- a/validator/db/kv/BUILD.bazel +++ b/validator/db/kv/BUILD.bazel @@ -7,6 +7,7 @@ go_library( "attestation_history.go", "attestation_history_v2.go", "db.go", + "genesis.go", "manage.go", "proposal_history.go", "proposal_history_v2.go", @@ -36,6 +37,7 @@ go_test( "attestation_history_test.go", "attestation_history_v2_test.go", "db_test.go", + "genesis_test.go", "manage_test.go", "proposal_history_test.go", "proposal_history_v2_test.go", diff --git a/validator/db/kv/db.go b/validator/db/kv/db.go index aa82941554..fc5a00d234 100644 --- a/validator/db/kv/db.go +++ b/validator/db/kv/db.go @@ -85,6 +85,7 @@ func NewKVStore(dirPath string, pubKeys [][48]byte) (*Store, error) { if err := kv.db.Update(func(tx *bolt.Tx) error { return createBuckets( tx, + genesisInfoBucket, historicProposalsBucket, historicAttestationsBucket, newHistoricAttestationsBucket, diff --git a/validator/db/kv/genesis.go b/validator/db/kv/genesis.go new file mode 100644 index 0000000000..f1f7670735 --- /dev/null +++ b/validator/db/kv/genesis.go @@ -0,0 +1,36 @@ +package kv + +import ( + "context" + "fmt" + + bolt "go.etcd.io/bbolt" +) + +// SaveGenesisValidatorsRoot saves the genesis validator root to db. +func (s *Store) SaveGenesisValidatorsRoot(ctx context.Context, genValRoot []byte) error { + err := s.db.Update(func(tx *bolt.Tx) error { + bkt := tx.Bucket(genesisInfoBucket) + enc := bkt.Get(genesisValidatorsRootKey) + if len(enc) != 0 { + return fmt.Errorf("cannot overwite existing genesis validators root: %#x", enc) + } + return bkt.Put(genesisValidatorsRootKey, genValRoot) + }) + return err +} + +// GenesisValidatorsRoot retrieves the genesis validator root from db. +func (s *Store) GenesisValidatorsRoot(ctx context.Context) ([]byte, error) { + var genValRoot []byte + err := s.db.View(func(tx *bolt.Tx) error { + bkt := tx.Bucket(genesisInfoBucket) + enc := bkt.Get(genesisValidatorsRootKey) + if len(enc) == 0 { + return nil + } + genValRoot = enc + return nil + }) + return genValRoot, err +} diff --git a/validator/db/kv/genesis_test.go b/validator/db/kv/genesis_test.go new file mode 100644 index 0000000000..e0818e0ab9 --- /dev/null +++ b/validator/db/kv/genesis_test.go @@ -0,0 +1,43 @@ +package kv + +import ( + "context" + "testing" + + "github.com/prysmaticlabs/prysm/shared/params" + "github.com/prysmaticlabs/prysm/shared/testutil/require" +) + +func TestStore_GenesisValidatorsRoot_ReadAndWrite(t *testing.T) { + ctx := context.Background() + db := setupDB(t, [][48]byte{}) + tests := []struct { + name string + want []byte + write []byte + wantErr bool + }{ + { + name: "empty then write", + want: nil, + write: params.BeaconConfig().ZeroHash[:], + }, + { + name: "zero then overwrite rejected", + want: params.BeaconConfig().ZeroHash[:], + write: []byte{5}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := db.GenesisValidatorsRoot(ctx) + require.NoError(t, err) + require.DeepEqual(t, tt.want, got) + err = db.SaveGenesisValidatorsRoot(ctx, tt.write) + if (err != nil) != tt.wantErr { + t.Errorf("GenesisValidatorRoot() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/validator/db/kv/manage.go b/validator/db/kv/manage.go index 2a29cb7a4a..309f697137 100644 --- a/validator/db/kv/manage.go +++ b/validator/db/kv/manage.go @@ -30,7 +30,7 @@ type pubKeyAttestations struct { // Merge merges data from sourceStores into a new store, which is created in targetDirectory. func Merge(ctx context.Context, sourceStores []*Store, targetDirectory string) error { - ctx, span := trace.StartSpan(ctx, "Validator.Db.Manage") + ctx, span := trace.StartSpan(ctx, "Validator.Db.Merge") defer span.End() allProposals, allAttestations, err := getAllProposalsAndAllAttestations(sourceStores) @@ -43,7 +43,7 @@ func Merge(ctx context.Context, sourceStores []*Store, targetDirectory string) e // Split splits data from sourceStore into several stores, one for each public key in sourceStore. // Each new store is created in its own subdirectory inside targetDirectory. func Split(ctx context.Context, sourceStore *Store, targetDirectory string) error { - ctx, span := trace.StartSpan(ctx, "Validator.Db.Manage") + ctx, span := trace.StartSpan(ctx, "Validator.Db.Split") defer span.End() allProposals, allAttestations, err := getAllProposalsAndAllAttestations([]*Store{sourceStore}) diff --git a/validator/db/kv/schema.go b/validator/db/kv/schema.go index 3bf10d5969..e7c683c1d7 100644 --- a/validator/db/kv/schema.go +++ b/validator/db/kv/schema.go @@ -1,6 +1,11 @@ package kv var ( + // Genesis information bucket key. + genesisInfoBucket = []byte("genesis-info-bucket") + // Genesis validators root key. + genesisValidatorsRootKey = []byte("genesis-val-root") + // Validator slashing protection from double proposals. historicProposalsBucket = []byte("proposal-history-bucket") // Validator slashing protection from double proposals.