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:
Josh Yudaken
2021-02-24 17:50:47 -05:00
committed by GitHub
parent d44c27ec63
commit f0eb843138
13 changed files with 211 additions and 5 deletions

View File

@@ -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())

View File

@@ -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(), &ethpb.ValidatorIndexRequest{PublicKey: pubKey[:]}).
Times(5).
Return(&ethpb.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)
}
}

View File

@@ -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,
}

View File

@@ -91,6 +91,7 @@ type validator struct {
graffiti []byte
voteStats voteStats
graffitiStruct *graffiti.Graffiti
graffitiOrderedIndex uint64
eipImportBlacklistedPublicKeys map[[48]byte]bool
}

View File

@@ -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)
}

View File

@@ -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",

View File

@@ -147,6 +147,7 @@ func NewKVStore(ctx context.Context, dirPath string, config *Config) (*Store, er
slashablePublicKeysBucket,
pubKeysBucket,
migrationsBucket,
graffitiBucket,
)
}); err != nil {
return nil, err

View 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
}

View 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)
})
}
}

View File

@@ -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")
)

View File

@@ -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",
],

View File

@@ -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
}

View File

@@ -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",