diff --git a/DEVELOP.md b/DEVELOP.md index 97b1a62c..5f1e8e99 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -19,6 +19,20 @@ This should work on Linux, MacOS and possibly even Windows. After installing all dependencies, just run `tilt up --update-mode=exec`. Whenever you modify a file, the devnet is automatically rebuilt and a rolling update is done. -Watch pod status in your cluster: `kubectl get pod -A -w`. +Specify number of guardians nodes to run (default is five): + + tilt up --update-mode=exec -- --num=10 + +Watch pod status in your cluster: + + kubectl get pod -A -w + +Get logs for single guardian node: + + kubectl logs guardian-0 + +Generate test ETH lockups once the cluster is up: + + kubectl exec -it -c tests eth-devnet-0 -- npx truffle exec src/send-lockups.js Once you're done, press Ctrl-C. Run `tilt down` to tear down the devnet. diff --git a/Tiltfile b/Tiltfile index 5281a1e4..6ecf07a8 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,3 +1,7 @@ +config.define_string("num", False, "Number of guardian nodes to run") +cfg = config.parse() +num_guardians = int(cfg.get("num", "5")) + # protos local_resource( @@ -14,7 +18,20 @@ docker_build( dockerfile = "bridge/Dockerfile", ) -k8s_yaml("devnet/bridge.yaml") +def build_bridge_yaml(): + bridge_yaml = read_yaml_stream("devnet/bridge.yaml") + + for obj in bridge_yaml: + if obj['kind'] == 'StatefulSet' and obj['metadata']['name'] == 'guardian': + obj['spec']['replicas'] = num_guardians + container = obj['spec']['template']['spec']['containers'][0] + if container['name'] != 'guardiand': + fail("container 0 is not guardiand") + container['command'] += ['-devNumGuardians', str(num_guardians)] + + return encode_yaml_stream(bridge_yaml) + +k8s_yaml(build_bridge_yaml()) k8s_resource("guardian", resource_deps=["proto-gen"]) @@ -60,7 +77,6 @@ docker_build( live_update = [ sync("./ethereum/src", "/home/node/app/src"), ], - ) k8s_yaml("devnet/eth-devnet.yaml") diff --git a/bridge/cmd/guardiand/ethlockups.go b/bridge/cmd/guardiand/ethlockups.go deleted file mode 100644 index cd4de1e1..00000000 --- a/bridge/cmd/guardiand/ethlockups.go +++ /dev/null @@ -1,30 +0,0 @@ -package main - -import ( - "context" - "crypto/ecdsa" - "encoding/hex" - - "go.uber.org/zap" - - "github.com/certusone/wormhole/bridge/pkg/common" - "github.com/certusone/wormhole/bridge/pkg/supervisor" -) - -func ethLockupProcessor(ec chan *common.ChainLock, gk *ecdsa.PrivateKey) func(ctx context.Context) error { - return func(ctx context.Context) error { - for { - select { - case <-ctx.Done(): - return ctx.Err() - case k := <-ec: - supervisor.Logger(ctx).Info("lockup confirmed", - zap.String("source", hex.EncodeToString(k.SourceAddress[:])), - zap.String("target", hex.EncodeToString(k.TargetAddress[:])), - zap.String("amount", k.Amount.String()), - zap.String("hash", hex.EncodeToString(k.Hash())), - ) - } - } - } -} diff --git a/bridge/cmd/guardiand/ethwatch.go b/bridge/cmd/guardiand/ethwatch.go new file mode 100644 index 00000000..1f616a8f --- /dev/null +++ b/bridge/cmd/guardiand/ethwatch.go @@ -0,0 +1,171 @@ +package main + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "encoding/hex" + "fmt" + "time" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" + + "github.com/certusone/wormhole/bridge/pkg/common" + "github.com/certusone/wormhole/bridge/pkg/devnet" + gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1" + "github.com/certusone/wormhole/bridge/pkg/supervisor" +) + +// aggregationState represents a single node's aggregation of guardian signatures. +type ( + lockupState struct { + firstObserved time.Time + signatures map[ethcommon.Address][]byte + } + + lockupMap map[string]*lockupState + + aggregationState struct { + lockupSignatures lockupMap + } +) + +func ethLockupProcessor(lockC chan *common.ChainLock, setC chan *common.GuardianSet, gk *ecdsa.PrivateKey, sendC chan []byte, obsvC chan *gossipv1.EthLockupObservation) func(ctx context.Context) error { + return func(ctx context.Context) error { + logger := supervisor.Logger(ctx) + our_addr := crypto.PubkeyToAddress(gk.PublicKey) + state := &aggregationState{lockupMap{}} + + // Get initial validator set + logger.Info("waiting for initial validator set to be fetched from Ethereum") + gs := <-setC + logger.Info("current guardian set received", + zap.Strings("set", gs.KeysAsHexStrings()), + zap.Uint32("index", gs.Index)) + + if *unsafeDevMode { + idx, err := devnet.GetDevnetIndex() + if err != nil { + return err + } + + if idx == 0 && (uint(len(gs.Keys)) != *devNumGuardians) { + vaa := devnet.DevnetGuardianSetVSS(*devNumGuardians) + + logger.Info(fmt.Sprintf("guardian set has %d members, expecting %d - submitting VAA", + len(gs.Keys), *devNumGuardians), + zap.Any("vaa", vaa)) + + timeout, _ := context.WithTimeout(ctx, 15*time.Second) + tx, err := devnet.SubmitVAA(timeout, *ethRPC, vaa) + if err != nil { + logger.Error("failed to submit devnet guardian set change", zap.Error(err)) + } + + logger.Info("devnet guardian set change submitted", zap.Any("tx", tx)) + } + } + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case gs = <-setC: + logger.Info("guardian set updated", + zap.Strings("set", gs.KeysAsHexStrings()), + zap.Uint32("index", gs.Index)) + case k := <-lockC: + supervisor.Logger(ctx).Info("lockup confirmed", + zap.String("source", hex.EncodeToString(k.SourceAddress[:])), + zap.String("target", hex.EncodeToString(k.TargetAddress[:])), + zap.String("amount", k.Amount.String()), + zap.String("txhash", k.TxHash.String()), + zap.String("lockhash", hex.EncodeToString(k.Hash())), + ) + + us, ok := gs.KeyIndex(our_addr) + if !ok { + logger.Error("we're not in the guardian set - refusing to sign", + zap.Uint32("index", gs.Index), + zap.String("our_addr", our_addr.Hex()), + zap.Any("set", gs.KeysAsHexStrings())) + break + } + + s, err := gk.Sign(rand.Reader, k.Hash(), nil) + if err != nil { + panic(err) + } + + logger.Info("observed and signed confirmed lockup on Ethereum", + zap.String("txhash", k.TxHash.String()), + zap.String("lockhash", hex.EncodeToString(k.Hash())), + zap.String("signature", hex.EncodeToString(s)), + zap.Int("us", us)) + + obsv := gossipv1.EthLockupObservation{ + Addr: crypto.PubkeyToAddress(gk.PublicKey).Bytes(), + Hash: k.Hash(), + Signature: s, + } + + w := gossipv1.GossipMessage{Message: &gossipv1.GossipMessage_EthLockupObservation{EthLockupObservation: &obsv}} + + msg, err := proto.Marshal(&w) + if err != nil { + panic(err) + } + + sendC <- msg + case m := <-obsvC: + logger.Info("received another guardian's eth lockup observation", + zap.String("hash", hex.EncodeToString(m.Hash)), + zap.Binary("signature", m.Signature), + zap.Binary("addr", m.Addr)) + + their_addr := ethcommon.BytesToAddress(m.Addr) + _, ok := gs.KeyIndex(their_addr) + if !ok { + logger.Warn("received eth observation by unknown guardian - is our guardian set outdated?", + zap.String("their_addr", their_addr.Hex()), + zap.Any("current_set", gs.KeysAsHexStrings()), + ) + break + } + + // TODO: timeout/garbage collection for lockup state + + // []byte isn't hashable in a map. Paying a small extra cost to for encoding for easier debugging. + hash := hex.EncodeToString(m.Hash) + + if state.lockupSignatures[hash] == nil { + state.lockupSignatures[hash] = &lockupState{ + firstObserved: time.Now(), + signatures: map[ethcommon.Address][]byte{}, + } + } + + state.lockupSignatures[hash].signatures[their_addr] = m.Signature + + // Enumerate guardian set and check for signatures + agg := make([]bool, len(gs.Keys)) + for i, a := range gs.Keys { + // TODO: verify signature + _, ok := state.lockupSignatures[hash].signatures[a] + agg[i] = ok + } + + logger.Info("aggregation state for eth lockup", + zap.String("hash", hash), + zap.Any("set", gs.KeysAsHexStrings()), + zap.Uint32("index", gs.Index), + zap.Bools("aggregation", agg)) + + // TODO: submit to Solana + } + } + } +} diff --git a/bridge/cmd/guardiand/guardiankey.go b/bridge/cmd/guardiand/guardiankey.go deleted file mode 100644 index 36e54566..00000000 --- a/bridge/cmd/guardiand/guardiankey.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "encoding/binary" - "fmt" - "os" - "strconv" - "strings" -) - -// getDevnetIndex returns the current host's devnet index (i.e. 0 for guardian-0). -func getDevnetIndex() (int, error) { - hostname, err := os.Hostname() - if err != nil { - panic(err) - } - - h := strings.Split(hostname, "-") - - if h[0] != "guardian" { - return 0, fmt.Errorf("hostname %s does not appear to be a devnet host", hostname) - } - - i, err := strconv.Atoi(h[1]) - if err != nil { - return 0, fmt.Errorf("invalid devnet index %s in hostname %s", h[1], hostname) - } - - return i, nil -} - -// deterministicKeyByIndex generates a deterministic address from a given index. -func deterministicKeyByIndex(c elliptic.Curve, idx uint64) (*ecdsa.PrivateKey) { - buf := make([]byte, 200) - binary.LittleEndian.PutUint64(buf, idx) - - worstRNG := bytes.NewBuffer(buf) - - key, err := ecdsa.GenerateKey(c, bytes.NewReader(worstRNG.Bytes())) - if err != nil { - panic(err) - } - - return key -} - diff --git a/bridge/cmd/guardiand/main.go b/bridge/cmd/guardiand/main.go index a0e5558f..5b315720 100644 --- a/bridge/cmd/guardiand/main.go +++ b/bridge/cmd/guardiand/main.go @@ -5,14 +5,19 @@ import ( "crypto/ecdsa" "flag" "fmt" + "net/http" + _ "net/http/pprof" "os" eth_common "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/libp2p/go-libp2p-core/peer" "go.uber.org/zap" "github.com/certusone/wormhole/bridge/pkg/common" + "github.com/certusone/wormhole/bridge/pkg/devnet" "github.com/certusone/wormhole/bridge/pkg/ethereum" + gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1" "github.com/certusone/wormhole/bridge/pkg/supervisor" ipfslog "github.com/ipfs/go-log/v2" @@ -29,11 +34,12 @@ var ( ethContract = flag.String("ethContract", "", "Ethereum bridge contract address") ethConfirmations = flag.Uint64("ethConfirmations", 15, "Ethereum confirmation count requirement") - logLevel = flag.String("loglevel", "info", "Logging level (debug, info, warn, error, dpanic, panic, fatal)") + logLevel = flag.String("logLevel", "info", "Logging level (debug, info, warn, error, dpanic, panic, fatal)") unsafeDevMode = flag.Bool("unsafeDevMode", false, "Launch node in unsafe, deterministic devnet mode") + devNumGuardians = flag.Uint("devNumGuardians", 5, "Number of devnet guardians to include in guardian set") - nodeName = flag.String("nodeName", "", "Node name to announce in gossip heartbeats (default: hostname)") + nodeName = flag.String("nodeName", "", "Node name to announce in gossip heartbeats") ) var ( @@ -41,6 +47,22 @@ var ( rootCtxCancel context.CancelFunc ) +// TODO: prometheus metrics +// TODO: telemetry? + +// "Why would anyone do this?" are famous last words. +// +// We already forcibly override RPC URLs and keys in dev mode to prevent security +// risks from operator error, but an extra warning won't hurt. +const devwarning = ` + +++++++++++++++++++++++++++++++++++++++++++++++++++ + | NODE IS RUNNING IN INSECURE DEVELOPMENT MODE | + | | + | Do not use -unsafeDevMode in prod. | + +++++++++++++++++++++++++++++++++++++++++++++++++++ + +` + func rootLoggerName() string { if *unsafeDevMode { // FIXME: add hostname to root logger for cleaner console output in multi-node development. @@ -61,13 +83,13 @@ func loadGuardianKey(logger *zap.Logger) *ecdsa.PrivateKey { if *unsafeDevMode { // Figure out our devnet index - idx, err := getDevnetIndex() + idx, err := devnet.GetDevnetIndex() if err != nil { logger.Fatal("Failed to parse hostname - are we running in devnet?") } // Generate guardian key - gk = deterministicKeyByIndex(crypto.S256(), uint64(idx)) + gk = devnet.DeterministicEcdsaKeyByIndex(crypto.S256(), uint64(idx)) } else { panic("not implemented") // TODO } @@ -81,6 +103,10 @@ func loadGuardianKey(logger *zap.Logger) *ecdsa.PrivateKey { func main() { flag.Parse() + if *unsafeDevMode { + fmt.Print(devwarning) + } + // Set up logging. The go-log zap wrapper that libp2p uses is compatible with our // usage of zap in supervisor, which is nice. lvl, err := ipfslog.LevelFromString(*logLevel) @@ -95,19 +121,26 @@ func main() { // Override the default go-log config, which uses a magic environment variable. ipfslog.SetAllLoggers(lvl) - // Mute chatty subsystems. - if err := ipfslog.SetLogLevel("swarm2", "error"); err != nil { - panic(err) - } // connection errors + // In devnet mode, we automatically set a number of flags that rely on deterministic keys. + if *unsafeDevMode { + go func() { + logger.Info("debug server listening on [::]:6060") + logger.Error("debug server crashed", zap.Error(http.ListenAndServe("[::]:6060", nil))) + }() - // Verify flags - if *nodeKeyPath == "" { - logger.Fatal("Please specify -nodeKey") - } - if *ethRPC == "" { - logger.Fatal("Please specify -ethRPC") - } - if *nodeName == "" { + g0key, err := peer.IDFromPrivateKey(devnet.DeterministicP2PPrivKeyByIndex(0)) + if err != nil { + panic(err) + } + + // Use the first guardian node as bootstrap + *p2pBootstrap = fmt.Sprintf("/dns4/guardian-0.guardian/udp/%d/quic/p2p/%s", *p2pPort, g0key.String()) + + // Deterministic ganache ETH devnet address. + *ethContract = devnet.BridgeContractAddress.Hex() + + // Use the hostname as nodeName. For production, we don't want to do this to + // prevent accidentally leaking sensitive hostnames. hostname, err := os.Hostname() if err != nil { panic(err) @@ -115,6 +148,21 @@ func main() { *nodeName = hostname } + // Verify flags + + if *nodeKeyPath == "" && !*unsafeDevMode { // In devnet mode, keys are deterministically generated. + logger.Fatal("Please specify -nodeKey") + } + if *ethRPC == "" { + logger.Fatal("Please specify -ethRPC") + } + if *ethContract == "" { + logger.Fatal("Please specify -ethContract") + } + if *nodeName == "" { + logger.Fatal("Please specify -nodeName") + } + ethContractAddr := eth_common.HexToAddress(*ethContract) // Guardian key @@ -125,20 +173,31 @@ func main() { defer rootCtxCancel() // Ethereum lock event channel - ec := make(chan *common.ChainLock) + lockC := make(chan *common.ChainLock) + + // Ethereum incoming guardian set updates + setC := make(chan *common.GuardianSet) + + // Outbound gossip message queue + sendC := make(chan []byte) + + // Inbound ETH observations + ethObsvC := make(chan *gossipv1.EthLockupObservation) // Run supervisor. supervisor.New(rootCtx, logger, func(ctx context.Context) error { - if err := supervisor.Run(ctx, "p2p", p2p); err != nil { + // TODO: use a dependency injection framework like wire? + + if err := supervisor.Run(ctx, "p2p", p2p(ethObsvC, sendC)); err != nil { return err } if err := supervisor.Run(ctx, "eth", - ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, ec).Run); err != nil { + ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, lockC, setC).Run); err != nil { return err } - if err := supervisor.Run(ctx, "lockups", ethLockupProcessor(ec, gk)); err != nil { + if err := supervisor.Run(ctx, "ethwatch", ethLockupProcessor(lockC, setC, gk, sendC, ethObsvC)); err != nil { return err } @@ -157,4 +216,3 @@ func main() { // TODO: wait for things to shut down gracefully } } - diff --git a/bridge/cmd/guardiand/nodekey.go b/bridge/cmd/guardiand/nodekey.go index a7e71e83..9d45e275 100644 --- a/bridge/cmd/guardiand/nodekey.go +++ b/bridge/cmd/guardiand/nodekey.go @@ -1,7 +1,6 @@ package main import ( - "encoding/base64" "fmt" "io/ioutil" "os" @@ -47,28 +46,3 @@ func getOrCreateNodeKey(logger *zap.Logger, path string) (crypto.PrivKey, error) return priv, nil } - -// deterministicNodeKey returns a non-nil value if we have a deterministic key on file for the current host. -func deterministicNodeKey() crypto.PrivKey { - idx, err := getDevnetIndex() - if err != nil { - panic(err) - } - - if idx == 0 { - // node ID: 12D3KooWQ1sV2kowPY1iJX1hJcVTysZjKv3sfULTGwhdpUGGZ1VF - b, err := base64.StdEncoding.DecodeString("CAESQGlv6OJOMXrZZVTCC0cgCv7goXr6QaSVMZIndOIXKNh80vYnG+EutVlZK20Nx9cLkUG5ymKB\n88LXi/vPBwP8zfY=") - if err != nil { - panic(err) - } - - priv, err := crypto.UnmarshalPrivateKey(b) - if err != nil { - panic(err) - } - - return priv - } - - return nil -} diff --git a/bridge/cmd/guardiand/p2p.go b/bridge/cmd/guardiand/p2p.go index ab900665..8bc9ef45 100644 --- a/bridge/cmd/guardiand/p2p.go +++ b/bridge/cmd/guardiand/p2p.go @@ -16,189 +16,237 @@ import ( dht "github.com/libp2p/go-libp2p-kad-dht" pubsub "github.com/libp2p/go-libp2p-pubsub" libp2pquic "github.com/libp2p/go-libp2p-quic-transport" - swarm "github.com/libp2p/go-libp2p-swarm" libp2ptls "github.com/libp2p/go-libp2p-tls" "github.com/multiformats/go-multiaddr" "go.uber.org/zap" "google.golang.org/protobuf/proto" + "github.com/certusone/wormhole/bridge/pkg/devnet" gossipv1 "github.com/certusone/wormhole/bridge/pkg/proto/gossip/v1" "github.com/certusone/wormhole/bridge/pkg/supervisor" ) -func p2p(ctx context.Context) (re error) { - logger := supervisor.Logger(ctx) - - var priv crypto.PrivKey - var err error - - if *unsafeDevMode { - priv = deterministicNodeKey() +func p2p(ethObsvC chan *gossipv1.EthLockupObservation, sendC chan []byte) func(ctx context.Context) error { + return func(ctx context.Context) (re error) { + logger := supervisor.Logger(ctx) + var priv crypto.PrivKey var err error - if priv == nil { + + if *unsafeDevMode { + idx, err2 := devnet.GetDevnetIndex() + if err2 != nil { + logger.Fatal("Failed to parse hostname - are we running in devnet?") + } + priv = devnet.DeterministicP2PPrivKeyByIndex(int64(idx)) + } else { priv, err = getOrCreateNodeKey(logger, *nodeKeyPath) if err != nil { return fmt.Errorf("failed to load node key: %w", err) } - } else { - logger.Info("devnet: loaded hardcoded node key") } - } else { - priv, err = getOrCreateNodeKey(logger, *nodeKeyPath) + + var idht *dht.IpfsDHT + + h, err := libp2p.New(ctx, + // Use the keypair we generated + libp2p.Identity(priv), + + // Multiple listen addresses + libp2p.ListenAddrStrings( + // Listen on QUIC only. + // TODO(leo): is this more or less stable than using both TCP and QUIC transports? + // https://github.com/libp2p/go-libp2p/issues/688 + fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *p2pPort), + fmt.Sprintf("/ip6/::/udp/%d/quic", *p2pPort), + ), + + // Enable TLS security as the only security protocol. + libp2p.Security(libp2ptls.ID, libp2ptls.New), + + // Enable QUIC transport as the only transport. + libp2p.Transport(libp2pquic.NewTransport), + + // Let's prevent our peer from having too many + // connections by attaching a connection manager. + libp2p.ConnectionManager(connmgr.NewConnManager( + 100, // Lowwater + 400, // HighWater, + time.Minute, // GracePeriod + )), + + // Let this host use the DHT to find other hosts + libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { + // TODO(leo): Persistent data store (i.e. address book) + idht, err = dht.New(ctx, h, dht.Mode(dht.ModeServer), + // TODO(leo): This intentionally makes us incompatible with the global IPFS DHT + dht.ProtocolPrefix(protocol.ID("/"+*p2pNetworkID)), + ) + return idht, err + }), + ) + if err != nil { - return fmt.Errorf("failed to load node key: %w", err) - } - } - - var idht *dht.IpfsDHT - - h, err := libp2p.New(ctx, - // Use the keypair we generated - libp2p.Identity(priv), - // Multiple listen addresses - libp2p.ListenAddrStrings( - // Listen on QUIC only. - // TODO(leo): listen on ipv6 - // TODO(leo): is this more or less stable than using both TCP and QUIC transports? - // https://github.com/libp2p/go-libp2p/issues/688 - fmt.Sprintf("/ip4/0.0.0.0/udp/%d/quic", *p2pPort), - ), - - // Enable TLS security only. - libp2p.Security(libp2ptls.ID, libp2ptls.New), - - // Enable QUIC transports. - libp2p.Transport(libp2pquic.NewTransport), - - // Enable TCP so we can connect to bootstrap nodes. - // (can be disabled if we bootstrap our own network) - libp2p.DefaultTransports, - - // Let's prevent our peer from having too many - // connections by attaching a connection manager. - libp2p.ConnectionManager(connmgr.NewConnManager( - 100, // Lowwater - 400, // HighWater, - time.Minute, // GracePeriod - )), - - // Let this host use the DHT to find other hosts - libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { - // TODO(leo): Persistent data store (i.e. address book) - idht, err = dht.New(ctx, h, dht.Mode(dht.ModeServer), - // TODO(leo): This intentionally makes us incompatible with the global IPFS DHT - dht.ProtocolPrefix(protocol.ID("/"+*p2pNetworkID)), - ) - return idht, err - }), - ) - - if err != nil { - panic(err) - } - - defer func() { - // TODO: libp2p cannot be cleanly restarted (https://github.com/libp2p/go-libp2p/issues/992) - logger.Error("p2p routine has exited, cancelling root context...", zap.Error(re)) - rootCtxCancel() - }() - - logger.Info("Connecting to bootstrap peers") - - // Add our own bootstrap nodes - - // Count number of successful connection attempts. If we fail to connect to every bootstrap peer, kill - // the service and have supervisor retry it. - successes := 0 - - for _, addr := range strings.Split(*p2pBootstrap, ",") { - if addr == "" { - continue - } - ma, err := multiaddr.NewMultiaddr(addr) - if err != nil { - logger.Error("Invalid bootstrap address", zap.String("peer", addr), zap.Error(err)) - continue - } - pi, err := peer.AddrInfoFromP2pAddr(ma) - if err != nil { - logger.Error("Invalid bootstrap address", zap.String("peer", addr), zap.Error(err)) - continue + panic(err) } - if err = h.Connect(ctx, *pi); err != nil { - if err != swarm.ErrDialToSelf { + defer func() { + // TODO: libp2p cannot be cleanly restarted (https://github.com/libp2p/go-libp2p/issues/992) + logger.Error("p2p routine has exited, cancelling root context...", zap.Error(re)) + rootCtxCancel() + }() + + logger.Info("Connecting to bootstrap peers", zap.String("bootstrap_peers", *p2pBootstrap)) + + // Add our own bootstrap nodes + + // Count number of successful connection attempts. If we fail to connect to every bootstrap peer, kill + // the service and have supervisor retry it. + successes := 0 + // Are we a bootstrap node? If so, it's okay to not have any peers. + bootstrap_node := false + + for _, addr := range strings.Split(*p2pBootstrap, ",") { + if addr == "" { + continue + } + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + logger.Error("Invalid bootstrap address", zap.String("peer", addr), zap.Error(err)) + continue + } + pi, err := peer.AddrInfoFromP2pAddr(ma) + if err != nil { + logger.Error("Invalid bootstrap address", zap.String("peer", addr), zap.Error(err)) + continue + } + + if pi.ID == h.ID() { + logger.Info("We're a bootstrap node") + bootstrap_node = true + continue + } + + if err = h.Connect(ctx, *pi); err != nil { logger.Error("Failed to connect to bootstrap peer", zap.String("peer", addr), zap.Error(err)) } else { - // Dialing self, carrying on... (we're a bootstrap peer) - logger.Info("Tried to connect to ourselves - we're a bootstrap peer") successes += 1 } - } else { - successes += 1 } - } - if successes == 0 { - h.Close() - return fmt.Errorf("Failed to connect to any bootstrap peer") - } else { - logger.Info("Connected to bootstrap peers", zap.Int("num", successes)) - } + // TODO: continually reconnect to bootstrap nodes? + if successes == 0 && !bootstrap_node { + return fmt.Errorf("Failed to connect to any bootstrap peer") + } else { + logger.Info("Connected to bootstrap peers", zap.Int("num", successes)) + } - topic := fmt.Sprintf("%s/%s", *p2pNetworkID, "broadcast") + topic := fmt.Sprintf("%s/%s", *p2pNetworkID, "broadcast") - logger.Info("Subscribing pubsub topic", zap.String("topic", topic)) - ps, err := pubsub.NewGossipSub(ctx, h) - if err != nil { - panic(err) - } + logger.Info("Subscribing pubsub topic", zap.String("topic", topic)) + ps, err := pubsub.NewGossipSub(ctx, h) + if err != nil { + panic(err) + } - th, err := ps.Join(topic) - if err != nil { - return fmt.Errorf("failed to join topic: %w", err) - } + th, err := ps.Join(topic) + if err != nil { + return fmt.Errorf("failed to join topic: %w", err) + } - sub, err := th.Subscribe() - if err != nil { - return fmt.Errorf("failed to subscribe topic: %w", err) - } + sub, err := th.Subscribe() + if err != nil { + return fmt.Errorf("failed to subscribe topic: %w", err) + } - logger.Info("Node has been started", zap.String("peer_id", h.ID().String()), - zap.String("addrs", fmt.Sprintf("%v", h.Addrs()))) + logger.Info("Node has been started", zap.String("peer_id", h.ID().String()), + zap.String("addrs", fmt.Sprintf("%v", h.Addrs()))) - go func() { - ctr := int64(0) + go func() { + ctr := int64(0) + tick := time.NewTicker(15 * time.Second) + defer tick.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-tick.C: + msg := gossipv1.GossipMessage{Message: &gossipv1.GossipMessage_Heartbeat{ + Heartbeat: &gossipv1.Heartbeat{ + NodeName: *nodeName, + Counter: ctr, + Timestamp: time.Now().UnixNano(), + }}} + + b, err := proto.Marshal(&msg) + if err != nil { + panic(err) + } + + err = th.Publish(ctx, b) + if err != nil { + logger.Warn("failed to publish heartbeat message", zap.Error(err)) + } + } + } + }() + + go func() { + for { + select { + case <-ctx.Done(): + return + case msg := <-sendC: + err := th.Publish(ctx, msg) + if err != nil { + logger.Error("failed to publish message from queue", zap.Error(err)) + } + } + } + }() + + supervisor.Signal(ctx, supervisor.SignalHealthy) for { - msg := gossipv1.Heartbeat{ - NodeName: *nodeName, - Counter: ctr, - } - - b, err := proto.Marshal(&msg) + envl, err := sub.Next(ctx) if err != nil { - panic(err) + return fmt.Errorf("failed to receive pubsub message: %w", err) } - err = th.Publish(ctx, b) + var msg gossipv1.GossipMessage + err = proto.Unmarshal(envl.Data, &msg) if err != nil { - logger.Warn("failed to publish message", zap.Error(err)) + logger.Info("received invalid message", + zap.String("data", string(envl.Data)), + zap.String("from", envl.GetFrom().String())) + continue } - time.Sleep(15 * time.Second) + if envl.GetFrom() == h.ID() { + logger.Debug("received message from ourselves, ignoring", + zap.Any("payload", msg.Message)) + continue + } + + logger.Debug("received message", + zap.Any("payload", msg.Message), + zap.Binary("raw", envl.Data), + zap.String("from", envl.GetFrom().String())) + + switch m := msg.Message.(type) { + case *gossipv1.GossipMessage_Heartbeat: + logger.Info("heartbeat received", + zap.Any("value", m.Heartbeat), + zap.String("from", envl.GetFrom().String())) + case *gossipv1.GossipMessage_EthLockupObservation: + ethObsvC <- m.EthLockupObservation + default: + logger.Warn("received unknown message type (running outdated software?)", + zap.Any("payload", msg.Message), + zap.Binary("raw", envl.Data), + zap.String("from", envl.GetFrom().String())) + } } - }() - - supervisor.Signal(ctx, supervisor.SignalHealthy) - - for { - msg, err := sub.Next(ctx) - if err != nil { - return fmt.Errorf("failed to receive pubsub message: %w", err) - } - - logger.Debug("received message", zap.String("data", string(msg.Data)), zap.String("from", msg.GetFrom().String())) } } diff --git a/bridge/cmd/vaa-test/main.go b/bridge/cmd/vaa-test/main.go index 2e139444..dfad12ec 100644 --- a/bridge/cmd/vaa-test/main.go +++ b/bridge/cmd/vaa-test/main.go @@ -3,17 +3,19 @@ package main import ( "crypto/ecdsa" "encoding/hex" - "github.com/certusone/wormhole/bridge/pkg/vaa" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "math/big" "math/rand" "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/certusone/wormhole/bridge/pkg/devnet" + "github.com/certusone/wormhole/bridge/pkg/vaa" ) - func main() { - addr := common.HexToAddress("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1") + addr := devnet.GanacheClientDefaultAccountAddress addrP := common.LeftPadBytes(addr[:], 32) addrTarget := vaa.Address{} copy(addrTarget[:], addrP) @@ -83,11 +85,11 @@ func main() { // }, //} - AddSignature(v,key,0) - AddSignature(v,key2,1) - AddSignature(v,key3,2) - AddSignature(v,key5,4) - AddSignature(v,key6,5) + v.AddSignature(key, 0) + v.AddSignature(key2, 1) + v.AddSignature(key3, 2) + v.AddSignature(key5, 4) + v.AddSignature(key6, 5) sigAddr := crypto.PubkeyToAddress(key.PublicKey) println(sigAddr.String()) println(crypto.PubkeyToAddress(key2.PublicKey).String()) @@ -96,28 +98,10 @@ func main() { println(crypto.PubkeyToAddress(key5.PublicKey).String()) println(crypto.PubkeyToAddress(key6.PublicKey).String()) - vData, err := v.Serialize() + vData, err := v.Marshal() if err != nil { panic(err) } println(hex.EncodeToString(vData)) } - -func AddSignature(v *vaa.VAA, key *ecdsa.PrivateKey,index uint8){ - data, err := v.SigningMsg() - if err != nil { - panic(err) - } - sig, err := crypto.Sign(data.Bytes(), key) - if err != nil { - panic(err) - } - sigData := [65]byte{} - copy(sigData[:], sig) - - v.Signatures = append(v.Signatures, &vaa.Signature{ - Index: index, - Signature: sigData, - }) -} diff --git a/bridge/go.mod b/bridge/go.mod index c98d22c3..b6c18ef9 100644 --- a/bridge/go.mod +++ b/bridge/go.mod @@ -3,13 +3,11 @@ module github.com/certusone/wormhole/bridge go 1.14 require ( - github.com/aristanetworks/goarista v0.0.0-20190204200901-2166578f3448 // indirect github.com/cenkalti/backoff/v4 v4.0.2 github.com/cespare/cp v1.1.1 // indirect github.com/deckarep/golang-set v1.7.1 // indirect github.com/ethereum/go-ethereum v1.9.18 github.com/go-kit/kit v0.9.0 // indirect - github.com/go-logfmt/logfmt v0.4.0 // indirect github.com/golang/mock v1.4.3 // indirect github.com/golang/protobuf v1.4.2 github.com/ipfs/go-log/v2 v2.1.1 @@ -19,17 +17,14 @@ require ( github.com/libp2p/go-libp2p-kad-dht v0.8.3 github.com/libp2p/go-libp2p-pubsub v0.3.3 github.com/libp2p/go-libp2p-quic-transport v0.7.1 - github.com/libp2p/go-libp2p-swarm v0.2.8 github.com/libp2p/go-libp2p-tls v0.1.3 github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.12 // indirect + github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20200123000308-a60dcd172b4c github.com/multiformats/go-multiaddr v0.2.2 github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/onsi/gomega v1.10.1 // indirect - github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 // indirect github.com/prometheus/tsdb v0.7.1 // indirect - github.com/rjeczalik/notify v0.9.2 // indirect - github.com/rs/cors v1.6.0 // indirect github.com/stretchr/testify v1.6.1 go.uber.org/zap v1.15.0 golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 // indirect diff --git a/bridge/go.sum b/bridge/go.sum index 7174f158..1d5774e0 100644 --- a/bridge/go.sum +++ b/bridge/go.sum @@ -24,8 +24,11 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.23.1/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw= @@ -35,16 +38,23 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/allegro/bigcache v1.2.1 h1:hg1sY1raCwic3Vnsvje6TT7/pnZba83LeFck5NrFKSc= +github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/aristanetworks/fsnotify v1.4.2/go.mod h1:D/rtu7LpjYM8tRJphJ0hUBYpjai8SfX+aSNsWDTq/Ks= +github.com/aristanetworks/glog v0.0.0-20180419172825-c15b03b3054f/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= -github.com/aristanetworks/goarista v0.0.0-20190204200901-2166578f3448 h1:c7dHl/Dp2sznWCZm0FCiQEJEoxEbTAZV7Ccdojs7Bwo= -github.com/aristanetworks/goarista v0.0.0-20190204200901-2166578f3448/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/aristanetworks/goarista v0.0.0-20190912214011-b54698eaaca6 h1:6bZNnQcA2fkzH9AhZXbp2nDqbWa4bBqFeUb70Zq1HBM= +github.com/aristanetworks/goarista v0.0.0-20190912214011-b54698eaaca6/go.mod h1:Z4RTxGAuYhPzcq8+EdRM+R8M48Ssle2TsWtwRKa+vns= +github.com/aristanetworks/splunk-hec-go v0.3.3/go.mod h1:1VHO9r17b0K7WmOlLb9nTk/2YanvOEnLMUgsFrxBROc= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/benbjohnson/clock v1.0.2/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.0.3 h1:vkLuvpK4fmtSCuo60+yC63p7y0BmQ8gm5ZXGuBCJyXg= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= @@ -54,6 +64,7 @@ github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQj github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= @@ -107,11 +118,18 @@ github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elastic/gosigar v0.10.5/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ethereum/go-ethereum v1.9.5/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= github.com/ethereum/go-ethereum v1.9.18 h1:+vzvufVD7+OfQa07IJP20Z7AGZsJaw0M6JIA/WQcqy8= github.com/ethereum/go-ethereum v1.9.18/go.mod h1:JSSTypSMTkGZtAdAChH2wP5dZEvPGh3nUTuDpH+hNrg= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -122,6 +140,7 @@ github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJn github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -178,12 +197,14 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.18 h1:lum7VRA9kdlvBi7/v2p7/zcbkduHaCH/SVVyurs7OpY= github.com/google/gopacket v1.1.18/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= @@ -202,8 +223,10 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -215,6 +238,7 @@ github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7 github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/influxdata/influxdb1-client v0.0.0-20190809212627-fc22c7df067e/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= @@ -272,21 +296,27 @@ github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsj github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= +github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= +github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= @@ -520,6 +550,8 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20200123000308-a60dcd172b4c h1:cbhK2JT4nl7k8frmCN98ttRdSGP75x9mDxDhlQ1kHQQ= +github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20200123000308-a60dcd172b4c/go.mod h1:Z4zI+CdJB1fyrZ1jfevFH6flNV9izrLZnQAeuD6Wkjk= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= @@ -530,7 +562,9 @@ github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKU github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= @@ -586,6 +620,7 @@ github.com/multiformats/go-varint v0.0.2/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXS github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= @@ -600,55 +635,70 @@ github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FW github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/openconfig/gnmi v0.0.0-20190823184014-89b2bf29312c/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc= +github.com/openconfig/reference v0.0.0-20190727015836-8dfd928c9696/go.mod h1:ym2A+zigScwkSEb/cVQB0/ZMpU3rqiH6X7WRRsxgOGw= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= -github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 h1:zNBQb37RGLmJybyMcs983HfUfpkw9OTFD9tbBfAViHE= -github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8= github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= @@ -707,10 +757,15 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v0.0.0-20180621010148-0d5a0ceb10cf/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= +github.com/templexxx/xor v0.0.0-20181023030647-4e92f724b73b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= +github.com/tjfoc/gmsm v1.0.1/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc= +github.com/tyler-smith/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -731,7 +786,11 @@ github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1: github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/xtaci/kcp-go v5.4.5+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= +github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -764,11 +823,13 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -791,6 +852,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190227160552-c95aed5357e7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -798,7 +860,10 @@ golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -823,18 +888,23 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -866,6 +936,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190912185636-87d9f09c5d89/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -900,6 +971,7 @@ google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= @@ -912,17 +984,26 @@ google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyz google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/karalabe/cookiejar.v2 v2.0.0-20150724131613-8dcd6a7f4951/go.mod h1:owOxCRGGeAx1uugABik6K9oeNu1cgxP/R9ItzLDxNWA= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6 h1:a6cXbcDDUkSBlpnkWV1bJ+vv3mOgQEltEJ2rPxroVu0= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/redis.v4 v4.2.4/go.mod h1:8KREHdypkCEojGKQcjMqAODMICIVwZAONWq8RowTITA= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= diff --git a/bridge/pkg/common/guardianset.go b/bridge/pkg/common/guardianset.go new file mode 100644 index 00000000..270086e8 --- /dev/null +++ b/bridge/pkg/common/guardianset.go @@ -0,0 +1,34 @@ +package common + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type GuardianSet struct { + // Guardian's public keys truncated by the ETH standard hashing mechanism (20 bytes). + Keys []common.Address + // On-chain set index + Index uint32 +} + +func (g *GuardianSet) KeysAsHexStrings() []string { + r := make([]string, len(g.Keys)) + + for n, k := range g.Keys { + r[n] = k.Hex() + } + + return r +} + +// Get a given address index from the guardian set. Returns (-1, false) +// if the address wasn't found and (addr, true) otherwise. +func (g *GuardianSet) KeyIndex(addr common.Address) (int, bool) { + for n, k := range g.Keys { + if k == addr { + return n, true + } + } + + return -1, false +} diff --git a/bridge/pkg/devnet/constants.go b/bridge/pkg/devnet/constants.go new file mode 100644 index 00000000..cef43f12 --- /dev/null +++ b/bridge/pkg/devnet/constants.go @@ -0,0 +1,44 @@ +// package devnet contains constants and helper functions for the local deterministic devnet. +package devnet + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + + "github.com/miguelmota/go-ethereum-hdwallet" +) + +var ( + // Address of the first account, which is used as the default client account. + GanacheClientDefaultAccountAddress = common.HexToAddress("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1") + + // Contracts (deployed by "truffle migrate" on a deterministic devnet) + WrappedAssetContractAddress = common.HexToAddress("0x79183957Be84C0F4dA451E534d5bA5BA3FB9c696") + BridgeContractAddress = common.HexToAddress("0xCfEB869F69431e42cdB54A4F4f105C19C080A601") +) + +const ( + // Ganache's hardcoded HD Wallet derivation path + ganacheWalletMnemonic = "myth like bonus scare over problem client lizard pioneer submit female collect" + ganacheDerivationPath = "m/44'/60'/0'/0/%d" +) + +func DeriveAccount(accountIndex uint) accounts.Account { + path := hdwallet.MustParseDerivationPath(fmt.Sprintf(ganacheDerivationPath, accountIndex)) + account, err := Wallet().Derive(path, false) + if err != nil { + panic(err) + } + + return account +} + +func Wallet() *hdwallet.Wallet { + wallet, err := hdwallet.NewFromMnemonic(ganacheWalletMnemonic) + if err != nil { + panic(err) + } + return wallet +} diff --git a/bridge/pkg/devnet/deterministic_key.go b/bridge/pkg/devnet/deterministic_key.go new file mode 100644 index 00000000..4c42cf7f --- /dev/null +++ b/bridge/pkg/devnet/deterministic_key.go @@ -0,0 +1,37 @@ +package devnet + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "encoding/binary" + mathrand "math/rand" + + "github.com/libp2p/go-libp2p-core/crypto" +) + +// DeterministicEcdsaKeyByIndex generates a deterministic ecdsa.PrivateKey from a given index. +func DeterministicEcdsaKeyByIndex(c elliptic.Curve, idx uint64) *ecdsa.PrivateKey { + buf := make([]byte, 200) + binary.LittleEndian.PutUint64(buf, idx) + + worstRNG := bytes.NewBuffer(buf) + + key, err := ecdsa.GenerateKey(c, bytes.NewReader(worstRNG.Bytes())) + if err != nil { + panic(err) + } + + return key +} + +// DeterministicP2PPrivKeyByIndex generates a deterministic libp2p crypto.PrivateKey from a given index. +func DeterministicP2PPrivKeyByIndex(idx int64) crypto.PrivKey { + r := mathrand.New(mathrand.NewSource(int64(idx))) + priv, _, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, -1, r) + if err != nil { + panic(err) + } + + return priv +} diff --git a/bridge/pkg/devnet/guardianset_vaa.go b/bridge/pkg/devnet/guardianset_vaa.go new file mode 100644 index 00000000..ea105a90 --- /dev/null +++ b/bridge/pkg/devnet/guardianset_vaa.go @@ -0,0 +1,75 @@ +package devnet + +import ( + "context" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + + "github.com/certusone/wormhole/bridge/pkg/ethereum/abi" + "github.com/certusone/wormhole/bridge/pkg/vaa" +) + +// DevnetGuardianSetVSS returns a VAA signed by guardian-0 that adds all n validators. +func DevnetGuardianSetVSS(n uint) *vaa.VAA { + pubkeys := make([]common.Address, n) + + for n := range pubkeys { + key := DeterministicEcdsaKeyByIndex(crypto.S256(), uint64(n)) + pubkeys[n] = crypto.PubkeyToAddress(key.PublicKey) + } + + v := &vaa.VAA{ + Version: 1, + GuardianSetIndex: 0, + Timestamp: time.Unix(5000, 0), + Payload: &vaa.BodyGuardianSetUpdate{ + Keys: pubkeys, + NewIndex: 1, + }, + } + + // The devnet is initialized with a single guardian (ethereum/migrations/1_initial_migration.js). + key0 := DeterministicEcdsaKeyByIndex(crypto.S256(), 0) + v.AddSignature(key0, 0) + + return v +} + +// SubmitVAA submits a VAA to the devnet chain using well-known accounts and contract addresses. +func SubmitVAA(ctx context.Context, rpcURL string, vaa *vaa.VAA) (*types.Transaction, error) { + c, err := ethclient.DialContext(ctx, rpcURL) + if err != nil { + return nil, fmt.Errorf("dialing eth client failed: %w", err) + } + + key, err := Wallet().PrivateKey(DeriveAccount(0)) + if err != nil { + panic(err) + } + + opts := bind.NewKeyedTransactor(key) + opts.Context = ctx + + bridge, err := abi.NewAbi(BridgeContractAddress, c) + if err != nil { + panic(err) + } + + b, err := vaa.Marshal() + if err != nil { + panic(err) + } + + tx, err := bridge.SubmitVAA(opts, b) + if err != nil { + return nil, err + } + + return tx, nil +} diff --git a/bridge/pkg/devnet/hostname.go b/bridge/pkg/devnet/hostname.go new file mode 100644 index 00000000..a077ff09 --- /dev/null +++ b/bridge/pkg/devnet/hostname.go @@ -0,0 +1,29 @@ +package devnet + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// GetDevnetIndex returns the current host's devnet index (i.e. 0 for guardian-0). +func GetDevnetIndex() (int, error) { + hostname, err := os.Hostname() + if err != nil { + panic(err) + } + + h := strings.Split(hostname, "-") + + if h[0] != "guardian" { + return 0, fmt.Errorf("hostname %s does not appear to be a devnet host", hostname) + } + + i, err := strconv.Atoi(h[1]) + if err != nil { + return 0, fmt.Errorf("invalid devnet index %s in hostname %s", h[1], hostname) + } + + return i, nil +} diff --git a/bridge/pkg/ethereum/watcher.go b/bridge/pkg/ethereum/watcher.go index a1bf640d..9c0a25fc 100644 --- a/bridge/pkg/ethereum/watcher.go +++ b/bridge/pkg/ethereum/watcher.go @@ -27,38 +27,55 @@ type ( pendingLocks map[eth_common.Hash]*pendingLock pendingLocksGuard sync.Mutex - evChan chan *common.ChainLock + lockChan chan *common.ChainLock + setChan chan *common.GuardianSet } pendingLock struct { - lock *common.ChainLock + lock *common.ChainLock height uint64 } ) -func NewEthBridgeWatcher(url string, bridge eth_common.Address, minConfirmations uint64, events chan *common.ChainLock) *EthBridgeWatcher { - return &EthBridgeWatcher{url: url, bridge: bridge, minConfirmations: minConfirmations, evChan: events, pendingLocks: map[eth_common.Hash]*pendingLock{}} +func NewEthBridgeWatcher(url string, bridge eth_common.Address, minConfirmations uint64, lockEvents chan *common.ChainLock, setEvents chan *common.GuardianSet) *EthBridgeWatcher { + return &EthBridgeWatcher{url: url, bridge: bridge, minConfirmations: minConfirmations, lockChan: lockEvents, setChan: setEvents, pendingLocks: map[eth_common.Hash]*pendingLock{}} } func (e *EthBridgeWatcher) Run(ctx context.Context) error { - c, err := ethclient.DialContext(ctx, e.url) + timeout, _ := context.WithTimeout(ctx, 15 * time.Second) + c, err := ethclient.DialContext(timeout, e.url) if err != nil { return fmt.Errorf("dialing eth client failed: %w", err) } - f, err := abi.NewWormholeBridgeFilterer(e.bridge, c) + f, err := abi.NewAbiFilterer(e.bridge, c) if err != nil { return fmt.Errorf("could not create wormhole bridge filter: %w", err) } - sink := make(chan *abi.WormholeBridgeLogTokensLocked, 2) - eventSubscription, err := f.WatchLogTokensLocked(&bind.WatchOpts{ - Context: ctx, - }, sink, nil, nil) + caller, err := abi.NewAbiCaller(e.bridge, c) if err != nil { - return fmt.Errorf("failed to subscribe to eth events: %w", err) + panic(err) } - defer eventSubscription.Unsubscribe() + + // Timeout for initializing subscriptions + timeout, _ = context.WithTimeout(ctx, 15 * time.Second) + + // Subscribe to new token lockups + tokensLockedC := make(chan *abi.AbiLogTokensLocked, 2) + tokensLockedSub, err := f.WatchLogTokensLocked(&bind.WatchOpts{Context: timeout}, tokensLockedC, nil, nil) + if err != nil { + return fmt.Errorf("failed to subscribe to token lockup events: %w", err) + } + defer tokensLockedSub.Unsubscribe() + + // Subscribe to guardian set changes + guardianSetC := make(chan *abi.AbiLogGuardianSetChanged, 2) + guardianSetEvent, err := f.WatchLogGuardianSetChanged(&bind.WatchOpts{Context: timeout}, guardianSetC) + if err != nil { + return fmt.Errorf("failed to subscribe to guardian set events: %w", err) + } + defer tokensLockedSub.Unsubscribe() errC := make(chan error) logger := supervisor.Logger(ctx) @@ -68,10 +85,13 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error { select { case <-ctx.Done(): return - case e := <-eventSubscription.Err(): - errC <- e + case e := <-tokensLockedSub.Err(): + errC <- fmt.Errorf("error while processing token lockup subscription: %w", e) return - case ev := <-sink: + case e := <-guardianSetEvent.Err(): + errC <- fmt.Errorf("error while processing guardian set subscription: %w", e) + return + case ev := <-tokensLockedC: lock := &common.ChainLock{ TxHash: ev.Raw.TxHash, SourceAddress: ev.Sender, @@ -91,6 +111,21 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error { height: ev.Raw.BlockNumber, } e.pendingLocksGuard.Unlock() + case ev := <-guardianSetC: + logger.Info("guardian set has changed, fetching new value", + zap.Uint32("new_index", ev.NewGuardianIndex)) + + gs, err := caller.GetGuardianSet(&bind.CallOpts{Context: timeout}, ev.NewGuardianIndex) + if err != nil { + errC <- fmt.Errorf("error requesting new guardian set value: %w", err) + return + } + + logger.Info("new guardian set fetched", zap.Any("value", gs), zap.Uint32("index", ev.NewGuardianIndex)) + e.setChan <- &common.GuardianSet{ + Keys: gs.Keys, + Index: ev.NewGuardianIndex, + } } } }() @@ -109,7 +144,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error { case <-ctx.Done(): return case e := <-headerSubscription.Err(): - errC <- e + errC <- fmt.Errorf("error while processing header subscription: %w", e) return case ev := <-headSink: start := time.Now() @@ -132,7 +167,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error { logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash), zap.Stringer("number", ev.Number)) delete(e.pendingLocks, hash) - e.evChan <- pLock.lock + e.lockChan <- pLock.lock } } @@ -145,6 +180,26 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error { supervisor.Signal(ctx, supervisor.SignalHealthy) + // Fetch current guardian set + timeout, _ = context.WithTimeout(ctx, 15 * time.Second) + opts := &bind.CallOpts{Context: timeout} + + currentIndex, err := caller.GuardianSetIndex(opts) + if err != nil { + return fmt.Errorf("error requesting current guardian set index: %w", err) + } + + gs, err := caller.GetGuardianSet(opts, currentIndex) + if err != nil { + return fmt.Errorf("error requesting current guardian set value: %w", err) + } + + logger.Info("current guardian set fetched", zap.Any("value", gs), zap.Uint32("index", currentIndex)) + e.setChan <- &common.GuardianSet{ + Keys: gs.Keys, + Index: currentIndex, + } + select { case <-ctx.Done(): return ctx.Err() diff --git a/bridge/pkg/vaa/structs.go b/bridge/pkg/vaa/structs.go index 881534b3..9bd13e58 100644 --- a/bridge/pkg/vaa/structs.go +++ b/bridge/pkg/vaa/structs.go @@ -2,14 +2,16 @@ package vaa import ( "bytes" + "crypto/ecdsa" "encoding/binary" "fmt" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "io" "math" "math/big" "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) type ( @@ -96,8 +98,8 @@ const ( supportedVAAVersion = 0x01 ) -// ParseVAA deserializes the binary representation of a VAA -func ParseVAA(data []byte) (*VAA, error) { +// Unmarshal deserializes the binary representation of a VAA +func Unmarshal(data []byte) (*VAA, error) { if len(data) < minVAALength { return nil, fmt.Errorf("VAA is too short") } @@ -215,8 +217,8 @@ func (v *VAA) VerifySignatures(addresses []common.Address) bool { return true } -// Serialize returns the binary representation of the VAA -func (v *VAA) Serialize() ([]byte, error) { +// Marshal returns the binary representation of the VAA +func (v *VAA) Marshal() ([]byte, error) { buf := new(bytes.Buffer) MustWrite(buf, binary.BigEndian, v.Version) MustWrite(buf, binary.BigEndian, v.GuardianSetIndex) @@ -257,6 +259,24 @@ func (v *VAA) serializeBody() ([]byte, error) { return buf.Bytes(), nil } +func (v *VAA) AddSignature(key *ecdsa.PrivateKey, index uint8) { + data, err := v.SigningMsg() + if err != nil { + panic(err) + } + sig, err := crypto.Sign(data.Bytes(), key) + if err != nil { + panic(err) + } + sigData := [65]byte{} + copy(sigData[:], sig) + + v.Signatures = append(v.Signatures, &Signature{ + Index: index, + Signature: sigData, + }) +} + func parseBodyTransfer(r io.Reader) (*BodyTransfer, error) { b := &BodyTransfer{} diff --git a/devnet/bridge.yaml b/devnet/bridge.yaml index 917ca947..f5cc64a7 100644 --- a/devnet/bridge.yaml +++ b/devnet/bridge.yaml @@ -39,24 +39,23 @@ spec: image: guardiand-image command: - /guardiand - - -nodeKey - - /data/node.key - - -bootstrap - - /dns4/guardian-0.guardian/udp/8999/quic/p2p/12D3KooWQ1sV2kowPY1iJX1hJcVTysZjKv3sfULTGwhdpUGGZ1VF - -ethRPC - ws://eth-devnet:8545 - - -ethContract - - 0xCfEB869F69431e42cdB54A4F4f105C19C080A601 - -ethConfirmations - - '2' + - '1' - -unsafeDevMode +# - -logLevel +# - debug ports: - containerPort: 8999 name: p2p protocol: UDP - volumeMounts: - - name: guardian-data - mountPath: /data + - containerPort: 6060 + name: pprof + protocol: TCP +# volumeMounts: +# - name: guardian-data +# mountPath: /data # - name: agent # image: solana-agent # command: @@ -70,11 +69,11 @@ spec: # - containerPort: 9000 # name: grpc # protocol: TCP - volumeClaimTemplates: - - metadata: - name: guardian-data - spec: - accessModes: [ "ReadWriteOnce" ] - resources: - requests: - storage: 1Gi +# volumeClaimTemplates: +# - metadata: +# name: guardian-data +# spec: +# accessModes: [ "ReadWriteOnce" ] +# resources: +# requests: +# storage: 1Gi diff --git a/ethereum/Dockerfile b/ethereum/Dockerfile index 55d2b968..9265d3d6 100644 --- a/ethereum/Dockerfile +++ b/ethereum/Dockerfile @@ -11,6 +11,7 @@ RUN mkdir -p /home/node/.npm WORKDIR /home/node/app # Only invalidate the npm install step if package.json changed +# TODO: build with "npm ci" for determinism ADD --chown=node:node package.json . # We want to cache node_modules *and* incorporate it into the final image. diff --git a/ethereum/src/send-lockups.js b/ethereum/src/send-lockups.js index e592c8b9..c1cb4935 100644 --- a/ethereum/src/send-lockups.js +++ b/ethereum/src/send-lockups.js @@ -36,7 +36,7 @@ module.exports = function(callback) { let block = await web3.eth.getBlock('latest'); console.log("block", block.number, "with txs", block.transactions, "and time", block.timestamp); await advanceBlock(); - await sleep(1000); + await sleep(5000); } } diff --git a/proto/gossip/v1/gossip.proto b/proto/gossip/v1/gossip.proto index 9d2662d9..eebe3f9b 100644 --- a/proto/gossip/v1/gossip.proto +++ b/proto/gossip/v1/gossip.proto @@ -4,7 +4,40 @@ package gossip.v1; option go_package = "proto/gossip/v1;gossipv1"; -message Heartbeat { - string node_name = 1; - int64 counter = 2; +message GossipMessage { + oneof message { + Heartbeat heartbeat = 1; + EthLockupObservation eth_lockup_observation = 2; + } +} + +// P2P gossip heartbeats for network introspection purposes. +message Heartbeat { + // The node's arbitrarily chosen, untrusted nodeName. + string node_name = 1; + // A monotonic counter that resets to zero on startup. + int64 counter = 2; + // UNIX wall time. + int64 timestamp = 3; + + // TODO: include statement of gk public key? + // TODO: software version/release +} + +// An EthLockupObservation is a signed statement by a given guardian node +// that they observed a finalized lockup on Ethereum. +// +// The lockup is uniquely identified by its hashed (tx_hash, nonce, values...) tuple. +// +// Other nodes will verify the signature. Once any node has observed a quorum of +// guardians submitting valid signatures for a given hash, they can be assembled into a VAA. +// +// Messages without valid signature are dropped unceremoniously. +message EthLockupObservation { + // Guardian pubkey as truncated eth address. + bytes addr = 1; + // The lockup's deterministic, unique hash. See pkg/common/chainlock.go. + bytes hash = 2; + // ECSDA signature of the hash using the node's guardian key. + bytes signature = 3; }