From 3708a8f476d787171a1e2f683813c3a60df06ae4 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 9 Sep 2019 14:31:19 -0700 Subject: [PATCH] Add tool and script for interop testing (#3417) * add tool and script for interop testing * identity * lint * merge upstream, fix conflict, update script, add comment * add comma separated support for --peer= * remove NUM_VALIDATORS, comma fix * WIP docker image * use CI builder * pr feedback * whatever antoine says * ignore git in docker * jobs=auto * disable remote cache * try to cache the golang part * try to cache the golang part * nvm * From Antoine with love * fix --- .dockerignore | 2 + beacon-chain/node/BUILD.bazel | 1 + beacon-chain/node/node.go | 3 +- interop.Dockerfile | 24 +++++++ scripts/interop_start.sh | 93 +++++++++++++++++++++++++ shared/sliceutil/slice.go | 13 ++++ shared/sliceutil/slice_test.go | 26 +++++++ tools/interop/convert-keys/BUILD.bazel | 18 +++++ tools/interop/convert-keys/main.go | 65 +++++++++++++++++ tools/unencrypted-keys-gen/BUILD.bazel | 4 +- tools/unencrypted-keys-gen/main.go | 21 +++--- tools/unencrypted-keys-gen/main_test.go | 4 +- 12 files changed, 261 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 interop.Dockerfile create mode 100755 scripts/interop_start.sh create mode 100644 tools/interop/convert-keys/BUILD.bazel create mode 100644 tools/interop/convert-keys/main.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..7d3fea638 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +bazel-* +.git diff --git a/beacon-chain/node/BUILD.bazel b/beacon-chain/node/BUILD.bazel index afbc98ea3..07c359576 100644 --- a/beacon-chain/node/BUILD.bazel +++ b/beacon-chain/node/BUILD.bazel @@ -26,6 +26,7 @@ go_library( "//shared/featureconfig:go_default_library", "//shared/params:go_default_library", "//shared/prometheus:go_default_library", + "//shared/sliceutil:go_default_library", "//shared/tracing:go_default_library", "//shared/version:go_default_library", "@com_github_ethereum_go_ethereum//common:go_default_library", diff --git a/beacon-chain/node/node.go b/beacon-chain/node/node.go index da8e1f607..0925751d4 100644 --- a/beacon-chain/node/node.go +++ b/beacon-chain/node/node.go @@ -34,6 +34,7 @@ import ( "github.com/prysmaticlabs/prysm/shared/featureconfig" "github.com/prysmaticlabs/prysm/shared/params" "github.com/prysmaticlabs/prysm/shared/prometheus" + "github.com/prysmaticlabs/prysm/shared/sliceutil" "github.com/prysmaticlabs/prysm/shared/tracing" "github.com/prysmaticlabs/prysm/shared/version" "github.com/sirupsen/logrus" @@ -213,7 +214,7 @@ func (b *BeaconNode) registerP2P(ctx *cli.Context) error { svc, err := p2p.NewService(&p2p.Config{ NoDiscovery: ctx.GlobalBool(cmd.NoDiscovery.Name), - StaticPeers: ctx.GlobalStringSlice(cmd.StaticPeers.Name), + StaticPeers: sliceutil.SplitCommaSeparated(ctx.GlobalStringSlice(cmd.StaticPeers.Name)), BootstrapNodeAddr: bootnodeENR, RelayNodeAddr: ctx.GlobalString(cmd.RelayNode.Name), HostAddress: ctx.GlobalString(cmd.P2PHost.Name), diff --git a/interop.Dockerfile b/interop.Dockerfile new file mode 100644 index 000000000..226321a96 --- /dev/null +++ b/interop.Dockerfile @@ -0,0 +1,24 @@ +FROM gcr.io/prysmaticlabs/build-agent AS builder + +WORKDIR /workspace + +COPY . /workspace/. + +# Build binaries for minimal configuration. +RUN bazel build --define ssz=minimal --jobs=auto --remote_cache= \ + //beacon-chain \ + //validator \ + //tools/interop/convert-keys + + +FROM gcr.io/whiteblock/base:ubuntu1804 + +COPY --from=builder /workspace/bazel-bin/beacon-chain/linux_amd64_stripped/beacon-chain . +COPY --from=builder /workspace/bazel-bin/validator/linux_amd64_pure_stripped/validator . +COPY --from=builder /workspace/bazel-bin/tools/interop/convert-keys/linux_amd64_stripped/convert-keys . + +RUN mkdir /launch + +COPY scripts/interop_start.sh /launch/start.sh + +ENTRYPOINT ["start.sh"] diff --git a/scripts/interop_start.sh b/scripts/interop_start.sh new file mode 100755 index 000000000..2270a28e3 --- /dev/null +++ b/scripts/interop_start.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +""" +2019/09/08 -- Interop start script. +This script is intended for dockerfile deployment for interop testing. +This script is fragile and subject to break as flags change. +Use at your own risk! + + +Use with interop.Dockerfile from the workspace root: + +docker build -f interop.Dockerfile . +""" + +# Flags +IDENTITY="" # P2P private key +PEERS="" # Comma separated list of peers +GEN_STATE="" # filepath to ssz encoded state. +PORT="8000" # port to serve p2p traffic +YAML_KEY_FILE="" # Path to yaml keyfile as defined here: https://github.com/ethereum/eth2.0-pm/tree/master/interop/mocked_start + +# Constants +BEACON_LOG_FILE="/tmp/beacon.log" +VALIDATOR_LOG_FILE="/tmp/validator.log" + +usage() { + echo "--identity=" + echo "--peer=" + echo "--num-validators=" + echo "--gen-state=" + port "--port=" +} + +while [ "$1" != "" ]; +do + PARAM=`echo $1 | awk -F= '{print $1}'` + VALUE=`echo $1 | sed 's/^[^=]*=//g'` + + case $PARAM in + --identity) + IDENTITY=$VALUE + ;; + --peers) + PEERS+=",$VALUE" + ;; + --validator-keys) + YAML_KEY_FILE=$VALUE + ;; + --gen-state) + GEN_STATE=$VALUE + ;; + --port) + PORT=$VALUE + ;; + --help) + usage + exit + ;; + *) + echo "ERROR: unknown parameter \"$PARAM\"" + usage + exit 1 + ;; + esac + shift +done + + +echo "Converting hex yaml keys to a format that Prysm understands" + +# Expect YAML keys in hex encoded format. Convert this into the format the the validator already understands. +./convert-keys $YAML_KEY_FILE /tmp/keys.json + +echo "Starting beacon chain and logging to $BEACON_LOG_FILE" + +BEACON_FLAGS="--bootstrap-node= \ + --deposit-contract=0xD775140349E6A5D12524C6ccc3d6A1d4519D4029 \ + --p2p-port=$PORT \ + --peer=$PEERS \ + --interop-genesis-state=$GEN_STATE \ + --p2p-priv-key=$IDENTITY \ + --log-file=$BEACON_LOG_FILE" + +./beacon-chain $BEACON_FLAGS & + +echo "Starting validator client and logging to $VALIDATOR_LOG_FILE" + +VALIDATOR_FLAGS="--monitoring-port=9091 \ + --unencrypted-keys /tmp/keys.json \ + --log-file=$VALIDATOR_LOG_FILE + +./validator- $VALIDATOR_FLAGS & + diff --git a/shared/sliceutil/slice.go b/shared/sliceutil/slice.go index 1fae2fa85..e9d00a706 100644 --- a/shared/sliceutil/slice.go +++ b/shared/sliceutil/slice.go @@ -1,5 +1,9 @@ package sliceutil +import ( + "strings" +) + // SubsetUint64 returns true if the first array is // completely contained in the second array with time // complexity of approximately o(n). @@ -259,3 +263,12 @@ func IntersectionByteSlices(s ...[][]byte) [][]byte { } return inter } + +// SplitCommaSeparated values from the list. Example: []string{"a,b", "c,d"} becomes []string{"a", "b", "c", "d"}. +func SplitCommaSeparated(arr []string) []string { + var result []string + for _, val := range arr { + result = append(result, strings.Split(val, ",")...) + } + return result +} \ No newline at end of file diff --git a/shared/sliceutil/slice_test.go b/shared/sliceutil/slice_test.go index 4d760515d..17f8f0b85 100644 --- a/shared/sliceutil/slice_test.go +++ b/shared/sliceutil/slice_test.go @@ -352,3 +352,29 @@ func TestIntersectionByteSlices(t *testing.T) { } } } + +func TestSplitCommaSeparated(t *testing.T) { + tests := []struct { + input []string + output []string + }{ + { + input: []string{"a,b", "c,d"}, + output: []string{"a", "b", "c", "d"}, + }, + { + input: []string{"a", "b,c,d"}, + output: []string{"a", "b", "c", "d"}, + }, + { + input: []string{"a", "b", "c"}, + output: []string{"a", "b", "c"}, + }, + } + + for _, tt := range tests { + if result := SplitCommaSeparated(tt.input); !reflect.DeepEqual(result, tt.output) { + t.Errorf("SplitCommaSeparated(%v) = %v; wanted %v", tt.input, result, tt.output) + } + } +} diff --git a/tools/interop/convert-keys/BUILD.bazel b/tools/interop/convert-keys/BUILD.bazel new file mode 100644 index 000000000..14543e54d --- /dev/null +++ b/tools/interop/convert-keys/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/prysmaticlabs/prysm/tools/interop/convert-keys", + visibility = ["//visibility:public"], + deps = [ + "//tools/unencrypted-keys-gen:go_default_library", + "@in_gopkg_yaml_v2//:go_default_library", + ], +) + +go_binary( + name = "convert-keys", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/tools/interop/convert-keys/main.go b/tools/interop/convert-keys/main.go new file mode 100644 index 000000000..dec4bb9cf --- /dev/null +++ b/tools/interop/convert-keys/main.go @@ -0,0 +1,65 @@ +// Used for converting keys.yaml files from eth2.0-pm for interop testing. +// See: https://github.com/ethereum/eth2.0-pm/tree/master/interop/mocked_start +// +// This code can be discarded after interop testing. +package main + +import ( + "encoding/hex" + "fmt" + "io/ioutil" + "log" + "os" + + "gopkg.in/yaml.v2" + keygen "github.com/prysmaticlabs/prysm/tools/unencrypted-keys-gen" +) + +// KeyPair with hex encoded data. +type KeyPair struct { + Priv string `yaml:"privkey"` + Pub string `yaml:"pubkey"` +} + +// KeyPairs represent the data format in the upstream yaml. +type KeyPairs []KeyPair + +func main() { + if len(os.Args) < 3 { + fmt.Println("Usage: convert-keys path/to/keys.yaml path/to/output.json") + return + } + inFile := os.Args[1] + + in, err := ioutil.ReadFile(inFile) + if err != nil { + log.Fatalf("Failed to read file %s: %v", inFile, err) + } + data := make(KeyPairs, 0) + if err := yaml.Unmarshal(in, &data); err != nil { + log.Fatalf("Failed to unmarshal yaml: %v", err) + } + + out := &keygen.UnencryptedKeysContainer{} + for _, key := range data { + pk, err := hex.DecodeString(key.Priv[2:]) + if err != nil { + log.Fatalf("Failed to decode hex string %s: %v", key.Priv, err) + } + + out.Keys = append(out.Keys, &keygen.UnencryptedKeys{ + ValidatorKey: pk, + WithdrawalKey: pk, + }) + } + + outFile, err := os.Create(os.Args[2]) + if err != nil { + log.Fatalf("Failed to create file at %s: %v", os.Args[2], err) + } + defer outFile.Close() + if err := keygen.SaveUnencryptedKeysToFile(outFile, out); err != nil { + log.Fatalf("Failed to save %v", err) + } + log.Printf("Wrote %s\n", os.Args[2]) +} diff --git a/tools/unencrypted-keys-gen/BUILD.bazel b/tools/unencrypted-keys-gen/BUILD.bazel index 5ba3dc646..11d215639 100644 --- a/tools/unencrypted-keys-gen/BUILD.bazel +++ b/tools/unencrypted-keys-gen/BUILD.bazel @@ -4,7 +4,9 @@ go_library( name = "go_default_library", srcs = ["main.go"], importpath = "github.com/prysmaticlabs/prysm/tools/unencrypted-keys-gen", - visibility = ["//visibility:private"], + visibility = [ + "//tools/interop/convert-keys:__pkg__", + ], deps = [ "//shared/bls:go_default_library", ], diff --git a/tools/unencrypted-keys-gen/main.go b/tools/unencrypted-keys-gen/main.go index 91059ce87..69429f87c 100644 --- a/tools/unencrypted-keys-gen/main.go +++ b/tools/unencrypted-keys-gen/main.go @@ -18,11 +18,13 @@ var ( overwrite = flag.Bool("overwrite", false, "If the key file exists, it will be overwritten") ) -type unencryptedKeysContainer struct { - Keys []*unencryptedKeys `json:"keys"` +// UnencryptedKeysContainer defines the structure of the unecrypted key JSON file. +type UnencryptedKeysContainer struct { + Keys []*UnencryptedKeys `json:"keys"` } -type unencryptedKeys struct { +// UnencryptedKeys is the inner struct of the JSON file. +type UnencryptedKeys struct { ValidatorKey []byte `json:"validator_key"` WithdrawalKey []byte `json:"withdrawal_key"` } @@ -53,14 +55,14 @@ func main() { }() ctnr := generateUnencryptedKeys(rand.Reader) - if err := saveUnencryptedKeysToFile(file, ctnr); err != nil { + if err := SaveUnencryptedKeysToFile(file, ctnr); err != nil { log.Fatal(err) } } -func generateUnencryptedKeys(r io.Reader) *unencryptedKeysContainer { - ctnr := &unencryptedKeysContainer{ - Keys: make([]*unencryptedKeys, *numKeys), +func generateUnencryptedKeys(r io.Reader) *UnencryptedKeysContainer { + ctnr := &UnencryptedKeysContainer{ + Keys: make([]*UnencryptedKeys, *numKeys), } for i := 0; i < *numKeys; i++ { signingKey, err := bls.RandKey(r) @@ -71,7 +73,7 @@ func generateUnencryptedKeys(r io.Reader) *unencryptedKeysContainer { if err != nil { log.Fatal(err) } - ctnr.Keys[i] = &unencryptedKeys{ + ctnr.Keys[i] = &UnencryptedKeys{ ValidatorKey: signingKey.Marshal(), WithdrawalKey: withdrawalKey.Marshal(), } @@ -79,7 +81,8 @@ func generateUnencryptedKeys(r io.Reader) *unencryptedKeysContainer { return ctnr } -func saveUnencryptedKeysToFile(w io.Writer, ctnr *unencryptedKeysContainer) error { +// SaveUnencryptedKeysToFile JSON encodes the container and writes to the writer. +func SaveUnencryptedKeysToFile(w io.Writer, ctnr *UnencryptedKeysContainer) error { enc, err := json.Marshal(ctnr) if err != nil { log.Fatal(err) diff --git a/tools/unencrypted-keys-gen/main_test.go b/tools/unencrypted-keys-gen/main_test.go index 1128cd1e7..7c900518d 100644 --- a/tools/unencrypted-keys-gen/main_test.go +++ b/tools/unencrypted-keys-gen/main_test.go @@ -11,11 +11,11 @@ import ( func TestSavesUnencryptedKeys(t *testing.T) { ctnr := generateUnencryptedKeys(rand.Reader) buf := new(bytes.Buffer) - if err := saveUnencryptedKeysToFile(buf, ctnr); err != nil { + if err := SaveUnencryptedKeysToFile(buf, ctnr); err != nil { t.Fatal(err) } enc := buf.Bytes() - dec := &unencryptedKeysContainer{} + dec := &UnencryptedKeysContainer{} if err := json.Unmarshal(enc, dec); err != nil { t.Fatal(err) }