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
This commit is contained in:
Preston Van Loon 2019-09-09 14:31:19 -07:00 committed by Raul Jordan
parent af07c13730
commit 3708a8f476
12 changed files with 261 additions and 13 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
bazel-*
.git

View File

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

View File

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

24
interop.Dockerfile Normal file
View File

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

93
scripts/interop_start.sh Executable file
View File

@ -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=<identity>"
echo "--peer=<peer>"
echo "--num-validators=<number>"
echo "--gen-state=<file path>"
port "--port=<port number>"
}
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 &

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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