mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Graffiti ordered index (#8482)
* Added ordered option to graffiti file * Updated validator to use Ordered graffiti * Track graffiti ordered index in db * Update `ordered` to only emit each graffiti once Co-authored-by: pinglamb <pinglambs@gmail.com>
This commit is contained in:
@@ -305,7 +305,18 @@ func (v *validator) getGraffiti(ctx context.Context, pubKey [48]byte) ([]byte, e
|
||||
return []byte(g), nil
|
||||
}
|
||||
|
||||
// When specified, a graffiti from the random list in the file take third priority.
|
||||
// When specified, a graffiti from the ordered list in the file take third priority.
|
||||
if v.graffitiOrderedIndex < uint64(len(v.graffitiStruct.Ordered)) {
|
||||
graffiti := v.graffitiStruct.Ordered[v.graffitiOrderedIndex]
|
||||
v.graffitiOrderedIndex = v.graffitiOrderedIndex + 1
|
||||
err := v.db.SaveGraffitiOrderedIndex(ctx, v.graffitiOrderedIndex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to update graffiti ordered index")
|
||||
}
|
||||
return []byte(graffiti), nil
|
||||
}
|
||||
|
||||
// When specified, a graffiti from the random list in the file take fourth priority.
|
||||
if len(v.graffitiStruct.Random) != 0 {
|
||||
r := rand.NewGenerator()
|
||||
r.Seed(time.Now().Unix())
|
||||
|
||||
@@ -744,3 +744,30 @@ func TestGetGraffiti_Ok(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGraffitiOrdered_Ok(t *testing.T) {
|
||||
pubKey := [48]byte{'a'}
|
||||
valDB := testing2.SetupDB(t, [][48]byte{pubKey})
|
||||
ctrl := gomock.NewController(t)
|
||||
m := &mocks{
|
||||
validatorClient: mock.NewMockBeaconNodeValidatorClient(ctrl),
|
||||
}
|
||||
m.validatorClient.EXPECT().
|
||||
ValidatorIndex(gomock.Any(), ðpb.ValidatorIndexRequest{PublicKey: pubKey[:]}).
|
||||
Times(5).
|
||||
Return(ðpb.ValidatorIndexResponse{Index: 2}, nil)
|
||||
|
||||
v := &validator{
|
||||
db: valDB,
|
||||
validatorClient: m.validatorClient,
|
||||
graffitiStruct: &graffiti.Graffiti{
|
||||
Ordered: []string{"a", "b", "c"},
|
||||
Default: "d",
|
||||
},
|
||||
}
|
||||
for _, want := range [][]byte{[]byte{'a'}, []byte{'b'}, []byte{'c'}, []byte{'d'}, []byte{'d'}} {
|
||||
got, err := v.getGraffiti(context.Background(), pubKey)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,12 @@ func (v *ValidatorService) Start() {
|
||||
slashablePublicKeys[pubKey] = true
|
||||
}
|
||||
|
||||
graffitiOrderedIndex, err := v.db.GraffitiOrderedIndex(v.ctx, v.graffitiStruct.Hash)
|
||||
if err != nil {
|
||||
log.Errorf("Could not read graffiti ordered index from disk: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
v.validator = &validator{
|
||||
db: v.db,
|
||||
validatorClient: ethpb.NewBeaconNodeValidatorClient(v.conn),
|
||||
@@ -196,6 +202,7 @@ func (v *ValidatorService) Start() {
|
||||
walletInitializedFeed: v.walletInitializedFeed,
|
||||
blockFeed: new(event.Feed),
|
||||
graffitiStruct: v.graffitiStruct,
|
||||
graffitiOrderedIndex: graffitiOrderedIndex,
|
||||
eipImportBlacklistedPublicKeys: slashablePublicKeys,
|
||||
logDutyCountDown: v.logDutyCountDown,
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ type validator struct {
|
||||
graffiti []byte
|
||||
voteStats voteStats
|
||||
graffitiStruct *graffiti.Graffiti
|
||||
graffitiOrderedIndex uint64
|
||||
eipImportBlacklistedPublicKeys map[[48]byte]bool
|
||||
}
|
||||
|
||||
|
||||
@@ -57,4 +57,8 @@ type ValidatorDB interface {
|
||||
AttestationHistoryForPubKey(
|
||||
ctx context.Context, pubKey [48]byte,
|
||||
) ([]*kv.AttestationRecord, error)
|
||||
|
||||
// Graffiti ordered index related methods
|
||||
SaveGraffitiOrderedIndex(ctx context.Context, index uint64) error
|
||||
GraffitiOrderedIndex(ctx context.Context, fileHash [32]byte) (uint64, error)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ go_library(
|
||||
"deprecated_attester_protection.go",
|
||||
"eip_blacklisted_keys.go",
|
||||
"genesis.go",
|
||||
"graffiti.go",
|
||||
"log.go",
|
||||
"migration.go",
|
||||
"migration_optimal_attester_protection.go",
|
||||
@@ -50,6 +51,7 @@ go_test(
|
||||
"deprecated_attester_protection_test.go",
|
||||
"eip_blacklisted_keys_test.go",
|
||||
"genesis_test.go",
|
||||
"graffiti_test.go",
|
||||
"migration_optimal_attester_protection_test.go",
|
||||
"migration_source_target_epochs_bucket_test.go",
|
||||
"proposer_protection_test.go",
|
||||
@@ -58,6 +60,7 @@ go_test(
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//shared/bytesutil:go_default_library",
|
||||
"//shared/hashutil:go_default_library",
|
||||
"//shared/params:go_default_library",
|
||||
"//shared/testutil/assert:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
|
||||
@@ -147,6 +147,7 @@ func NewKVStore(ctx context.Context, dirPath string, config *Config) (*Store, er
|
||||
slashablePublicKeysBucket,
|
||||
pubKeysBucket,
|
||||
migrationsBucket,
|
||||
graffitiBucket,
|
||||
)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
||||
39
validator/db/kv/graffiti.go
Normal file
39
validator/db/kv/graffiti.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// SaveGraffitiOrderedIndex writes the current graffiti index to the db
|
||||
func (s *Store) SaveGraffitiOrderedIndex(ctx context.Context, index uint64) error {
|
||||
return s.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(graffitiBucket)
|
||||
indexBytes := bytesutil.Uint64ToBytesBigEndian(index)
|
||||
return bkt.Put(graffitiOrderedIndexKey, indexBytes)
|
||||
})
|
||||
}
|
||||
|
||||
// GraffitiOrderedIndex fetches the ordered index, resetting if the file hash changed
|
||||
func (s *Store) GraffitiOrderedIndex(ctx context.Context, fileHash [32]byte) (uint64, error) {
|
||||
orderedIndex := uint64(0)
|
||||
err := s.db.Update(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(graffitiBucket)
|
||||
dbFileHash := bkt.Get(graffitiFileHashKey)
|
||||
if bytes.Equal(dbFileHash, fileHash[:]) {
|
||||
indexBytes := bkt.Get(graffitiOrderedIndexKey)
|
||||
orderedIndex = bytesutil.BytesToUint64BigEndian(indexBytes)
|
||||
} else {
|
||||
indexBytes := bytesutil.Uint64ToBytesBigEndian(0)
|
||||
if err := bkt.Put(graffitiOrderedIndexKey, indexBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
return bkt.Put(graffitiFileHashKey, fileHash[:])
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return orderedIndex, err
|
||||
}
|
||||
59
validator/db/kv/graffiti_test.go
Normal file
59
validator/db/kv/graffiti_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
)
|
||||
|
||||
func TestStore_GraffitiOrderedIndex_ReadAndWrite(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := setupDB(t, [][48]byte{})
|
||||
tests := []struct {
|
||||
name string
|
||||
want uint64
|
||||
write uint64
|
||||
fileHash [32]byte
|
||||
}{
|
||||
{
|
||||
name: "empty then write",
|
||||
want: 0,
|
||||
write: 15,
|
||||
fileHash: hashutil.Hash([]byte("one")),
|
||||
},
|
||||
{
|
||||
name: "update with same file hash",
|
||||
want: 15,
|
||||
write: 20,
|
||||
fileHash: hashutil.Hash([]byte("one")),
|
||||
},
|
||||
{
|
||||
name: "continued updates",
|
||||
want: 20,
|
||||
write: 21,
|
||||
fileHash: hashutil.Hash([]byte("one")),
|
||||
},
|
||||
{
|
||||
name: "reset with new file hash",
|
||||
want: 0,
|
||||
write: 10,
|
||||
fileHash: hashutil.Hash([]byte("two")),
|
||||
},
|
||||
{
|
||||
name: "read with new file hash",
|
||||
want: 10,
|
||||
fileHash: hashutil.Hash([]byte("two")),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := db.GraffitiOrderedIndex(ctx, tt.fileHash)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, tt.want, got)
|
||||
err = db.SaveGraffitiOrderedIndex(ctx, tt.write)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,4 +30,11 @@ var (
|
||||
|
||||
// Migrations
|
||||
migrationsBucket = []byte("migrations")
|
||||
|
||||
// Graffiti
|
||||
graffitiBucket = []byte("graffiti")
|
||||
|
||||
// Graffiti ordered index and hash keys
|
||||
graffitiOrderedIndexKey = []byte("graffiti-ordered-index")
|
||||
graffitiFileHashKey = []byte("graffiti-file-hash")
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ go_library(
|
||||
importpath = "github.com/prysmaticlabs/prysm/validator/graffiti",
|
||||
visibility = ["//validator:__subpackages__"],
|
||||
deps = [
|
||||
"//shared/hashutil:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
"@in_gopkg_yaml_v2//:go_default_library",
|
||||
],
|
||||
@@ -17,6 +18,7 @@ go_test(
|
||||
srcs = ["parse_graffiti_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//shared/hashutil:go_default_library",
|
||||
"//shared/testutil/require:go_default_library",
|
||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||
],
|
||||
|
||||
@@ -4,11 +4,14 @@ import (
|
||||
"io/ioutil"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type Graffiti struct {
|
||||
Hash [32]byte
|
||||
Default string `yaml:"default,omitempty"`
|
||||
Ordered []string `yaml:"ordered,omitempty"`
|
||||
Random []string `yaml:"random,omitempty"`
|
||||
Specific map[types.ValidatorIndex]string `yaml:"specific,omitempty"`
|
||||
}
|
||||
@@ -23,5 +26,6 @@ func ParseGraffitiFile(f string) (*Graffiti, error) {
|
||||
if err := yaml.Unmarshal(yamlFile, g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Hash = hashutil.Hash(yamlFile)
|
||||
return g, nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
types "github.com/prysmaticlabs/eth2-types"
|
||||
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||
)
|
||||
|
||||
@@ -23,13 +24,14 @@ func TestParseGraffitiFile_Default(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := &Graffiti{
|
||||
Hash: hashutil.Hash(input),
|
||||
Default: "Mr T was here",
|
||||
}
|
||||
require.DeepEqual(t, wanted, got)
|
||||
}
|
||||
|
||||
func TestParseGraffitiFile_Random(t *testing.T) {
|
||||
input := []byte(`random:
|
||||
input := []byte(`random:
|
||||
- "Mr A was here"
|
||||
- "Mr B was here"
|
||||
- "Mr C was here"`)
|
||||
@@ -44,6 +46,7 @@ func TestParseGraffitiFile_Random(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := &Graffiti{
|
||||
Hash: hashutil.Hash(input),
|
||||
Random: []string{
|
||||
"Mr A was here",
|
||||
"Mr B was here",
|
||||
@@ -53,9 +56,35 @@ func TestParseGraffitiFile_Random(t *testing.T) {
|
||||
require.DeepEqual(t, wanted, got)
|
||||
}
|
||||
|
||||
func TestParseGraffitiFile_Ordered(t *testing.T) {
|
||||
input := []byte(`ordered:
|
||||
- "Mr D was here"
|
||||
- "Mr E was here"
|
||||
- "Mr F was here"`)
|
||||
|
||||
dirName := t.TempDir() + "somedir"
|
||||
err := os.MkdirAll(dirName, os.ModePerm)
|
||||
require.NoError(t, err)
|
||||
someFileName := filepath.Join(dirName, "somefile.txt")
|
||||
require.NoError(t, ioutil.WriteFile(someFileName, input, os.ModePerm))
|
||||
|
||||
got, err := ParseGraffitiFile(someFileName)
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := &Graffiti{
|
||||
Hash: hashutil.Hash(input),
|
||||
Ordered: []string{
|
||||
"Mr D was here",
|
||||
"Mr E was here",
|
||||
"Mr F was here",
|
||||
},
|
||||
}
|
||||
require.DeepEqual(t, wanted, got)
|
||||
}
|
||||
|
||||
func TestParseGraffitiFile_Validators(t *testing.T) {
|
||||
input := []byte(`
|
||||
specific:
|
||||
specific:
|
||||
1234: Yolo
|
||||
555: "What's up"
|
||||
703727: Meow`)
|
||||
@@ -70,6 +99,7 @@ specific:
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := &Graffiti{
|
||||
Hash: hashutil.Hash(input),
|
||||
Specific: map[types.ValidatorIndex]string{
|
||||
1234: "Yolo",
|
||||
555: "What's up",
|
||||
@@ -82,12 +112,17 @@ specific:
|
||||
func TestParseGraffitiFile_AllFields(t *testing.T) {
|
||||
input := []byte(`default: "Mr T was here"
|
||||
|
||||
random:
|
||||
random:
|
||||
- "Mr A was here"
|
||||
- "Mr B was here"
|
||||
- "Mr C was here"
|
||||
|
||||
specific:
|
||||
ordered:
|
||||
- "Mr D was here"
|
||||
- "Mr E was here"
|
||||
- "Mr F was here"
|
||||
|
||||
specific:
|
||||
1234: Yolo
|
||||
555: "What's up"
|
||||
703727: Meow`)
|
||||
@@ -102,12 +137,18 @@ specific:
|
||||
require.NoError(t, err)
|
||||
|
||||
wanted := &Graffiti{
|
||||
Hash: hashutil.Hash(input),
|
||||
Default: "Mr T was here",
|
||||
Random: []string{
|
||||
"Mr A was here",
|
||||
"Mr B was here",
|
||||
"Mr C was here",
|
||||
},
|
||||
Ordered: []string{
|
||||
"Mr D was here",
|
||||
"Mr E was here",
|
||||
"Mr F was here",
|
||||
},
|
||||
Specific: map[types.ValidatorIndex]string{
|
||||
1234: "Yolo",
|
||||
555: "What's up",
|
||||
|
||||
Reference in New Issue
Block a user