mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 23:48:06 -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
|
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 {
|
if len(v.graffitiStruct.Random) != 0 {
|
||||||
r := rand.NewGenerator()
|
r := rand.NewGenerator()
|
||||||
r.Seed(time.Now().Unix())
|
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
|
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{
|
v.validator = &validator{
|
||||||
db: v.db,
|
db: v.db,
|
||||||
validatorClient: ethpb.NewBeaconNodeValidatorClient(v.conn),
|
validatorClient: ethpb.NewBeaconNodeValidatorClient(v.conn),
|
||||||
@@ -196,6 +202,7 @@ func (v *ValidatorService) Start() {
|
|||||||
walletInitializedFeed: v.walletInitializedFeed,
|
walletInitializedFeed: v.walletInitializedFeed,
|
||||||
blockFeed: new(event.Feed),
|
blockFeed: new(event.Feed),
|
||||||
graffitiStruct: v.graffitiStruct,
|
graffitiStruct: v.graffitiStruct,
|
||||||
|
graffitiOrderedIndex: graffitiOrderedIndex,
|
||||||
eipImportBlacklistedPublicKeys: slashablePublicKeys,
|
eipImportBlacklistedPublicKeys: slashablePublicKeys,
|
||||||
logDutyCountDown: v.logDutyCountDown,
|
logDutyCountDown: v.logDutyCountDown,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ type validator struct {
|
|||||||
graffiti []byte
|
graffiti []byte
|
||||||
voteStats voteStats
|
voteStats voteStats
|
||||||
graffitiStruct *graffiti.Graffiti
|
graffitiStruct *graffiti.Graffiti
|
||||||
|
graffitiOrderedIndex uint64
|
||||||
eipImportBlacklistedPublicKeys map[[48]byte]bool
|
eipImportBlacklistedPublicKeys map[[48]byte]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,4 +57,8 @@ type ValidatorDB interface {
|
|||||||
AttestationHistoryForPubKey(
|
AttestationHistoryForPubKey(
|
||||||
ctx context.Context, pubKey [48]byte,
|
ctx context.Context, pubKey [48]byte,
|
||||||
) ([]*kv.AttestationRecord, error)
|
) ([]*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",
|
"deprecated_attester_protection.go",
|
||||||
"eip_blacklisted_keys.go",
|
"eip_blacklisted_keys.go",
|
||||||
"genesis.go",
|
"genesis.go",
|
||||||
|
"graffiti.go",
|
||||||
"log.go",
|
"log.go",
|
||||||
"migration.go",
|
"migration.go",
|
||||||
"migration_optimal_attester_protection.go",
|
"migration_optimal_attester_protection.go",
|
||||||
@@ -50,6 +51,7 @@ go_test(
|
|||||||
"deprecated_attester_protection_test.go",
|
"deprecated_attester_protection_test.go",
|
||||||
"eip_blacklisted_keys_test.go",
|
"eip_blacklisted_keys_test.go",
|
||||||
"genesis_test.go",
|
"genesis_test.go",
|
||||||
|
"graffiti_test.go",
|
||||||
"migration_optimal_attester_protection_test.go",
|
"migration_optimal_attester_protection_test.go",
|
||||||
"migration_source_target_epochs_bucket_test.go",
|
"migration_source_target_epochs_bucket_test.go",
|
||||||
"proposer_protection_test.go",
|
"proposer_protection_test.go",
|
||||||
@@ -58,6 +60,7 @@ go_test(
|
|||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
"//shared/bytesutil:go_default_library",
|
"//shared/bytesutil:go_default_library",
|
||||||
|
"//shared/hashutil:go_default_library",
|
||||||
"//shared/params:go_default_library",
|
"//shared/params:go_default_library",
|
||||||
"//shared/testutil/assert:go_default_library",
|
"//shared/testutil/assert:go_default_library",
|
||||||
"//shared/testutil/require: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,
|
slashablePublicKeysBucket,
|
||||||
pubKeysBucket,
|
pubKeysBucket,
|
||||||
migrationsBucket,
|
migrationsBucket,
|
||||||
|
graffitiBucket,
|
||||||
)
|
)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
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
|
// Migrations
|
||||||
migrationsBucket = []byte("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",
|
importpath = "github.com/prysmaticlabs/prysm/validator/graffiti",
|
||||||
visibility = ["//validator:__subpackages__"],
|
visibility = ["//validator:__subpackages__"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//shared/hashutil:go_default_library",
|
||||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||||
"@in_gopkg_yaml_v2//:go_default_library",
|
"@in_gopkg_yaml_v2//:go_default_library",
|
||||||
],
|
],
|
||||||
@@ -17,6 +18,7 @@ go_test(
|
|||||||
srcs = ["parse_graffiti_test.go"],
|
srcs = ["parse_graffiti_test.go"],
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//shared/hashutil:go_default_library",
|
||||||
"//shared/testutil/require:go_default_library",
|
"//shared/testutil/require:go_default_library",
|
||||||
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
"@com_github_prysmaticlabs_eth2_types//:go_default_library",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
types "github.com/prysmaticlabs/eth2-types"
|
types "github.com/prysmaticlabs/eth2-types"
|
||||||
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Graffiti struct {
|
type Graffiti struct {
|
||||||
|
Hash [32]byte
|
||||||
Default string `yaml:"default,omitempty"`
|
Default string `yaml:"default,omitempty"`
|
||||||
|
Ordered []string `yaml:"ordered,omitempty"`
|
||||||
Random []string `yaml:"random,omitempty"`
|
Random []string `yaml:"random,omitempty"`
|
||||||
Specific map[types.ValidatorIndex]string `yaml:"specific,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 {
|
if err := yaml.Unmarshal(yamlFile, g); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
g.Hash = hashutil.Hash(yamlFile)
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
types "github.com/prysmaticlabs/eth2-types"
|
types "github.com/prysmaticlabs/eth2-types"
|
||||||
|
"github.com/prysmaticlabs/prysm/shared/hashutil"
|
||||||
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,13 +24,14 @@ func TestParseGraffitiFile_Default(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wanted := &Graffiti{
|
wanted := &Graffiti{
|
||||||
|
Hash: hashutil.Hash(input),
|
||||||
Default: "Mr T was here",
|
Default: "Mr T was here",
|
||||||
}
|
}
|
||||||
require.DeepEqual(t, wanted, got)
|
require.DeepEqual(t, wanted, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseGraffitiFile_Random(t *testing.T) {
|
func TestParseGraffitiFile_Random(t *testing.T) {
|
||||||
input := []byte(`random:
|
input := []byte(`random:
|
||||||
- "Mr A was here"
|
- "Mr A was here"
|
||||||
- "Mr B was here"
|
- "Mr B was here"
|
||||||
- "Mr C was here"`)
|
- "Mr C was here"`)
|
||||||
@@ -44,6 +46,7 @@ func TestParseGraffitiFile_Random(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wanted := &Graffiti{
|
wanted := &Graffiti{
|
||||||
|
Hash: hashutil.Hash(input),
|
||||||
Random: []string{
|
Random: []string{
|
||||||
"Mr A was here",
|
"Mr A was here",
|
||||||
"Mr B was here",
|
"Mr B was here",
|
||||||
@@ -53,9 +56,35 @@ func TestParseGraffitiFile_Random(t *testing.T) {
|
|||||||
require.DeepEqual(t, wanted, got)
|
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) {
|
func TestParseGraffitiFile_Validators(t *testing.T) {
|
||||||
input := []byte(`
|
input := []byte(`
|
||||||
specific:
|
specific:
|
||||||
1234: Yolo
|
1234: Yolo
|
||||||
555: "What's up"
|
555: "What's up"
|
||||||
703727: Meow`)
|
703727: Meow`)
|
||||||
@@ -70,6 +99,7 @@ specific:
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wanted := &Graffiti{
|
wanted := &Graffiti{
|
||||||
|
Hash: hashutil.Hash(input),
|
||||||
Specific: map[types.ValidatorIndex]string{
|
Specific: map[types.ValidatorIndex]string{
|
||||||
1234: "Yolo",
|
1234: "Yolo",
|
||||||
555: "What's up",
|
555: "What's up",
|
||||||
@@ -82,12 +112,17 @@ specific:
|
|||||||
func TestParseGraffitiFile_AllFields(t *testing.T) {
|
func TestParseGraffitiFile_AllFields(t *testing.T) {
|
||||||
input := []byte(`default: "Mr T was here"
|
input := []byte(`default: "Mr T was here"
|
||||||
|
|
||||||
random:
|
random:
|
||||||
- "Mr A was here"
|
- "Mr A was here"
|
||||||
- "Mr B was here"
|
- "Mr B was here"
|
||||||
- "Mr C was here"
|
- "Mr C was here"
|
||||||
|
|
||||||
specific:
|
ordered:
|
||||||
|
- "Mr D was here"
|
||||||
|
- "Mr E was here"
|
||||||
|
- "Mr F was here"
|
||||||
|
|
||||||
|
specific:
|
||||||
1234: Yolo
|
1234: Yolo
|
||||||
555: "What's up"
|
555: "What's up"
|
||||||
703727: Meow`)
|
703727: Meow`)
|
||||||
@@ -102,12 +137,18 @@ specific:
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
wanted := &Graffiti{
|
wanted := &Graffiti{
|
||||||
|
Hash: hashutil.Hash(input),
|
||||||
Default: "Mr T was here",
|
Default: "Mr T was here",
|
||||||
Random: []string{
|
Random: []string{
|
||||||
"Mr A was here",
|
"Mr A was here",
|
||||||
"Mr B was here",
|
"Mr B was here",
|
||||||
"Mr C 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{
|
Specific: map[types.ValidatorIndex]string{
|
||||||
1234: "Yolo",
|
1234: "Yolo",
|
||||||
555: "What's up",
|
555: "What's up",
|
||||||
|
|||||||
Reference in New Issue
Block a user