Преглед изворни кода

[dev.v1] backport v2 machinery (#869)

Co-authored-by: Jeff Schroeder <jeffschroeder@computer.org>
Leopold Schabel пре 3 година
родитељ
комит
8e2ccfa42a
100 измењених фајлова са 2207 додато и 7682 уклоњено
  1. 7 0
      .spr.yml
  2. 0 10
      DEVELOP.md
  3. 2 2
      Dockerfile.agent
  4. 26 0
      Dockerfile.proto
  5. 1 1
      LICENSE
  6. 4 1
      Makefile
  7. 0 1
      README.md
  8. 14 31
      Tiltfile
  9. 12 0
      buf.gen.yaml
  10. 17 0
      buf.lock
  11. 22 0
      buf.yaml
  12. 2 11
      devnet/node.yaml
  13. 0 95
      devnet/terra-devnet.yaml
  14. 0 8
      docs/operations.md
  15. 1 1
      ethereum/Dockerfile
  16. 0 25
      generate-protos.sh
  17. 2 2
      node/Dockerfile
  18. 71 4
      node/cmd/guardiand/adminclient.go
  19. 179 0
      node/cmd/guardiand/adminnodes.go
  20. 54 31
      node/cmd/guardiand/adminserver.go
  21. 28 37
      node/cmd/guardiand/admintemplate.go
  22. 23 18
      node/cmd/guardiand/adminverify.go
  23. 49 86
      node/cmd/guardiand/bridge.go
  24. 28 0
      node/cmd/guardiand/logging.go
  25. 13 151
      node/e2e/e2e_test.go
  26. 1 84
      node/e2e/eth.go
  27. 2 67
      node/e2e/solana.go
  28. 0 391
      node/e2e/terra.go
  29. 72 99
      node/go.mod
  30. 128 179
      node/go.sum
  31. 30 0
      node/pkg/common/grpc.go
  32. 113 2
      node/pkg/common/guardianset.go
  33. 0 1
      node/pkg/common/readiness.go
  34. 2 25
      node/pkg/devnet/constants.go
  35. 1 1
      node/pkg/devnet/guardianset_vaa.go
  36. 86 0
      node/pkg/ethereum/by_transaction.go
  37. 201 43
      node/pkg/ethereum/watcher.go
  38. 87 0
      node/pkg/p2p/netmetrics.go
  39. 33 0
      node/pkg/p2p/netmetrics_test.go
  40. 250 19
      node/pkg/p2p/p2p.go
  41. 18 1
      node/pkg/p2p/registry.go
  42. 4 6
      node/pkg/processor/broadcast.go
  43. 3 6
      node/pkg/processor/injection.go
  44. 3 1
      node/pkg/processor/lockup.go
  45. 5 32
      node/pkg/processor/observation.go
  46. 10 39
      node/pkg/processor/processor.go
  47. 71 0
      node/pkg/publicrpc/publicrpcserver.go
  48. 7 1
      node/pkg/readiness/health.go
  49. 11 9
      node/pkg/solana/client.go
  50. 0 107
      node/pkg/terra/sender.go
  51. 0 317
      node/pkg/terra/watcher.go
  52. 35 4
      node/pkg/vaa/structs.go
  53. 3 0
      node/pkg/version/version.go
  54. 25 2
      node/tools/go.mod
  55. 300 21
      node/tools/go.sum
  56. 3 0
      node/tools/tools.go
  57. 46 13
      proto/gossip/v1/gossip.proto
  58. 22 17
      proto/node/v1/node.proto
  59. 59 0
      proto/publicrpc/v1/publicrpc.proto
  60. 21 16
      solana/Dockerfile
  61. 0 6
      solana/devnet_setup.sh
  62. 0 3
      terra/.dockerignore
  63. 0 1236
      terra/Cargo.lock
  64. 0 13
      terra/Cargo.toml
  65. 0 26
      terra/Dockerfile
  66. 0 25
      terra/Tiltfile
  67. BIN
      terra/artifacts/cw20_base.wasm
  68. 0 50
      terra/contracts/README.md
  69. 0 5
      terra/contracts/cw20-wrapped/.cargo/config
  70. 0 26
      terra/contracts/cw20-wrapped/Cargo.toml
  71. 0 302
      terra/contracts/cw20-wrapped/src/contract.rs
  72. 0 27
      terra/contracts/cw20-wrapped/src/error.rs
  73. 0 9
      terra/contracts/cw20-wrapped/src/lib.rs
  74. 0 111
      terra/contracts/cw20-wrapped/src/msg.rs
  75. 0 25
      terra/contracts/cw20-wrapped/src/state.rs
  76. 0 227
      terra/contracts/cw20-wrapped/tests/integration.rs
  77. 0 5
      terra/contracts/wormhole/.cargo/config
  78. 0 33
      terra/contracts/wormhole/Cargo.toml
  79. 0 43
      terra/contracts/wormhole/src/byte_utils.rs
  80. 0 1335
      terra/contracts/wormhole/src/contract.rs
  81. 0 114
      terra/contracts/wormhole/src/error.rs
  82. 0 14
      terra/contracts/wormhole/src/lib.rs
  83. 0 62
      terra/contracts/wormhole/src/msg.rs
  84. 0 238
      terra/contracts/wormhole/src/state.rs
  85. 0 90
      terra/contracts/wormhole/tests/integration.rs
  86. 0 3
      terra/devnet/Dockerfile
  87. 0 4
      terra/devnet/config/addrbook.json
  88. 0 36
      terra/devnet/config/app.toml
  89. 0 335
      terra/devnet/config/config.toml
  90. 0 623
      terra/devnet/config/genesis.json
  91. 0 1
      terra/devnet/config/node_key.json
  92. 0 11
      terra/devnet/config/priv_validator_key.json
  93. 0 9
      terra/devnet/config/terrad.toml
  94. 0 15
      terra/devnet/config/wasm.toml
  95. 0 5
      terra/tools/deploy.sh
  96. 0 26
      terra/tools/lock-token.ts
  97. 0 494
      terra/tools/package-lock.json
  98. 0 22
      terra/tools/package.json
  99. 0 28
      terra/tools/prepare-token.ts
  100. 0 27
      terra/tools/prepare-wormhole.ts

+ 7 - 0
.spr.yml

@@ -0,0 +1,7 @@
+githubRepoOwner: certusone
+githubRepoName: wormhole
+githubHost: github.com
+requireChecks: false
+requireApproval: true
+githubRemote: origin
+githubBranch: dev.v1

+ 0 - 10
DEVELOP.md

@@ -126,7 +126,6 @@ After a few seconds, the SPL token balance shown below will increase as the VAA
 | `3C3m4tjTy4nSMkkYdqCDSiCWEgpDa6whvprvABdFGBiW` | Account that holds 6qRhs8oA... SPL tokens           |
 | `85kW19uNvETzH43p3AfpyqPaQS5rWouq4x9rGiKUvihf` | Wrapped token for the 0xCfEB86... ERC20 token       |
 | `7EFk3VrWeb29SWJPQs5cUyqcY3fQd33S9gELkGybRzeu` | Account that holds 85kW19u... wrapped tokens [2]    |
-| `9ESkHLgJH4zqbG7fvhpC9u2ZeHMoLJznCHtaRLviEVRh` | Wrapped token for the terra18vd8f... CW20 token     |
 | `EERzaqe8Agm8p1ZkGQFq9zKpP7MDW29FX1pC1vEw9Yfv` | Account that holds 9ESkHLg... wrapped tokens        |
 
 [1]: The account will eventually run out of funds if you run the lockup sending scripts for a long time. Refill it
@@ -143,12 +142,3 @@ using `kubectl exec solana-devnet-0 -c setup -- cli airdrop solana-devnet:9900`
 | `0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab` | Wrapped asset contract                                |
 | `0xCfEB869F69431e42cdB54A4F4f105C19C080A601` | Example ERC20 token                                   |
 | `0xf5b1d8fab1054b9cf7db274126972f97f9d42a11` | Wrapped asset address for the 6qRhs8oA... SPL token   |
-| `0x62b47a23cd900da982bdbe75aeb891d3ed18cc36` | Wrapped asset address for the terra18v... Terra token |
-
-**Terra**
-
-| Account                                        | Description                                         |
-|------------------------------------------------|-----------------------------------------------------|
-| `terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v` | Main test account                                   |
-| `terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5` | Test token account to send via bridge               |
-| `terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc` | Bridge contract instance                            |

+ 2 - 2
Dockerfile.agent

@@ -1,5 +1,5 @@
-# syntax=docker.io/docker/dockerfile:experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44
-FROM docker.io/library/rust:1.49@sha256:a50165ea96983c21832578afb1c8c028674c965bc1ed43b607871b1f362e06a5
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/library/rust:1.58@sha256:e4979d36d5d30838126ea5ef05eb59c4c25ede7f064985e676feb21402d0661b
 
 RUN apt-get update && apt-get install -y libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang ncat
 RUN rustup component add rustfmt

+ 26 - 0
Dockerfile.proto

@@ -0,0 +1,26 @@
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/golang:1.17.5@sha256:90d1ab81f3d157ca649a9ff8d251691b810d95ea6023a03cdca139df58bca599 AS go-tools
+
+RUN mkdir /app
+
+ADD tools/build.sh /app/tools/
+ADD tools/go.* /app/tools/
+
+RUN --mount=type=cache,target=/root/.cache --mount=type=cache,target=/go \
+	cd /app/tools && CGO_ENABLED=0 ./build.sh
+
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/golang:1.17.5@sha256:90d1ab81f3d157ca649a9ff8d251691b810d95ea6023a03cdca139df58bca599 AS go-build
+
+COPY --from=go-tools /app /app
+
+ADD buf.* /app
+ADD proto /app/proto
+
+RUN --mount=type=cache,target=/root/.cache \
+	cd /app && \
+	tools/bin/buf lint && \
+	tools/bin/buf generate
+
+FROM scratch AS go-export
+COPY --from=go-build /app/node/pkg/proto pkg/proto

+ 1 - 1
LICENSE

@@ -1,4 +1,4 @@
-Copyright 2020 Wormhole Project Contributors
+Copyright 2022 Wormhole Project Contributors
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.

+ 4 - 1
Makefile

@@ -22,7 +22,10 @@ install:
 
 .PHONY: generate
 generate: dirs
-	./generate-protos.sh
+	cd tools && ./build.sh
+	rm -rf bridge
+	rm -rf node/pkg/proto
+	tools/bin/buf generate
 
 .PHONY: bridge
 bridge: $(BIN)/guardiand

+ 0 - 1
README.md

@@ -16,7 +16,6 @@ See [docs/operations.md](docs/operations.md) for node operator instructions.
 | Ethereum contract | Certus One | Kudelski | ✔️ Audited      |
 | Solana contract   | Certus One | Kudelski | ✔️ Audited      |
 | Bridge node       | Certus One | Kudelski | ✔️ Audited      |
-| Terra contract    | Everstake  | Kudelski | ✔️ Audited      |
 
 ⚠ **This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 implied. See the License for the specific language governing permissions and limitations under the License.** Or plainly

+ 14 - 31
Tiltfile

@@ -59,10 +59,15 @@ def k8s_yaml_with_ns(objects):
 
 # protos
 
+proto_deps = ["./proto", "buf.yaml", "buf.gen.yaml"]
+
 local_resource(
     name = "proto-gen",
-    deps = ["./proto", "./generate-protos.sh"],
-    cmd = "./generate-protos.sh",
+    deps = proto_deps,
+    cmd = "tilt docker build -- --target go-export -f Dockerfile.proto -o type=local,dest=node .",
+    env = {"DOCKER_BUILDKIT": "1"},
+    labels = ["protobuf"],
+    allow_parallel = True,
 )
 
 # node
@@ -88,9 +93,13 @@ def build_node_yaml():
 
 k8s_yaml_with_ns(build_node_yaml())
 
-k8s_resource("guardian", resource_deps = ["proto-gen", "solana-devnet"], port_forwards = [
-    port_forward(6060, name = "Debug/Status Server [:6060]"),
-])
+k8s_resource(
+    "guardian",
+    resource_deps = ["proto-gen", "solana-devnet"],
+    port_forwards = [
+        port_forward(6060, name = "Debug/Status Server [:6060]"),
+    ],
+)
 
 # solana agent and cli (runs alongside node)
 
@@ -147,29 +156,3 @@ k8s_yaml_with_ns("devnet/eth-devnet.yaml")
 k8s_resource("eth-devnet", port_forwards = [
     port_forward(8545, name = "Ganache RPC [:8545]"),
 ])
-
-# terra devnet
-
-docker_build(
-    ref = "terra-image",
-    context = "./terra/devnet",
-    dockerfile = "terra/devnet/Dockerfile",
-)
-
-docker_build(
-    ref = "terra-contracts",
-    context = "./terra",
-    dockerfile = "./terra/Dockerfile",
-)
-
-k8s_yaml_with_ns("devnet/terra-devnet.yaml")
-
-k8s_resource(
-    "terra-lcd",
-    port_forwards = [port_forward(1317, name = "Terra LCD interface [:1317]")],
-)
-
-k8s_resource(
-    "terra-terrad",
-    port_forwards = [port_forward(26657, name = "Terra RPC [:26657]")],
-)

+ 12 - 0
buf.gen.yaml

@@ -0,0 +1,12 @@
+version: v1beta1
+plugins:
+  - name: go
+    out: node/pkg/proto
+    path: tools/bin/protoc-gen-go
+    opt:
+      - paths=source_relative
+  - name: go-grpc
+    out: node/pkg/proto
+    path: tools/bin/protoc-gen-go-grpc
+    opt:
+      - paths=source_relative

+ 17 - 0
buf.lock

@@ -0,0 +1,17 @@
+# Generated by buf. DO NOT EDIT.
+version: v1
+deps:
+  - remote: buf.build
+    owner: beta
+    repository: googleapis
+    branch: main
+    commit: 1c473ad9220a49bca9320f4cc690eba5
+    digest: b1-unlhrcI3tnJd0JEGuOb692LZ_tY_gCGq6mK1bgCn1Pg=
+    create_time: 2021-06-23T20:16:47.788079Z
+  - remote: buf.build
+    owner: grpc-ecosystem
+    repository: grpc-gateway
+    branch: main
+    commit: 3d91372e5af6451c8af137f3b5594ac6
+    digest: b1-Iy4ANYcU1Z6T0LEswISKGoxqT-UJNNHWlJS6ZFhno4o=
+    create_time: 2021-08-01T01:14:29.490277Z

+ 22 - 0
buf.yaml

@@ -0,0 +1,22 @@
+---
+version: v1beta1
+name: buf.build/certusone/wormhole
+deps:
+  - buf.build/beta/googleapis
+  - buf.build/grpc-ecosystem/grpc-gateway
+build:
+  roots:
+    - proto
+lint:
+  use:
+    - DEFAULT
+    # https://github.com/twitchtv/twirp/issues/70#issuecomment-470367807
+    - UNARY_RPC
+  ignore_only:
+    SERVICE_SUFFIX:
+      # Too late to rename the service from Agent to AgentService -
+      # service names cannot be changed in a backwards compatible fashion.
+      - agent/v1/service.proto
+breaking:
+  use:
+    - WIRE_JSON

+ 2 - 11
devnet/node.yaml

@@ -60,17 +60,6 @@ spec:
             - bridge
             - --ethRPC
             - ws://eth-devnet:8545
-            - --terra
-            - --terraWS
-            - ws://terra-terrad:26657/websocket
-            - --terraLCD
-            - http://terra-lcd:1317
-            - --terraChainID
-            - localterra
-            - --terraContract
-            - terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc
-            - --terraKey
-            - /tmp/terra.key
             - --agentRPC
             - /run/bridge/agent.sock
             - --ethConfirmations
@@ -81,6 +70,8 @@ spec:
             - ws://solana-devnet:8900
             - --solanaRPC
             - http://solana-devnet:8899
+            - --statusAddr
+            - '[::]:6060'
             - --unsafeDevMode
             - --bridgeKey
             - /tmp/bridge.key

+ 0 - 95
devnet/terra-devnet.yaml

@@ -1,95 +0,0 @@
----
-apiVersion: v1
-kind: Service
-metadata:
-  labels:
-    app: terra-lcd
-  name: terra-lcd
-spec:
-  ports:
-  - name: lcd
-    port: 1317
-    protocol: TCP
-  selector:
-    app: terra-lcd
----
-apiVersion: v1
-kind: Service
-metadata:
-  labels:
-    app: terra-terrad
-  name: terra-terrad
-spec:
-  ports:
-  - name: rpc
-    port: 26657
-    protocol: TCP
-  selector:
-    app: terra-terrad
----
-apiVersion: apps/v1
-kind: StatefulSet
-metadata:
-  labels:
-    app: terra-lcd
-  name: terra-lcd
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: terra-lcd
-  template:
-    metadata:
-      labels:
-        app: terra-lcd
-    spec:
-      containers:
-      - args:
-        - terracli
-        - rest-server
-        - --laddr=tcp://0.0.0.0:1317
-        - --node=tcp://terra-terrad:26657
-        - --trust-node=true
-        - --unsafe-cors
-        image: terra-image
-        name: terra-lcd
-        ports:
-        - containerPort: 1317
-        readinessProbe:
-          tcpSocket:
-            port: 1317
-      restartPolicy: Always
-  serviceName: terra-lcd
----
-apiVersion: apps/v1
-kind: StatefulSet
-metadata:
-  labels:
-    app: terra-terrad
-  name: terra-terrad
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: terra-terrad
-  template:
-    metadata:
-      labels:
-        app: terra-terrad
-    spec:
-      containers:
-      - args:
-        - terrad
-        - start
-        image: terra-image
-        name: terra-terrad
-        ports:
-        - containerPort: 26657
-        readinessProbe:
-          httpGet:
-            port: 26657
-        resources: {}
-      - name: terra-contracts
-        image: terra-contracts
-      restartPolicy: Always
-  serviceName: terra-terrad

+ 0 - 8
docs/operations.md

@@ -16,12 +16,6 @@ In addition to Wormhole itself, you need to run your own verifying node for ever
 
 - **Ethereum**. See below - you need at least a light client. For stability reasons, a full node is recommended.
 
-- \[**Terra** requires a full node and an [LCD server](https://docs.terra.money/terracli/lcd.html#light-client-daemon)
-  pointing to your full node. Refer to the [Terra documentation](https://docs.terra.money/node/join-network.html)
-  on how to run a full node. From a security point of view, running only an LCD server with `--trust-node=false` pointed
-  to somebody else's full node would be sufficient, but you'd then depend on that single node for availability unless
-  you set up a load balancer pointing to a set of nodes.\]
-
 Do NOT use third-party RPC service providers for any of the chains! You'd fully trust them and they could lie to you on
 whether a lockup has actually been observed, and the whole point of Wormhole is to not rely on centralized nodes.
 
@@ -252,8 +246,6 @@ You'll have to manage the following keys:
    fees to reimburse guardians, so during normal operation, you shouldn't have to top up the account (but by
    all means, set up monitoring for it!).
    
- - _\[The **Terra fee payer** account. Terra support is still a work in progress - more details on this later\]._ 
-
 For production, we strongly recommend to either encrypt your disks, and/or take care to never have keys touch the disk.
 One way to accomplish is to store keys on an in-memory ramfs, which can't be swapped out, and restore it from cold
 storage or an HSM/vault whenever the node is rebooted. You might want to disable swap altogether. None of that is

+ 1 - 1
ethereum/Dockerfile

@@ -1,4 +1,4 @@
-# syntax=docker.io/docker/dockerfile:experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
 FROM node:lts-alpine@sha256:2ae9624a39ce437e7f58931a5747fdc60224c6e40f8980db90728de58e22af7c
 
 # npm wants to clone random Git repositories - lovely.

+ 0 - 25
generate-protos.sh

@@ -1,25 +0,0 @@
-#!/usr/bin/env bash
-
-(
-  cd tools/
-  ./build.sh
-)
-
-(
-  cd third_party/
-  [[ ! -d googleapis ]] && git clone https://github.com/googleapis/googleapis
-  cd googleapis
-  git checkout 24fb9e5d1f37110bfa198189c34324aa3fdb0896
-)
-
-tools/bin/buf protoc \
-  -Iproto \
-  -Ithird_party/googleapis \
-  --plugin tools/bin/protoc-gen-go \
-  --go_out=node/pkg/ proto/**/**/**
-
-tools/bin/buf protoc \
-  -Iproto \
-  -Ithird_party/googleapis \
-  --plugin tools/bin/protoc-gen-go-grpc \
-  --go-grpc_out=node/pkg/ proto/**/**/**

+ 2 - 2
node/Dockerfile

@@ -1,5 +1,5 @@
-# syntax=docker.io/docker/dockerfile:experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44
-FROM docker.io/golang:1.17.0@sha256:06e92e576fc7a7067a268d47727f3083c0a564331bfcbfdde633157fc91fb17d AS go-tools
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/golang:1.17.5@sha256:90d1ab81f3d157ca649a9ff8d251691b810d95ea6023a03cdca139df58bca599
 
 WORKDIR /app
 

+ 71 - 4
node/cmd/guardiand/adminclient.go

@@ -2,9 +2,14 @@ package guardiand
 
 import (
 	"context"
+	"encoding/hex"
 	"fmt"
+	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
+	publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
+	"github.com/spf13/pflag"
 	"io/ioutil"
 	"log"
+	"strconv"
 	"time"
 
 	"github.com/spf13/cobra"
@@ -18,15 +23,23 @@ import (
 var clientSocketPath *string
 
 func init() {
-	pf := AdminClientInjectGuardianSetUpdateCmd.Flags()
+	// Shared flags for all admin commands
+	pf := pflag.NewFlagSet("commonAdminFlags", pflag.ContinueOnError)
 	clientSocketPath = pf.String("socket", "", "gRPC admin server socket to connect to")
 	err := cobra.MarkFlagRequired(pf, "socket")
 	if err != nil {
 		panic(err)
 	}
 
+	AdminClientInjectGuardianSetUpdateCmd.Flags().AddFlagSet(pf)
+	AdminClientGovernanceVAAVerifyCmd.Flags().AddFlagSet(pf)
+	AdminClientListNodes.Flags().AddFlagSet(pf)
+	SendObservationRequest.Flags().AddFlagSet(pf)
+
 	AdminCmd.AddCommand(AdminClientInjectGuardianSetUpdateCmd)
 	AdminCmd.AddCommand(AdminClientGovernanceVAAVerifyCmd)
+	AdminCmd.AddCommand(AdminClientListNodes)
+	AdminCmd.AddCommand(SendObservationRequest)
 }
 
 var AdminCmd = &cobra.Command{
@@ -41,14 +54,32 @@ var AdminClientInjectGuardianSetUpdateCmd = &cobra.Command{
 	Args:  cobra.ExactArgs(1),
 }
 
-func getAdminClient(ctx context.Context, addr string) (*grpc.ClientConn, error, nodev1.NodePrivilegedClient) {
+var SendObservationRequest = &cobra.Command{
+	Use:   "send-observation-request [CHAIN_ID] [TX_HASH_HEX]",
+	Short: "Broadcast an observation request for the given chain ID and chain-specific tx_hash",
+	Run:   runSendObservationRequest,
+	Args:  cobra.ExactArgs(2),
+}
+
+func getAdminClient(ctx context.Context, addr string) (*grpc.ClientConn, error, nodev1.NodePrivilegedServiceClient) {
 	conn, err := grpc.DialContext(ctx, fmt.Sprintf("unix:///%s", addr), grpc.WithInsecure())
 
 	if err != nil {
 		log.Fatalf("failed to connect to %s: %v", addr, err)
 	}
 
-	c := nodev1.NewNodePrivilegedClient(conn)
+	c := nodev1.NewNodePrivilegedServiceClient(conn)
+	return conn, err, c
+}
+
+func getPublicRPCServiceClient(ctx context.Context, addr string) (*grpc.ClientConn, error, publicrpcv1.PublicRPCServiceClient) {
+	conn, err := grpc.DialContext(ctx, fmt.Sprintf("unix:///%s", addr), grpc.WithInsecure())
+
+	if err != nil {
+		log.Fatalf("failed to connect to %s: %v", addr, err)
+	}
+
+	c := publicrpcv1.NewPublicRPCServiceClient(conn)
 	return conn, err, c
 }
 
@@ -59,6 +90,9 @@ func runInjectGovernanceVAA(cmd *cobra.Command, args []string) {
 
 	conn, err, c := getAdminClient(ctx, *clientSocketPath)
 	defer conn.Close()
+	if err != nil {
+		log.Fatalf("failed to get admin client: %v", err)
+	}
 
 	b, err := ioutil.ReadFile(path)
 	if err != nil {
@@ -76,5 +110,38 @@ func runInjectGovernanceVAA(cmd *cobra.Command, args []string) {
 		log.Fatalf("failed to submit governance VAA: %v", err)
 	}
 
-	log.Printf("VAA successfully injected with digest %s", hexutils.BytesToHex(resp.Digest))
+	for _, digest := range resp.Digests {
+		log.Printf("VAA successfully injected with digest %s", hexutils.BytesToHex(digest))
+	}
+}
+
+func runSendObservationRequest(cmd *cobra.Command, args []string) {
+	chainID, err := strconv.Atoi(args[0])
+	if err != nil {
+		log.Fatalf("invalid chain ID: %v", err)
+	}
+
+	txHash, err := hex.DecodeString(args[1])
+	if err != nil {
+		log.Fatalf("invalid transaction hash: %v", err)
+	}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	conn, err, c := getAdminClient(ctx, *clientSocketPath)
+	defer conn.Close()
+	if err != nil {
+		log.Fatalf("failed to get admin client: %v", err)
+	}
+
+	_, err = c.SendObservationRequest(ctx, &nodev1.SendObservationRequestRequest{
+		ObservationRequest: &gossipv1.ObservationRequest{
+			ChainId: uint32(chainID),
+			TxHash:  txHash,
+		},
+	})
+	if err != nil {
+		log.Fatalf("failed to send observation request: %v", err)
+	}
 }

+ 179 - 0
node/cmd/guardiand/adminnodes.go

@@ -0,0 +1,179 @@
+package guardiand
+
+import (
+	"context"
+	"fmt"
+	publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
+	"github.com/certusone/wormhole/node/pkg/vaa"
+	"github.com/spf13/cobra"
+	"log"
+	"os"
+	"sort"
+	"strings"
+	"text/tabwriter"
+	"time"
+)
+
+// How to test in container:
+//    kubectl exec guardian-0 -- /guardiand admin list-nodes --socket /tmp/admin.sock
+
+var (
+	showDetails bool
+	only        []string
+)
+
+func init() {
+	AdminClientListNodes.Flags().BoolVar(&showDetails, "showDetails", false, "Show error counter and contract addresses")
+	AdminClientListNodes.Flags().StringSliceVar(&only, "only", nil, "Show only networks with the given name")
+}
+
+var AdminClientListNodes = &cobra.Command{
+	Use:   "list-nodes",
+	Short: "Fetches an aggregated list of guardian nodes",
+	Run:   runListNodes,
+}
+
+func runListNodes(cmd *cobra.Command, args []string) {
+	ctx := context.Background()
+	conn, err, c := getPublicRPCServiceClient(ctx, *clientSocketPath)
+	defer conn.Close()
+	if err != nil {
+		log.Fatalf("failed to get publicrpc client: %v", err)
+	}
+
+	lastHeartbeats, err := c.GetLastHeartbeats(ctx, &publicrpcv1.GetLastHeartbeatsRequest{})
+	if err != nil {
+		log.Fatalf("failed to list nodes: %v", err)
+	}
+
+	gs, err := c.GetCurrentGuardianSet(ctx, &publicrpcv1.GetCurrentGuardianSetRequest{})
+	if err != nil {
+		log.Fatalf("failed to list current guardian get: %v", err)
+	}
+
+	log.Printf("current guardian set index: %d (%d guardians)",
+		gs.GuardianSet.Index, len(gs.GuardianSet.Addresses))
+
+	nodes := lastHeartbeats.Entries
+
+	sort.Slice(nodes, func(i, j int) bool {
+		if nodes[i].RawHeartbeat == nil || nodes[j].RawHeartbeat == nil {
+			return false
+		}
+		return nodes[i].RawHeartbeat.NodeName < nodes[j].RawHeartbeat.NodeName
+	})
+
+	log.Printf("%d nodes in guardian state set", len(nodes))
+
+	w := tabwriter.NewWriter(os.Stdout, 0, 8, 2, ' ', 0)
+
+	headers := []string{
+		"Node key",
+		"Guardian key",
+		"Node name",
+		"Version",
+		"Last seen",
+	}
+
+	if showDetails {
+		headers = append(headers, "Uptime")
+	}
+
+	type network struct {
+		string
+		vaa.ChainID
+	}
+
+	networks := []network{
+		{"Solana", vaa.ChainIDSolana},
+		{"Ethereum", vaa.ChainIDEthereum},
+	}
+
+	if len(only) > 0 {
+		var filtered []network
+		for _, network := range networks {
+			for _, name := range only {
+				if strings.EqualFold(network.string, name) {
+					filtered = append(filtered, network)
+				}
+			}
+		}
+		networks = filtered
+	}
+
+	for _, k := range networks {
+		headers = append(headers, k.string)
+	}
+
+	for _, header := range headers {
+		_, _ = fmt.Fprintf(w, "%s\t", header)
+	}
+	_, _ = fmt.Fprintln(w)
+
+	for _, h := range nodes {
+		if h.RawHeartbeat == nil {
+			continue
+		}
+
+		last := time.Unix(0, h.RawHeartbeat.Timestamp)
+		boot := time.Unix(0, h.RawHeartbeat.BootTimestamp)
+
+		heights := map[vaa.ChainID]int64{}
+		truncAddrs := make(map[vaa.ChainID]string)
+		errors := map[vaa.ChainID]uint64{}
+		for _, n := range h.RawHeartbeat.Networks {
+			heights[vaa.ChainID(n.Id)] = n.Height
+			errors[vaa.ChainID(n.Id)] = n.ErrorCount
+			if len(n.ContractAddress) >= 16 {
+				truncAddrs[vaa.ChainID(n.Id)] = n.ContractAddress[:16]
+			} else {
+				truncAddrs[vaa.ChainID(n.Id)] = "INVALID"
+			}
+		}
+
+		fields := []string{
+			h.P2PNodeAddr,
+			h.RawHeartbeat.GuardianAddr,
+			h.RawHeartbeat.NodeName,
+			h.RawHeartbeat.Version,
+			time.Since(last).String(),
+		}
+
+		if showDetails {
+			fields = append(fields, time.Since(boot).String())
+		}
+
+		for _, n := range networks {
+			if showDetails {
+				fields = append(fields, fmt.Sprintf("%s %d (%d)",
+					truncAddrs[n.ChainID], heights[n.ChainID], errors[n.ChainID]))
+			} else {
+				fields = append(fields, fmt.Sprintf("%d", heights[n.ChainID]))
+			}
+		}
+
+		for _, field := range fields {
+			_, _ = fmt.Fprintf(w, "%s\t", field)
+		}
+
+		_, _ = fmt.Fprintln(w)
+	}
+
+	w.Flush()
+	fmt.Print("\n")
+
+	for _, addr := range gs.GuardianSet.Addresses {
+		var found bool
+		for _, h := range nodes {
+			if h.VerifiedGuardianAddr == addr {
+				found = true
+			}
+		}
+
+		if !found {
+			fmt.Printf("Missing guardian: %s\n", addr)
+		}
+	}
+
+	fmt.Println("\n[do not parse - use the gRPC or REST API for scripting]")
+}

+ 54 - 31
node/cmd/guardiand/adminserver.go

@@ -4,6 +4,9 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
+	publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
+	"github.com/certusone/wormhole/node/pkg/publicrpc"
 	"math"
 	"net"
 	"os"
@@ -11,7 +14,6 @@ import (
 
 	ethcommon "github.com/ethereum/go-ethereum/common"
 	"go.uber.org/zap"
-	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
 
@@ -22,9 +24,10 @@ import (
 )
 
 type nodePrivilegedService struct {
-	nodev1.UnimplementedNodePrivilegedServer
-	injectC chan<- *vaa.VAA
-	logger  *zap.Logger
+	nodev1.UnimplementedNodePrivilegedServiceServer
+	injectC      chan<- *vaa.VAA
+	obsvReqSendC chan *gossipv1.ObservationRequest
+	logger       *zap.Logger
 }
 
 // adminGuardianSetUpdateToVAA converts a nodev1.GuardianSetUpdate message to its canonical VAA representation.
@@ -101,35 +104,42 @@ func (s *nodePrivilegedService) InjectGovernanceVAA(ctx context.Context, req *no
 		v   *vaa.VAA
 		err error
 	)
-	switch payload := req.Payload.(type) {
-	case *nodev1.InjectGovernanceVAARequest_GuardianSet:
-		v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, req.CurrentSetIndex, req.Timestamp)
-	case *nodev1.InjectGovernanceVAARequest_ContractUpgrade:
-		v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, req.Timestamp)
-	default:
-		panic(fmt.Sprintf("unsupported VAA type: %T", payload))
-	}
-	if err != nil {
-		return nil, status.Error(codes.InvalidArgument, err.Error())
-	}
 
-	// Generate digest of the unsigned VAA.
-	digest, err := v.SigningMsg()
-	if err != nil {
-		panic(err)
-	}
+	digests := make([][]byte, len(req.Messages))
 
-	s.logger.Info("governance VAA constructed",
-		zap.Any("vaa", v),
-		zap.String("digest", digest.String()),
-	)
+	for i, message := range req.Messages {
+		switch payload := message.Payload.(type) {
+		case *nodev1.GovernanceMessage_GuardianSet:
+			v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, req.CurrentSetIndex, message.Timestamp)
+		case *nodev1.GovernanceMessage_ContractUpgrade:
+			v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, req.CurrentSetIndex, message.Timestamp)
+		default:
+			panic(fmt.Sprintf("unsupported VAA type: %T", payload))
+		}
+		if err != nil {
+			return nil, status.Error(codes.InvalidArgument, err.Error())
+		}
+
+		// Generate digest of the unsigned VAA.
+		digest, err := v.SigningMsg()
+		if err != nil {
+			panic(err)
+		}
+
+		s.logger.Info("governance VAA constructed",
+			zap.Any("vaa", v),
+			zap.String("digest", digest.String()),
+		)
 
-	s.injectC <- v
+		s.injectC <- v
 
-	return &nodev1.InjectGovernanceVAAResponse{Digest: digest.Bytes()}, nil
+		digests[i] = digest.Bytes()
+	}
+
+	return &nodev1.InjectGovernanceVAAResponse{Digests: digests}, nil
 }
 
-func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<- *vaa.VAA) (supervisor.Runnable, error) {
+func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<- *vaa.VAA, obsvReqSendC chan *gossipv1.ObservationRequest, gst *common.GuardianSetState) (supervisor.Runnable, error) {
 	// Delete existing UNIX socket, if present.
 	fi, err := os.Stat(socketPath)
 	if err == nil {
@@ -151,6 +161,9 @@ func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<-
 	// The umask avoids a race condition between file creation and chmod.
 
 	laddr, err := net.ResolveUnixAddr("unix", socketPath)
+	if err != nil {
+		return nil, fmt.Errorf("invalid listen address: %w", err)
+	}
 	l, err := net.ListenUnix("unix", laddr)
 	if err != nil {
 		return nil, fmt.Errorf("failed to listen on %s: %w", socketPath, err)
@@ -159,11 +172,21 @@ func adminServiceRunnable(logger *zap.Logger, socketPath string, injectC chan<-
 	logger.Info("admin server listening on", zap.String("path", socketPath))
 
 	nodeService := &nodePrivilegedService{
-		injectC: injectC,
-		logger:  logger.Named("adminservice"),
+		injectC:      injectC,
+		obsvReqSendC: obsvReqSendC,
+		logger:       logger.Named("adminservice"),
 	}
 
-	grpcServer := grpc.NewServer()
-	nodev1.RegisterNodePrivilegedServer(grpcServer, nodeService)
+	publicrpcService := publicrpc.NewPublicrpcServer(logger, gst)
+
+	grpcServer := common.NewInstrumentedGRPCServer(logger)
+	nodev1.RegisterNodePrivilegedServiceServer(grpcServer, nodeService)
+	publicrpcv1.RegisterPublicRPCServiceServer(grpcServer, publicrpcService)
 	return supervisor.GRPCServer(grpcServer, l, false), nil
 }
+
+func (s *nodePrivilegedService) SendObservationRequest(ctx context.Context, req *nodev1.SendObservationRequestRequest) (*nodev1.SendObservationRequestResponse, error) {
+	s.obsvReqSendC <- req.ObservationRequest
+	s.logger.Info("sent observation request", zap.Any("request", req.ObservationRequest))
+	return &nodev1.SendObservationRequestResponse{}, nil
+}

+ 28 - 37
node/cmd/guardiand/admintemplate.go

@@ -2,15 +2,11 @@ package guardiand
 
 import (
 	"fmt"
-	"io/ioutil"
-	"log"
-
+	"github.com/certusone/wormhole/node/pkg/devnet"
+	nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1"
 	"github.com/ethereum/go-ethereum/crypto"
 	"github.com/spf13/cobra"
 	"google.golang.org/protobuf/encoding/prototext"
-
-	"github.com/certusone/wormhole/node/pkg/devnet"
-	nodev1 "github.com/certusone/wormhole/node/pkg/proto/node/v1"
 )
 
 var setUpdateNumGuardians *int
@@ -30,22 +26,18 @@ var TemplateCmd = &cobra.Command{
 }
 
 var AdminClientGuardianSetTemplateCmd = &cobra.Command{
-	Use:   "guardian-set-update [FILENAME]",
-	Short: "Generate an empty guardian set template at specified path (offline)",
+	Use:   "guardian-set-update",
+	Short: "Generate an empty guardian set template",
 	Run:   runGuardianSetTemplate,
-	Args:  cobra.ExactArgs(1),
 }
 
 var AdminClientContractUpgradeTemplateCmd = &cobra.Command{
-	Use:   "contract-upgrade [FILENAME]",
-	Short: "Generate an empty contract upgrade template at specified path (offline)",
+	Use:   "contract-upgrade",
+	Short: "Generate an empty contract upgrade template",
 	Run:   runContractUpgradeTemplate,
-	Args:  cobra.ExactArgs(1),
 }
 
 func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
-	path := args[0]
-
 	// Use deterministic devnet addresses as examples in the template, such that this doubles as a test fixture.
 	guardians := make([]*nodev1.GuardianSetUpdate_Guardian, *setUpdateNumGuardians)
 	for i := 0; i < *setUpdateNumGuardians; i++ {
@@ -58,11 +50,15 @@ func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
 
 	m := &nodev1.InjectGovernanceVAARequest{
 		CurrentSetIndex: uint32(*templateGuardianIndex),
-		// Timestamp is hardcoded to make it reproducible on different devnet nodes.
-		// In production, a real UNIX timestamp should be used (see node.proto).
-		Timestamp: 1605744545,
-		Payload: &nodev1.InjectGovernanceVAARequest_GuardianSet{
-			GuardianSet: &nodev1.GuardianSetUpdate{Guardians: guardians},
+		Messages: []*nodev1.GovernanceMessage{
+			{
+				// Timestamp is hardcoded to make it reproducible on different devnet nodes.
+				// In production, a real UNIX timestamp should be used (see node.proto).
+				Timestamp: 1605744545,
+				Payload: &nodev1.GovernanceMessage_GuardianSet{
+					GuardianSet: &nodev1.GuardianSetUpdate{Guardians: guardians},
+				},
+			},
 		},
 	}
 
@@ -71,24 +67,23 @@ func runGuardianSetTemplate(cmd *cobra.Command, args []string) {
 		panic(err)
 	}
 
-	err = ioutil.WriteFile(path, b, 0640)
-	if err != nil {
-		log.Fatal(err)
-	}
+	fmt.Print(string(b))
 }
 
 func runContractUpgradeTemplate(cmd *cobra.Command, args []string) {
-	path := args[0]
-
 	m := &nodev1.InjectGovernanceVAARequest{
 		CurrentSetIndex: uint32(*templateGuardianIndex),
-		// Timestamp is hardcoded to make it reproducible on different devnet nodes.
-		// In production, a real UNIX timestamp should be used (see node.proto).
-		Timestamp: 1605744545,
-		Payload: &nodev1.InjectGovernanceVAARequest_ContractUpgrade{
-			ContractUpgrade: &nodev1.ContractUpgrade{
-				ChainId:     1,
-				NewContract: make([]byte, 32),
+		Messages: []*nodev1.GovernanceMessage{
+			{
+				// Timestamp is hardcoded to make it reproducible on different devnet nodes.
+				// In production, a real UNIX timestamp should be used (see node.proto).
+				Timestamp: 1605744545,
+				Payload: &nodev1.GovernanceMessage_ContractUpgrade{
+					ContractUpgrade: &nodev1.ContractUpgrade{
+						ChainId:     1,
+						NewContract: make([]byte, 32),
+					},
+				},
 			},
 		},
 	}
@@ -97,9 +92,5 @@ func runContractUpgradeTemplate(cmd *cobra.Command, args []string) {
 	if err != nil {
 		panic(err)
 	}
-
-	err = ioutil.WriteFile(path, b, 0640)
-	if err != nil {
-		log.Fatal(err)
-	}
+	fmt.Print(string(b))
 }

+ 23 - 18
node/cmd/guardiand/adminverify.go

@@ -1,6 +1,7 @@
 package guardiand
 
 import (
+	"encoding/hex"
 	"github.com/certusone/wormhole/node/pkg/vaa"
 	"io/ioutil"
 	"log"
@@ -33,23 +34,27 @@ func runGovernanceVAAVerify(cmd *cobra.Command, args []string) {
 		log.Fatalf("failed to deserialize: %v", err)
 	}
 
-	var (
-		v *vaa.VAA
-	)
-	switch payload := msg.Payload.(type) {
-	case *nodev1.InjectGovernanceVAARequest_GuardianSet:
-		v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, msg.CurrentSetIndex, msg.Timestamp)
-	case *nodev1.InjectGovernanceVAARequest_ContractUpgrade:
-		v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, msg.CurrentSetIndex, msg.Timestamp)
+	for _, message := range msg.Messages {
+		var (
+			v *vaa.VAA
+		)
+		switch payload := message.Payload.(type) {
+		case *nodev1.GovernanceMessage_GuardianSet:
+			v, err = adminGuardianSetUpdateToVAA(payload.GuardianSet, msg.CurrentSetIndex, message.Timestamp)
+		case *nodev1.GovernanceMessage_ContractUpgrade:
+			v, err = adminContractUpgradeToVAA(payload.ContractUpgrade, msg.CurrentSetIndex, message.Timestamp)
+		}
+		if err != nil {
+			log.Fatalf("invalid update: %v", err)
+		}
+
+		digest, err := v.SigningMsg()
+		if err != nil {
+			panic(err)
+		}
+
+		log.Printf("Serialized: %v", hex.EncodeToString(b))
+
+		log.Printf("VAA with digest %s: %+v", digest.Hex(), spew.Sdump(v))
 	}
-	if err != nil {
-		log.Fatalf("invalid update: %v", err)
-	}
-
-	digest, err := v.SigningMsg()
-	if err != nil {
-		panic(err)
-	}
-
-	log.Printf("VAA with digest %s: %+v", digest.Hex(), spew.Sdump(v))
 }

+ 49 - 86
node/cmd/guardiand/bridge.go

@@ -3,12 +3,13 @@ package guardiand
 import (
 	"context"
 	"fmt"
+	"go.uber.org/zap/zapcore"
 	"net/http"
 	_ "net/http/pprof"
 	"os"
 	"syscall"
 
-	solana_types "github.com/dfuse-io/solana-go"
+	solana_types "github.com/gagliardetto/solana-go"
 	"github.com/gorilla/mux"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 
@@ -31,8 +32,6 @@ import (
 	"github.com/certusone/wormhole/node/pkg/supervisor"
 	"github.com/certusone/wormhole/node/pkg/vaa"
 
-	"github.com/certusone/wormhole/node/pkg/terra"
-
 	ipfslog "github.com/ipfs/go-log/v2"
 )
 
@@ -54,13 +53,6 @@ var (
 	ethContract      *string
 	ethConfirmations *uint64
 
-	terraSupport  *bool
-	terraWS       *string
-	terraLCD      *string
-	terraChainID  *string
-	terraContract *string
-	terraKeyPath  *string
-
 	solanaWsRPC *string
 	solanaRPC   *string
 
@@ -78,7 +70,7 @@ func init() {
 	p2pPort = BridgeCmd.Flags().Uint("port", 8999, "P2P UDP listener port")
 	p2pBootstrap = BridgeCmd.Flags().String("bootstrap", "", "P2P bootstrap peers (comma-separated)")
 
-	statusAddr = BridgeCmd.Flags().String("statusAddr", "Listen address for status server (disabled if blank)", "[::1]:6060")
+	statusAddr = BridgeCmd.Flags().String("statusAddr", "[::1]:6060", "Listen address for status server (disabled if blank)")
 
 	nodeKeyPath = BridgeCmd.Flags().String("nodeKey", "", "Path to node key (will be generated if it doesn't exist)")
 
@@ -91,13 +83,6 @@ func init() {
 	ethContract = BridgeCmd.Flags().String("ethContract", "", "Ethereum bridge contract address")
 	ethConfirmations = BridgeCmd.Flags().Uint64("ethConfirmations", 15, "Ethereum confirmation count requirement")
 
-	terraSupport = BridgeCmd.Flags().Bool("terra", false, "Turn on support for Terra")
-	terraWS = BridgeCmd.Flags().String("terraWS", "", "Path to terrad root for websocket connection")
-	terraLCD = BridgeCmd.Flags().String("terraLCD", "", "Path to LCD service root for http calls")
-	terraChainID = BridgeCmd.Flags().String("terraChainID", "", "Terra chain ID, used in LCD client initialization")
-	terraContract = BridgeCmd.Flags().String("terraContract", "", "Wormhole contract address on Terra blockchain")
-	terraKeyPath = BridgeCmd.Flags().String("terraKey", "", "Path to mnemonic for account paying gas for submitting transactions to Terra")
-
 	solanaWsRPC = BridgeCmd.Flags().String("solanaWS", "", "Solana Websocket URL (required")
 	solanaRPC = BridgeCmd.Flags().String("solanaRPC", "", "Solana RPC URL (required")
 
@@ -128,21 +113,6 @@ const devwarning = `
 
 `
 
-func rootLoggerName() string {
-	if *unsafeDevMode {
-		// FIXME: add hostname to root logger for cleaner console output in multi-node development.
-		// The proper way is to change the output format to include the hostname.
-		hostname, err := os.Hostname()
-		if err != nil {
-			panic(err)
-		}
-
-		return fmt.Sprintf("%s-%s", "wormhole", hostname)
-	} else {
-		return "wormhole"
-	}
-}
-
 // lockMemory locks current and future pages in memory to protect secret keys from being swapped out to disk.
 // It's possible (and strongly recommended) to deploy Wormhole such that keys are only ever
 // stored in memory and never touch the disk. This is a privileged operation and requires CAP_IPC_LOCK.
@@ -183,8 +153,27 @@ func runBridge(cmd *cobra.Command, args []string) {
 		os.Exit(1)
 	}
 
-	// Our root logger. Convert directly to a regular Zap logger.
-	logger := ipfslog.Logger(rootLoggerName()).Desugar()
+	logger := zap.New(zapcore.NewCore(
+		consoleEncoder{zapcore.NewConsoleEncoder(
+			zap.NewDevelopmentEncoderConfig())},
+		zapcore.AddSync(zapcore.Lock(os.Stderr)),
+		zap.NewAtomicLevelAt(zapcore.Level(lvl))))
+
+	if *unsafeDevMode {
+		// 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)
+		}
+		*nodeName = hostname
+
+		// Put node name into the log for development.
+		logger = logger.Named(*nodeName)
+	}
+
+	// Redirect ipfs logs to plain zap
+	ipfslog.SetPrimaryCore(logger.Core())
 
 	// Override the default go-log config, which uses a magic environment variable.
 	ipfslog.SetAllLoggers(lvl)
@@ -192,9 +181,6 @@ func runBridge(cmd *cobra.Command, args []string) {
 	// Register components for readiness checks.
 	readiness.RegisterComponent(common.ReadinessEthSyncing)
 	readiness.RegisterComponent(common.ReadinessSolanaSyncing)
-	if *terraSupport {
-		readiness.RegisterComponent(common.ReadinessTerraSyncing)
-	}
 
 	if *statusAddr != "" {
 		// Use a custom routing instead of using http.DefaultServeMux directly to avoid accidentally exposing packages
@@ -217,7 +203,7 @@ func runBridge(cmd *cobra.Command, args []string) {
 
 		go func() {
 			logger.Info("status server listening on [::]:6060")
-			logger.Error("status server crashed", zap.Error(http.ListenAndServe("[::]:6060", router)))
+			logger.Error("status server crashed", zap.Error(http.ListenAndServe(*statusAddr, router)))
 		}()
 	}
 
@@ -277,24 +263,6 @@ func runBridge(cmd *cobra.Command, args []string) {
 		logger.Fatal("Please specify --solanaUrl")
 	}
 
-	if *terraSupport {
-		if *terraWS == "" {
-			logger.Fatal("Please specify --terraWS")
-		}
-		if *terraLCD == "" {
-			logger.Fatal("Please specify --terraLCD")
-		}
-		if *terraChainID == "" {
-			logger.Fatal("Please specify --terraChainID")
-		}
-		if *terraContract == "" {
-			logger.Fatal("Please specify --terraContract")
-		}
-		if *terraKeyPath == "" {
-			logger.Fatal("Please specify --terraKey")
-		}
-	}
-
 	ethContractAddr := eth_common.HexToAddress(*ethContract)
 	solBridgeAddress, err := solana_types.PublicKeyFromBase58(*solanaBridgeAddress)
 	if err != nil {
@@ -342,12 +310,21 @@ func runBridge(cmd *cobra.Command, args []string) {
 	// Inbound observations
 	obsvC := make(chan *gossipv1.SignedObservation, 50)
 
+	// Inbound observation requests from the p2p service (for all chains)
+	obsvReqC := make(chan *gossipv1.ObservationRequest, 50)
+
+	// Outbound observation requests
+	obsvReqSendC := make(chan *gossipv1.ObservationRequest)
+
 	// VAAs to submit to Solana
 	solanaVaaC := make(chan *vaa.VAA)
 
 	// Injected VAAs (manually generated rather than created via observation)
 	injectC := make(chan *vaa.VAA)
 
+	// Guardian set state managed by processor
+	gst := common.NewGuardianSetState()
+
 	// Load p2p private key
 	var priv crypto.PrivKey
 	if *unsafeDevMode {
@@ -363,19 +340,7 @@ func runBridge(cmd *cobra.Command, args []string) {
 		}
 	}
 
-	// Load Terra fee payer key
-	var terraFeePayer string
-	if *terraSupport {
-		if *unsafeDevMode {
-			terra.WriteDevnetKey(*terraKeyPath)
-		}
-		terraFeePayer, err = terra.ReadKey(*terraKeyPath)
-		if err != nil {
-			logger.Fatal("Failed to load Terra fee payer key", zap.Error(err))
-		}
-	}
-
-	adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC)
+	adminService, err := adminServiceRunnable(logger, *adminSocketPath, injectC, obsvReqSendC, gst)
 	if err != nil {
 		logger.Fatal("failed to create admin service socket", zap.Error(err))
 	}
@@ -383,24 +348,26 @@ func runBridge(cmd *cobra.Command, args []string) {
 	// Run supervisor.
 	supervisor.New(rootCtx, logger, func(ctx context.Context) error {
 		if err := supervisor.Run(ctx, "p2p", p2p.Run(
-			obsvC, sendC, priv, *p2pPort, *p2pNetworkID, *p2pBootstrap, *nodeName, rootCtxCancel)); err != nil {
+			obsvC,
+			sendC,
+			obsvReqC,
+			obsvReqSendC,
+			priv,
+			gk,
+			gst,
+			*p2pPort,
+			*p2pNetworkID,
+			*p2pBootstrap,
+			*nodeName,
+			rootCtxCancel)); err != nil {
 			return err
 		}
 
 		if err := supervisor.Run(ctx, "ethwatch",
-			ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, lockC, setC).Run); err != nil {
+			ethereum.NewEthBridgeWatcher(*ethRPC, ethContractAddr, *ethConfirmations, lockC, setC, obsvReqC).Run); err != nil {
 			return err
 		}
 
-		// Start Terra watcher only if configured
-		if *terraSupport {
-			logger.Info("Starting Terra watcher")
-			if err := supervisor.Run(ctx, "terrawatch",
-				terra.NewTerraBridgeWatcher(*terraWS, *terraLCD, *terraContract, lockC, setC).Run); err != nil {
-				return err
-			}
-		}
-
 		if err := supervisor.Run(ctx, "solvaa",
 			solana.NewSolanaVAASubmitter(*agentRPC, solanaVaaC, false).Run); err != nil {
 			return err
@@ -420,14 +387,10 @@ func runBridge(cmd *cobra.Command, args []string) {
 			solanaVaaC,
 			injectC,
 			gk,
+			gst,
 			*unsafeDevMode,
 			*devNumGuardians,
 			*ethRPC,
-			*terraSupport,
-			*terraLCD,
-			*terraChainID,
-			*terraContract,
-			terraFeePayer,
 		)
 		if err := supervisor.Run(ctx, "processor", p.Run); err != nil {
 			return err

+ 28 - 0
node/cmd/guardiand/logging.go

@@ -0,0 +1,28 @@
+package guardiand
+
+import (
+	"go.uber.org/zap/buffer"
+	"go.uber.org/zap/zapcore"
+	"unicode"
+)
+
+type consoleEncoder struct {
+	zapcore.Encoder
+}
+
+func (e consoleEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
+	buf, err := e.Encoder.EncodeEntry(entry, fields)
+	if err != nil {
+		buf.Free()
+		return nil, err
+	}
+
+	b := buf.Bytes()
+	for i := range b {
+		if unicode.IsControl(rune(b[i])) && !unicode.IsSpace(rune(b[i])) {
+			b[i] = '\x1A' // Substitute character
+		}
+	}
+
+	return buf, nil
+}

+ 13 - 151
node/e2e/e2e_test.go

@@ -2,20 +2,21 @@ package e2e
 
 import (
 	"context"
+	"os"
 	"testing"
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/abi/bind"
-	"github.com/mr-tron/base58"
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/certusone/wormhole/node/pkg/devnet"
-	"github.com/certusone/wormhole/node/pkg/ethereum"
-	"github.com/certusone/wormhole/node/pkg/vaa"
 	"github.com/ethereum/go-ethereum/ethclient"
 )
 
-func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.TransactOpts, *TerraClient) {
+// Run in a remote Tilt env:
+//   ETH_RPC=http://<bind IP>:8545 CGO_ENABLED=0 go test ./... -v
+
+func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.TransactOpts) {
 	// List of pods we need in a ready state before we can run tests.
 	want := []string{
 		// Our test guardian set.
@@ -29,9 +30,6 @@ func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.Transa
 		// Connected chains
 		"solana-devnet-0",
 
-		"terra-terrad-0",
-		"terra-lcd-0",
-
 		"eth-devnet-0",
 	}
 
@@ -45,20 +43,19 @@ func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.Transa
 		t.Fatal(ctx.Err())
 	}
 
+	ethRPC := devnet.GanacheRPCURL
+	if env := os.Getenv("ETH_RPC"); env != "" {
+		ethRPC = env
+	}
+
 	// Ethereum client.
-	ec, err := ethclient.Dial(devnet.GanacheRPCURL)
+	ec, err := ethclient.Dial(ethRPC)
 	if err != nil {
 		t.Fatalf("dialing devnet eth rpc failed: %v", err)
 	}
 	kt := devnet.GetKeyedTransactor(context.Background())
 
-	// Terra client
-	tc, err := NewTerraClient()
-	if err != nil {
-		t.Fatalf("creating devnet terra client failed: %v", err)
-	}
-
-	return c, ec, kt, tc
+	return c, ec, kt
 }
 
 // Careful about parallel tests - accounts on some chains like Ethereum cannot be
@@ -66,7 +63,7 @@ func setup(t *testing.T) (*kubernetes.Clientset, *ethclient.Client, *bind.Transa
 // Either use different Ethereum account, or do not run Ethereum tests in parallel.
 
 func TestEndToEnd_SOL_ETH(t *testing.T) {
-	c, ec, kt, _ := setup(t)
+	c, ec, kt := setup(t)
 
 	t.Run("[SOL] Native -> [ETH] Wrapped", func(t *testing.T) {
 		testSolanaLockup(t, context.Background(), ec, c,
@@ -125,138 +122,3 @@ func TestEndToEnd_SOL_ETH(t *testing.T) {
 		)
 	})
 }
-
-func TestEndToEnd_SOL_Terra(t *testing.T) {
-	c, _, _, tc := setup(t)
-
-	t.Run("[Terra] Native -> [SOL] Wrapped", func(t *testing.T) {
-		testTerraLockup(t, context.Background(), tc, c,
-			// Source CW20 token
-			devnet.TerraTokenAddress,
-			// Destination SPL token account
-			devnet.SolanaExampleWrappedCWTokenOwningAccount,
-			// Amount
-			2*devnet.TerraDefaultPrecision,
-			// Same precision - same amount, no precision gained.
-			0,
-		)
-	})
-
-	t.Run("[SOL] Wrapped -> [Terra] Native", func(t *testing.T) {
-		testSolanaToTerraLockup(t, context.Background(), c,
-			// Source SPL account
-			devnet.SolanaExampleWrappedCWTokenOwningAccount,
-			// Source SPL token
-			devnet.SolanaExampleWrappedCWToken,
-			// Wrapped
-			false,
-			// Amount of SPL token value to transfer.
-			2*devnet.TerraDefaultPrecision,
-			// Same precision - same amount, no precision gained.
-			0,
-		)
-	})
-
-	t.Run("[SOL] Native -> [Terra] Wrapped", func(t *testing.T) {
-		testSolanaToTerraLockup(t, context.Background(), c,
-			// Source SPL account
-			devnet.SolanaExampleTokenOwningAccount,
-			// Source SPL token
-			devnet.SolanaExampleToken,
-			// Native
-			true,
-			// Amount of SPL token value to transfer.
-			50*devnet.SolanaDefaultPrecision,
-			// Same precision - same amount, no precision gained.
-			0,
-		)
-	})
-
-	t.Run("[Terra] Wrapped -> [SOL] Native", func(t *testing.T) {
-
-		tokenSlice, err := base58.Decode(devnet.SolanaExampleToken)
-		if err != nil {
-			t.Fatal(err)
-		}
-		wrappedAsset, err := waitTerraAsset(t, context.Background(), devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice)
-
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		testTerraLockup(t, context.Background(), tc, c,
-			// Source wrapped token
-			wrappedAsset,
-			// Destination SPL token account
-			devnet.SolanaExampleTokenOwningAccount,
-			// Amount of Terra token value to transfer.
-			50*devnet.SolanaDefaultPrecision,
-			// Same precision
-			0,
-		)
-	})
-}
-
-func TestEndToEnd_ETH_Terra(t *testing.T) {
-	_, ec, kt, tc := setup(t)
-
-	t.Run("[Terra] Native -> [ETH] Wrapped", func(t *testing.T) {
-		testTerraToEthLockup(t, context.Background(), tc, ec,
-			// Source CW20 token
-			devnet.TerraTokenAddress,
-			// Destination ETH token
-			devnet.GanacheExampleERC20WrappedTerra,
-			// Amount
-			2*devnet.TerraDefaultPrecision,
-			// Same precision - same amount, no precision gained.
-			0,
-		)
-	})
-
-	t.Run("[ETH] Wrapped -> [Terra] Native", func(t *testing.T) {
-		testEthereumToTerraLockup(t, context.Background(), ec, kt,
-			// Source Ethereum token
-			devnet.GanacheExampleERC20WrappedTerra,
-			// Wrapped
-			false,
-			// Amount of Ethereum token value to transfer.
-			2*devnet.TerraDefaultPrecision,
-			// Same precision
-			0,
-		)
-	})
-
-	t.Run("[ETH] Native -> [Terra] Wrapped", func(t *testing.T) {
-		testEthereumToTerraLockup(t, context.Background(), ec, kt,
-			// Source Ethereum token
-			devnet.GanacheExampleERC20Token,
-			// Native
-			true,
-			// Amount of Ethereum token value to transfer.
-			0.000000012*devnet.ERC20DefaultPrecision,
-			// We lose 9 digits of precision on this path, as the default ERC20 token has 10**18 precision.
-			9,
-		)
-	})
-
-	t.Run("[Terra] Wrapped -> [ETH] Native", func(t *testing.T) {
-
-		paddedTokenAddress := ethereum.PadAddress(devnet.GanacheExampleERC20Token)
-		wrappedAsset, err := waitTerraAsset(t, context.Background(), devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:])
-
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		testTerraToEthLockup(t, context.Background(), tc, ec,
-			// Source wrapped token
-			wrappedAsset,
-			// Destination ETH token
-			devnet.GanacheExampleERC20Token,
-			// Amount of Terra token value to transfer.
-			0.000000012*1e9, // 10**9 because default ETH precision is 18 and we lost 9 digits on wrapping
-			// We gain 9 digits of precision on Eth.
-			9,
-		)
-	})
-}

+ 1 - 84
node/e2e/eth.go

@@ -2,21 +2,19 @@ package e2e
 
 import (
 	"context"
-	"encoding/hex"
 	"math"
 	"math/big"
+	"math/rand"
 	"testing"
 	"time"
 
 	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/ethclient"
-	"github.com/tendermint/tendermint/libs/rand"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/certusone/wormhole/node/pkg/devnet"
-	"github.com/certusone/wormhole/node/pkg/ethereum"
 	"github.com/certusone/wormhole/node/pkg/ethereum/abi"
 	"github.com/certusone/wormhole/node/pkg/ethereum/erc20"
 	"github.com/certusone/wormhole/node/pkg/vaa"
@@ -107,84 +105,3 @@ func testEthereumLockup(t *testing.T, ctx context.Context, ec *ethclient.Client,
 	// Source account decreases by the full amount.
 	waitEthBalance(t, ctx, token, beforeErc20, -int64(amount))
 }
-
-func testEthereumToTerraLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, kt *bind.TransactOpts,
-	tokenAddr common.Address, isNative bool, amount int64, precisionLoss int) {
-
-	// Bridge client
-	ethBridge, err := abi.NewAbi(devnet.GanacheBridgeContractAddress, ec)
-	if err != nil {
-		panic(err)
-	}
-
-	// Source token client
-	token, err := erc20.NewErc20(tokenAddr, ec)
-	if err != nil {
-		panic(err)
-	}
-
-	// Store balance of source ERC20 token
-	beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
-	if err != nil {
-		beforeErc20 = new(big.Int)
-		t.Log(err) // account may not yet exist, defaults to 0
-	}
-	t.Logf("ERC20 balance: %v", beforeErc20)
-
-	// Store balance of destination CW20 token
-	paddedTokenAddress := ethereum.PadAddress(tokenAddr)
-	var terraToken string
-	if isNative {
-		terraToken, err = getAssetAddress(ctx, devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:])
-		if err != nil {
-			t.Log(err)
-		}
-	} else {
-		terraToken = devnet.TerraTokenAddress
-	}
-
-	// Get balance if deployed
-	beforeCw20, err := getTerraBalance(ctx, terraToken)
-	if err != nil {
-		beforeCw20 = new(big.Int)
-		t.Log(err) // account may not yet exist, defaults to 0
-	}
-	t.Logf("CW20 balance: %v", beforeCw20)
-
-	// Send lockup
-	dstAddress, err := hex.DecodeString(devnet.TerraMainTestAddressHex)
-	if err != nil {
-		t.Fatal(err)
-	}
-	var dstAddressBytes [32]byte
-	copy(dstAddressBytes[:], dstAddress)
-	tx, err := ethBridge.LockAssets(kt,
-		// asset address
-		tokenAddr,
-		// token amount
-		new(big.Int).SetInt64(amount),
-		// recipient address on target chain
-		dstAddressBytes,
-		// target chain
-		vaa.ChainIDTerra,
-		// random nonce
-		rand.Uint32(),
-		// refund dust?
-		false,
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	t.Logf("sent lockup tx: %v", tx.Hash().Hex())
-
-	// Destination account increases by the full amount.
-	if isNative {
-		waitTerraUnknownBalance(t, ctx, devnet.TerraBridgeAddress, vaa.ChainIDEthereum, paddedTokenAddress[:], beforeCw20, int64(float64(amount)/math.Pow10(precisionLoss)))
-	} else {
-		waitTerraBalance(t, ctx, devnet.TerraTokenAddress, beforeCw20, int64(float64(amount)/math.Pow10(precisionLoss)))
-	}
-
-	// Source account decreases by the full amount.
-	waitEthBalance(t, ctx, token, beforeErc20, -int64(amount))
-}

+ 2 - 67
node/e2e/solana.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"math"
 	"math/big"
+	"math/rand"
 	"regexp"
 	"strconv"
 	"testing"
@@ -12,8 +13,6 @@ import (
 
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/ethclient"
-	"github.com/mr-tron/base58"
-	"github.com/tendermint/tendermint/libs/rand"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
 
@@ -104,7 +103,7 @@ func testSolanaLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, c
 			// Destination chain ID.
 			strconv.Itoa(vaa.ChainIDEthereum),
 			// Random nonce.
-			strconv.Itoa(int(rand.Uint16())),
+			strconv.Itoa(int(rand.Uint32())),
 			// Destination account on Ethereum
 			devnet.GanacheClientDefaultAccountAddress.Hex()[2:],
 		})
@@ -118,67 +117,3 @@ func testSolanaLockup(t *testing.T, ctx context.Context, ec *ethclient.Client, c
 	// Source account decreases by full amount.
 	waitSPLBalance(t, ctx, c, sourceAcct, beforeSPL, -int64(amount))
 }
-
-func testSolanaToTerraLockup(t *testing.T, ctx context.Context, c *kubernetes.Clientset,
-	sourceAcct string, tokenAddr string, isNative bool, amount int, precisionGain int) {
-
-	tokenSlice, err := base58.Decode(tokenAddr)
-	if err != nil {
-		t.Fatal(err)
-	}
-	var terraToken string
-	if isNative {
-		terraToken, err = getAssetAddress(ctx, devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice)
-		if err != nil {
-			t.Log(err)
-		}
-	} else {
-		terraToken = devnet.TerraTokenAddress
-	}
-
-	// Get balance if deployed
-	beforeCw20, err := getTerraBalance(ctx, terraToken)
-	if err != nil {
-		beforeCw20 = new(big.Int)
-		t.Log(err) // account may not yet exist, defaults to 0
-	}
-	t.Logf("CW20 balance: %v", beforeCw20)
-
-	// Store balance of source SPL token
-	beforeSPL, err := getSPLBalance(ctx, c, sourceAcct)
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Logf("SPL balance: %d", beforeSPL)
-
-	_, err = executeCommandInPod(ctx, c, "solana-devnet-0", "setup",
-		[]string{"cli", "lock",
-			// Address of the Wormhole bridge.
-			devnet.SolanaBridgeContract,
-			// Account which holds the SPL tokens to be sent.
-			sourceAcct,
-			// The SPL token.
-			tokenAddr,
-			// Token amount.
-			strconv.Itoa(amount),
-			// Destination chain ID.
-			strconv.Itoa(vaa.ChainIDTerra),
-			// Random nonce.
-			strconv.Itoa(int(rand.Uint16())),
-			// Destination account on Terra
-			devnet.TerraMainTestAddressHex,
-		})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	// Source account decreases by full amount.
-	waitSPLBalance(t, ctx, c, sourceAcct, beforeSPL, -int64(amount))
-
-	// Destination account increases by the full amount.
-	if isNative {
-		waitTerraUnknownBalance(t, ctx, devnet.TerraBridgeAddress, vaa.ChainIDSolana, tokenSlice, beforeCw20, int64(float64(amount)*math.Pow10(precisionGain)))
-	} else {
-		waitTerraBalance(t, ctx, devnet.TerraTokenAddress, beforeCw20, int64(float64(amount)*math.Pow10(precisionGain)))
-	}
-}

+ 0 - 391
node/e2e/terra.go

@@ -1,391 +0,0 @@
-package e2e
-
-import (
-	"context"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"math"
-	"math/big"
-	"net/http"
-	"net/url"
-	"testing"
-	"time"
-
-	"github.com/certusone/wormhole/node/pkg/devnet"
-	"github.com/certusone/wormhole/node/pkg/ethereum"
-	"github.com/certusone/wormhole/node/pkg/ethereum/erc20"
-	"github.com/certusone/wormhole/node/pkg/vaa"
-	"github.com/ethereum/go-ethereum/common"
-	"github.com/ethereum/go-ethereum/ethclient"
-	"github.com/tendermint/tendermint/libs/rand"
-	"github.com/terra-project/terra.go/client"
-	"github.com/terra-project/terra.go/key"
-	"github.com/terra-project/terra.go/msg"
-	"github.com/terra-project/terra.go/tx"
-	"github.com/tidwall/gjson"
-	"k8s.io/apimachinery/pkg/util/wait"
-	"k8s.io/client-go/kubernetes"
-)
-
-type lockAssetsMsg struct {
-	Params lockAssetsParams `json:"lock_assets"`
-}
-
-type increaseAllowanceMsg struct {
-	Params increaseAllowanceParams `json:"increase_allowance"`
-}
-
-type lockAssetsParams struct {
-	Asset       string `json:"asset"`
-	Amount      string `json:"amount"`
-	Recipient   []byte `json:"recipient"`
-	TargetChain uint8  `json:"target_chain"`
-	Nonce       uint32 `json:"nonce"`
-}
-
-type increaseAllowanceParams struct {
-	Spender string `json:"spender"`
-	Amount  string `json:"amount"`
-}
-
-// TerraClient encapsulates Terra LCD client and fee payer signing address
-type TerraClient struct {
-	lcdClient client.LCDClient
-	address   msg.AccAddress
-}
-
-const (
-	feeAmount       = 10000
-	feeDenomination = "uluna"
-)
-
-func (tc TerraClient) lockAssets(t *testing.T, ctx context.Context, token string, amount *big.Int, recipient [32]byte, targetChain uint8, nonce uint32) (*client.TxResponse, error) {
-	bridgeContract, err := msg.AccAddressFromBech32(devnet.TerraBridgeAddress)
-	if err != nil {
-		return nil, err
-	}
-
-	tokenContract, err := msg.AccAddressFromBech32(token)
-	if err != nil {
-		return nil, err
-	}
-
-	// Create tx
-	increaseAllowanceCall, err := json.Marshal(increaseAllowanceMsg{
-		Params: increaseAllowanceParams{
-			Spender: devnet.TerraBridgeAddress,
-			Amount:  amount.String(),
-		}})
-
-	if err != nil {
-		return nil, err
-	}
-
-	lockAssetsCall, err := json.Marshal(lockAssetsMsg{
-		Params: lockAssetsParams{
-			Asset:       token,
-			Amount:      amount.String(),
-			Recipient:   recipient[:],
-			TargetChain: targetChain,
-			Nonce:       nonce,
-		}})
-
-	if err != nil {
-		return nil, err
-	}
-
-	t.Logf("increaseAllowanceCall\n %s", increaseAllowanceCall)
-	t.Logf("lockAssetsCall\n %s", lockAssetsCall)
-
-	executeIncreaseAllowance := msg.NewExecuteContract(tc.address, tokenContract, increaseAllowanceCall, msg.NewCoins())
-	executeLockAssets := msg.NewExecuteContract(tc.address, bridgeContract, lockAssetsCall, msg.NewCoins(msg.NewInt64Coin(feeDenomination, feeAmount)))
-
-	transaction, err := tc.lcdClient.CreateAndSignTx(ctx, client.CreateTxOptions{
-		Msgs: []msg.Msg{
-			executeIncreaseAllowance,
-			executeLockAssets,
-		},
-		Fee: tx.StdFee{
-			Gas:    msg.NewInt(0),
-			Amount: msg.NewCoins(),
-		},
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	// Broadcast
-	return tc.lcdClient.Broadcast(ctx, transaction)
-}
-
-// NewTerraClient creates new TerraClient instance to work
-func NewTerraClient() (*TerraClient, error) {
-	// Derive Raw Private Key
-	privKey, err := key.DerivePrivKey(devnet.TerraFeePayerKey, key.CreateHDPath(0, 0))
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate StdPrivKey
-	tmKey, err := key.StdPrivKeyGen(privKey)
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate Address from Public Key
-	address := msg.AccAddress(tmKey.PubKey().Address())
-
-	// Terra client
-	lcdClient := client.NewLCDClient(
-		devnet.TerraLCDURL,
-		devnet.TerraChainID,
-		msg.NewDecCoinFromDec("uusd", msg.NewDecFromIntWithPrec(msg.NewInt(15), 2)), // 0.15uusd
-		msg.NewDecFromIntWithPrec(msg.NewInt(15), 1), tmKey, time.Second*15,
-	)
-
-	return &TerraClient{
-		lcdClient: *lcdClient,
-		address:   address,
-	}, nil
-}
-
-func getTerraBalance(ctx context.Context, token string) (*big.Int, error) {
-	json, err := terraQuery(ctx, token, fmt.Sprintf("{\"balance\":{\"address\":\"%s\"}}", devnet.TerraMainTestAddress))
-	if err != nil {
-		return nil, err
-	}
-	balance := gjson.Get(json, "result.balance").String()
-	parsed, success := new(big.Int).SetString(balance, 10)
-
-	if !success {
-		return nil, fmt.Errorf("cannot parse balance: %s", balance)
-	}
-
-	return parsed, nil
-}
-
-func getAssetAddress(ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
-	json, err := terraQuery(ctx, contract, fmt.Sprintf("{\"wrapped_registry\":{\"chain\":%d,\"address\":\"%s\"}}",
-		chain,
-		base64.StdEncoding.EncodeToString(asset)))
-	if err != nil {
-		return "", err
-	}
-	return gjson.Get(json, "result.address").String(), nil
-}
-
-func terraQuery(ctx context.Context, contract string, query string) (string, error) {
-
-	requestURL := fmt.Sprintf("%s/wasm/contracts/%s/store?query_msg=%s", devnet.TerraLCDURL, contract, url.QueryEscape(query))
-
-	req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
-	if err != nil {
-		return "", fmt.Errorf("http request error: %w", err)
-	}
-
-	client := &http.Client{
-		Timeout: time.Second * 15,
-	}
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", fmt.Errorf("http execution error: %w", err)
-	}
-
-	body, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return "", fmt.Errorf("http read error: %w", err)
-	}
-
-	return string(body), nil
-}
-
-// waitTerraAsset waits for asset contract to be deployed on terra
-func waitTerraAsset(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte) (string, error) {
-	ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
-	defer cancel()
-
-	assetAddress := ""
-
-	err := wait.PollUntil(3*time.Second, func() (bool, error) {
-
-		address, err := getAssetAddress(ctx, contract, chain, asset)
-		if err != nil {
-			t.Log(err)
-			return false, nil
-		}
-
-		// Check the case if request was successful, but asset address is not yet in the registry
-		if address == "" {
-			return false, nil
-		}
-		t.Logf("Returning asset: %s", address)
-
-		assetAddress = address
-		return true, nil
-	}, ctx.Done())
-
-	if err != nil {
-		t.Error(err)
-	}
-	return assetAddress, err
-}
-
-// waitTerraBalance waits for target account before to increase.
-func waitTerraBalance(t *testing.T, ctx context.Context, token string, before *big.Int, target int64) {
-	ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
-	defer cancel()
-
-	err := wait.PollUntil(1*time.Second, func() (bool, error) {
-
-		after, err := getTerraBalance(ctx, token)
-		if err != nil {
-			return false, err
-		}
-
-		d := new(big.Int).Sub(after, before)
-		t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
-
-		if after.Cmp(before) != 0 {
-			if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
-				t.Errorf("expected CW20 delta of %v, got: %v", target, d)
-			}
-			return true, nil
-		}
-		return false, nil
-	}, ctx.Done())
-
-	if err != nil {
-		t.Error(err)
-	}
-}
-
-func waitTerraUnknownBalance(t *testing.T, ctx context.Context, contract string, chain uint8, asset []byte, before *big.Int, target int64) {
-
-	token, err := waitTerraAsset(t, ctx, contract, chain, asset)
-	if err != nil {
-		return
-	}
-
-	ctx, cancel := context.WithTimeout(ctx, 90*time.Second)
-	defer cancel()
-
-	err = wait.PollUntil(3*time.Second, func() (bool, error) {
-
-		after, err := getTerraBalance(ctx, token)
-		if err != nil {
-			return false, err
-		}
-
-		d := new(big.Int).Sub(after, before)
-		t.Logf("CW20 balance after: %d -> %d, delta %d", before, after, d)
-
-		if after.Cmp(before) != 0 {
-			if d.Cmp(new(big.Int).SetInt64(target)) != 0 {
-				t.Errorf("expected CW20 delta of %v, got: %v", target, d)
-			}
-			return true, nil
-		}
-		return false, nil
-	}, ctx.Done())
-
-	if err != nil {
-		t.Error(err)
-	}
-}
-
-func testTerraLockup(t *testing.T, ctx context.Context, tc *TerraClient,
-	c *kubernetes.Clientset, token string, destination string, amount int64, precisionLoss int) {
-
-	// Store balance of source CW20 token
-	beforeCw20, err := getTerraBalance(ctx, token)
-	if err != nil {
-		t.Log(err) // account may not yet exist, defaults to 0
-	}
-	t.Logf("CW20 balance: %v", beforeCw20)
-
-	// Store balance of destination SPL token
-	beforeSPL, err := getSPLBalance(ctx, c, destination)
-	if err != nil {
-		t.Fatal(err)
-	}
-	t.Logf("SPL balance: %d", beforeSPL)
-
-	// Send lockup
-	tx, err := tc.lockAssets(
-		t, ctx,
-		// asset address
-		token,
-		// token amount
-		new(big.Int).SetInt64(amount),
-		// recipient address on target chain
-		devnet.MustBase58ToEthAddress(destination),
-		// target chain
-		vaa.ChainIDSolana,
-		// random nonce
-		rand.Uint32(),
-	)
-	if err != nil {
-		t.Error(err)
-	}
-
-	t.Logf("sent lockup tx: %s", tx.TxHash)
-
-	// Destination account increases by full amount.
-	waitSPLBalance(t, ctx, c, destination, beforeSPL, int64(float64(amount)/math.Pow10(precisionLoss)))
-
-	// Source account decreases by the full amount.
-	waitTerraBalance(t, ctx, token, beforeCw20, -int64(amount))
-}
-
-func testTerraToEthLockup(t *testing.T, ctx context.Context, tc *TerraClient,
-	ec *ethclient.Client, tokenAddr string, destination common.Address, amount int64, precisionGain int) {
-
-	token, err := erc20.NewErc20(destination, ec)
-	if err != nil {
-		panic(err)
-	}
-
-	// Store balance of source CW20 token
-	beforeCw20, err := getTerraBalance(ctx, tokenAddr)
-	if err != nil {
-		t.Log(err) // account may not yet exist, defaults to 0
-		beforeCw20 = new(big.Int)
-	}
-	t.Logf("CW20 balance: %v", beforeCw20)
-
-	/// Store balance of wrapped destination token
-	beforeErc20, err := token.BalanceOf(nil, devnet.GanacheClientDefaultAccountAddress)
-	if err != nil {
-		t.Log(err) // account may not yet exist, defaults to 0
-		beforeErc20 = new(big.Int)
-	}
-	t.Logf("ERC20 balance: %v", beforeErc20)
-
-	// Send lockup
-	tx, err := tc.lockAssets(
-		t, ctx,
-		// asset address
-		tokenAddr,
-		// token amount
-		new(big.Int).SetInt64(amount),
-		// recipient address on target chain
-		ethereum.PadAddress(devnet.GanacheClientDefaultAccountAddress),
-		// target chain
-		vaa.ChainIDEthereum,
-		// random nonce
-		rand.Uint32(),
-	)
-	if err != nil {
-		t.Error(err)
-	}
-
-	t.Logf("sent lockup tx: %s", tx.TxHash)
-
-	// Destination account increases by full amount.
-	waitEthBalance(t, ctx, token, beforeErc20, int64(float64(amount)*math.Pow10(precisionGain)))
-
-	// Source account decreases by the full amount.
-	waitTerraBalance(t, ctx, tokenAddr, beforeCw20, -int64(amount))
-}

+ 72 - 99
node/go.mod

@@ -2,80 +2,101 @@ module github.com/certusone/wormhole/node
 
 go 1.17
 
+require (
+	github.com/cenkalti/backoff/v4 v4.1.1
+	github.com/davecgh/go-spew v1.1.1
+	github.com/ethereum/go-ethereum v1.10.6
+	github.com/gagliardetto/solana-go v0.3.5-0.20210727215348-0cf016734976
+	github.com/golang/glog v0.0.0-20210429001901-424d2337a529
+	github.com/gorilla/mux v1.8.0
+	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
+	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
+	github.com/ipfs/go-log/v2 v2.3.0
+	github.com/libp2p/go-libp2p v0.14.4
+	github.com/libp2p/go-libp2p-connmgr v0.2.4
+	github.com/libp2p/go-libp2p-core v0.8.6
+	github.com/libp2p/go-libp2p-kad-dht v0.12.2
+	github.com/libp2p/go-libp2p-pubsub v0.5.0
+	github.com/libp2p/go-libp2p-quic-transport v0.11.2
+	github.com/libp2p/go-libp2p-tls v0.1.3
+	github.com/miguelmota/go-ethereum-hdwallet v0.1.0
+	github.com/mitchellh/go-homedir v1.1.0
+	github.com/mr-tron/base58 v1.2.0
+	github.com/multiformats/go-multiaddr v0.3.3
+	github.com/prometheus/client_golang v1.10.0
+	github.com/spf13/cobra v1.1.1
+	github.com/spf13/pflag v1.0.5
+	github.com/spf13/viper v1.7.1
+	github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969
+	github.com/stretchr/testify v1.7.0
+	go.uber.org/zap v1.16.0
+	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
+	golang.org/x/sys v0.0.0-20210917161153-d61c044b1678
+	google.golang.org/grpc v1.40.0
+	google.golang.org/protobuf v1.27.1
+	k8s.io/api v0.23.3
+	k8s.io/apimachinery v0.23.3
+	k8s.io/client-go v0.23.3
+)
+
 require (
 	contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect
-	github.com/99designs/keyring v1.1.6 // indirect
-	github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
 	github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
 	github.com/VictoriaMetrics/fastcache v1.6.0 // indirect
-	github.com/benbjohnson/clock v1.1.0 // indirect
+	github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
+	github.com/benbjohnson/clock v1.0.3 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/bgentry/speakeasy v0.1.0 // indirect
 	github.com/blendle/zapdriver v1.3.1 // indirect
 	github.com/btcsuite/btcd v0.21.0-beta // indirect
 	github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect
 	github.com/cespare/xxhash/v2 v2.1.1 // indirect
 	github.com/cheekybits/genny v1.0.0 // indirect
-	github.com/cosmos/cosmos-sdk v0.39.2 // indirect
-	github.com/cosmos/go-bip39 v0.0.0-20200817134856-d632e0d11689 // indirect
-	github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect
-	github.com/cosmos/ledger-go v0.9.2 // indirect
-	github.com/danieljoos/wincred v1.0.2 // indirect
 	github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
 	github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea // indirect
 	github.com/dfuse-io/binary v0.0.0-20210216024852-4ae6830a495d // indirect
 	github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect
-	github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b // indirect
 	github.com/edsrzf/mmap-go v1.0.0 // indirect
 	github.com/flynn/noise v1.0.0 // indirect
 	github.com/francoispqt/gojay v1.2.13 // indirect
 	github.com/fsnotify/fsnotify v1.4.9 // indirect
 	github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
-	github.com/go-kit/kit v0.10.0 // indirect
-	github.com/go-logfmt/logfmt v0.5.0 // indirect
-	github.com/go-logr/logr v0.4.0 // indirect
+	github.com/go-logr/logr v1.2.0 // indirect
 	github.com/go-ole/go-ole v1.2.1 // indirect
 	github.com/go-stack/stack v1.8.0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/golang/snappy v0.0.3 // indirect
-	github.com/google/btree v1.0.1 // indirect
 	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa // indirect
 	github.com/google/gopacket v1.1.19 // indirect
-	github.com/google/uuid v1.3.0 // indirect
+	github.com/google/uuid v1.2.0 // indirect
 	github.com/googleapis/gnostic v0.5.5 // indirect
-	github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
-	github.com/gtank/merlin v0.1.1 // indirect
-	github.com/gtank/ristretto255 v0.1.2 // indirect
+	github.com/gorilla/websocket v1.4.2 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
 	github.com/holiman/uint256 v1.2.0 // indirect
-	github.com/huin/goupnp v1.0.2 // indirect
+	github.com/huin/goupnp v1.0.1-0.20210626160114-33cdcbb30dda // indirect
 	github.com/imdario/mergo v0.3.5 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/ipfs/go-cid v0.0.7 // indirect
 	github.com/ipfs/go-datastore v0.4.5 // indirect
 	github.com/ipfs/go-ipfs-util v0.0.2 // indirect
-	github.com/ipfs/go-ipns v0.1.2 // indirect
+	github.com/ipfs/go-ipns v0.0.2 // indirect
 	github.com/ipfs/go-log v1.0.5 // indirect
-	github.com/ipld/go-ipld-prime v0.9.0 // indirect
 	github.com/jackpal/go-nat-pmp v1.0.2 // indirect
 	github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect
 	github.com/jbenet/goprocess v0.1.4 // indirect
-	github.com/jmhodges/levigo v1.0.0 // indirect
-	github.com/json-iterator/go v1.1.11 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 // indirect
-	github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
 	github.com/klauspost/compress v1.13.1 // indirect
 	github.com/klauspost/cpuid/v2 v2.0.4 // indirect
 	github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d // indirect
-	github.com/libp2p/go-addr-util v0.1.0 // indirect
+	github.com/libp2p/go-addr-util v0.0.2 // indirect
 	github.com/libp2p/go-buffer-pool v0.0.2 // indirect
 	github.com/libp2p/go-cidranger v1.1.0 // indirect
 	github.com/libp2p/go-conn-security-multistream v0.2.1 // indirect
@@ -85,16 +106,16 @@ require (
 	github.com/libp2p/go-libp2p-autonat v0.4.2 // indirect
 	github.com/libp2p/go-libp2p-blankhost v0.2.0 // indirect
 	github.com/libp2p/go-libp2p-circuit v0.4.0 // indirect
-	github.com/libp2p/go-libp2p-discovery v0.5.1 // indirect
+	github.com/libp2p/go-libp2p-discovery v0.5.0 // indirect
 	github.com/libp2p/go-libp2p-kbucket v0.4.7 // indirect
 	github.com/libp2p/go-libp2p-mplex v0.4.1 // indirect
 	github.com/libp2p/go-libp2p-nat v0.0.6 // indirect
 	github.com/libp2p/go-libp2p-noise v0.2.0 // indirect
-	github.com/libp2p/go-libp2p-peerstore v0.2.8 // indirect
+	github.com/libp2p/go-libp2p-peerstore v0.2.7 // indirect
 	github.com/libp2p/go-libp2p-pnet v0.2.0 // indirect
 	github.com/libp2p/go-libp2p-record v0.1.3 // indirect
-	github.com/libp2p/go-libp2p-swarm v0.5.3 // indirect
-	github.com/libp2p/go-libp2p-transport-upgrader v0.4.6 // indirect
+	github.com/libp2p/go-libp2p-swarm v0.5.0 // indirect
+	github.com/libp2p/go-libp2p-transport-upgrader v0.4.2 // indirect
 	github.com/libp2p/go-libp2p-yamux v0.5.4 // indirect
 	github.com/libp2p/go-maddr-filter v0.1.0 // indirect
 	github.com/libp2p/go-mplex v0.3.0 // indirect
@@ -103,17 +124,18 @@ require (
 	github.com/libp2p/go-netroute v0.1.6 // indirect
 	github.com/libp2p/go-openssl v0.0.7 // indirect
 	github.com/libp2p/go-reuseport v0.0.2 // indirect
-	github.com/libp2p/go-reuseport-transport v0.0.5 // indirect
+	github.com/libp2p/go-reuseport-transport v0.0.4 // indirect
 	github.com/libp2p/go-sockaddr v0.1.1 // indirect
 	github.com/libp2p/go-stream-muxer-multistream v0.3.0 // indirect
-	github.com/libp2p/go-tcp-transport v0.2.7 // indirect
+	github.com/libp2p/go-tcp-transport v0.2.4 // indirect
 	github.com/libp2p/go-ws-transport v0.4.0 // indirect
 	github.com/libp2p/go-yamux/v2 v2.2.0 // indirect
 	github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
-	github.com/lucas-clemente/quic-go v0.23.0 // indirect
+	github.com/lucas-clemente/quic-go v0.21.2 // indirect
 	github.com/magiconair/properties v1.8.1 // indirect
+	github.com/marten-seemann/qtls-go1-15 v0.1.5 // indirect
 	github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
-	github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
+	github.com/marten-seemann/qtls-go1-17 v0.1.0-rc.1 // indirect
 	github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect
 	github.com/mattn/go-isatty v0.0.13 // indirect
 	github.com/mattn/go-runewidth v0.0.9 // indirect
@@ -121,21 +143,19 @@ require (
 	github.com/miekg/dns v1.1.41 // indirect
 	github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect
 	github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
-	github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
 	github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
 	github.com/minio/sha256-simd v1.0.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.14.1 // indirect
 	github.com/mitchellh/mapstructure v1.1.2 // indirect
 	github.com/moby/spdystream v0.2.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
-	github.com/modern-go/reflect2 v1.0.1 // indirect
-	github.com/mtibben/percent v0.2.1 // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/multiformats/go-base32 v0.0.3 // indirect
 	github.com/multiformats/go-base36 v0.1.0 // indirect
 	github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
 	github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect
+	github.com/multiformats/go-multiaddr-net v0.2.0 // indirect
 	github.com/multiformats/go-multibase v0.0.3 // indirect
-	github.com/multiformats/go-multicodec v0.2.0 // indirect
 	github.com/multiformats/go-multihash v0.0.15 // indirect
 	github.com/multiformats/go-multistream v0.2.2 // indirect
 	github.com/multiformats/go-varint v0.0.6 // indirect
@@ -147,27 +167,20 @@ require (
 	github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/common v0.18.0 // indirect
 	github.com/prometheus/procfs v0.6.0 // indirect
 	github.com/prometheus/tsdb v0.7.1 // indirect
-	github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
 	github.com/rjeczalik/notify v0.9.1 // indirect
 	github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
 	github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
 	github.com/spf13/afero v1.2.2 // indirect
 	github.com/spf13/cast v1.3.0 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
-	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.2.0 // indirect
 	github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 // indirect
-	github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
-	github.com/tendermint/btcd v0.1.1 // indirect
-	github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect
-	github.com/tendermint/go-amino v0.15.1 // indirect
-	github.com/tendermint/tm-db v0.5.1 // indirect
 	github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect
+	github.com/tidwall/gjson v1.8.1 // indirect
 	github.com/tidwall/match v1.0.3 // indirect
 	github.com/tidwall/pretty v1.1.0 // indirect
 	github.com/tklauser/go-sysconf v0.3.5 // indirect
@@ -176,70 +189,30 @@ require (
 	github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
 	github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7 // indirect
 	github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee // indirect
-	github.com/ybbus/jsonrpc v2.1.2+incompatible // indirect
-	github.com/zondax/hid v0.9.0 // indirect
-	go.etcd.io/bbolt v1.3.3 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
-	golang.org/x/mod v0.4.2 // indirect
-	golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect
-	golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 // indirect
+	go.uber.org/ratelimit v0.2.0 // indirect
+	golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
+	golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
+	golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 // indirect
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
-	golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
+	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
-	golang.org/x/tools v0.1.3 // indirect
-	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+	golang.org/x/tools v0.1.5 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
+	google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	gopkg.in/ini.v1 v1.51.0 // indirect
 	gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
-	k8s.io/klog/v2 v2.9.0 // indirect
-	k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 // indirect
-	sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
+	k8s.io/klog/v2 v2.30.0 // indirect
+	k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
+	k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
+	sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
 	sigs.k8s.io/yaml v1.2.0 // indirect
 )
-
-require (
-	github.com/cenkalti/backoff/v4 v4.1.1
-	github.com/davecgh/go-spew v1.1.1
-	github.com/dfuse-io/solana-go v0.2.0
-	github.com/ethereum/go-ethereum v1.10.6
-	github.com/golang/glog v0.0.0-20210429001901-424d2337a529
-	github.com/golang/protobuf v1.5.2
-	github.com/gorilla/mux v1.7.4
-	github.com/gorilla/websocket v1.4.2
-	github.com/ipfs/go-log/v2 v2.3.0
-	github.com/libp2p/go-libp2p v0.14.4
-	github.com/libp2p/go-libp2p-connmgr v0.2.4
-	github.com/libp2p/go-libp2p-core v0.8.6
-	github.com/libp2p/go-libp2p-kad-dht v0.12.2
-	github.com/libp2p/go-libp2p-pubsub v0.5.0
-	github.com/libp2p/go-libp2p-quic-transport v0.11.2
-	github.com/libp2p/go-libp2p-tls v0.1.3
-	github.com/miguelmota/go-ethereum-hdwallet v0.1.0
-	github.com/mitchellh/go-homedir v1.1.0
-	github.com/mr-tron/base58 v1.2.0
-	github.com/multiformats/go-multiaddr v0.3.3
-	github.com/prometheus/client_golang v1.10.0
-	github.com/spf13/cobra v1.1.1
-	github.com/spf13/viper v1.7.1
-	github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969
-	github.com/stretchr/testify v1.7.0
-	github.com/tendermint/tendermint v0.33.9
-	github.com/terra-project/terra.go v1.0.1-0.20210129055710-7a586e5e027a
-	github.com/tidwall/gjson v1.8.1
-	go.uber.org/zap v1.16.0
-	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
-	golang.org/x/sys v0.0.0-20210616094352-59db8d763f22
-	google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced
-	google.golang.org/grpc v1.38.0
-	google.golang.org/protobuf v1.26.0
-	k8s.io/api v0.22.1
-	k8s.io/apimachinery v0.22.1
-	k8s.io/client-go v0.22.1
-)

Разлика између датотеке није приказан због своје велике величине
+ 128 - 179
node/go.sum


+ 30 - 0
node/pkg/common/grpc.go

@@ -0,0 +1,30 @@
+package common
+
+import (
+	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
+	grpc_zap "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap"
+	grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags"
+	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
+	"go.uber.org/zap"
+	"google.golang.org/grpc"
+)
+
+func NewInstrumentedGRPCServer(logger *zap.Logger) *grpc.Server {
+	server := grpc.NewServer(
+		grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
+			grpc_ctxtags.StreamServerInterceptor(),
+			grpc_prometheus.StreamServerInterceptor,
+			grpc_zap.StreamServerInterceptor(logger),
+		)),
+		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
+			grpc_ctxtags.UnaryServerInterceptor(),
+			grpc_prometheus.UnaryServerInterceptor,
+			grpc_zap.UnaryServerInterceptor(logger),
+		)),
+	)
+
+	grpc_prometheus.EnableHandlingTimeHistogram()
+	grpc_prometheus.Register(server)
+
+	return server
+}

+ 113 - 2
node/pkg/common/guardianset.go

@@ -1,16 +1,32 @@
 package common
 
 import (
+	"fmt"
+	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
 	"github.com/ethereum/go-ethereum/common"
+	"github.com/libp2p/go-libp2p-core/peer"
+	"sync"
+	"time"
 )
 
 // Matching constants:
 //  - MAX_LEN_GUARDIAN_KEYS in Solana contract (limited by transaction size - 19 is the maximum amount possible)
 //
-// The Eth and Terra contracts do not specify a maximum number and support more than that,
+// The Eth contracts do not specify a maximum number and support more than that,
 // but presumably, chain-specific transaction size limits will apply at some point (untested).
 const MaxGuardianCount = 19
 
+// MaxNodesPerGuardian specifies the maximum amount of nodes per guardian key that we'll accept
+// whenever we maintain any per-guardian, per-node state.
+//
+// There currently isn't any state clean up, so the value is on the high side to prevent
+// accidentally reaching the limit due to operational mistakes.
+const MaxNodesPerGuardian = 15
+
+// MaxStateAge specified the maximum age of state entries in seconds. Expired entries are purged
+// from the state by Cleanup().
+const MaxStateAge = 1 * time.Minute
+
 type GuardianSet struct {
 	// Guardian's public key hashes truncated by the ETH standard hashing mechanism (20 bytes).
 	Keys []common.Address
@@ -28,7 +44,7 @@ func (g *GuardianSet) KeysAsHexStrings() []string {
 	return r
 }
 
-// Get a given address index from the guardian set. Returns (-1, false)
+// KeyIndex returns 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 {
@@ -39,3 +55,98 @@ func (g *GuardianSet) KeyIndex(addr common.Address) (int, bool) {
 
 	return -1, false
 }
+
+type GuardianSetState struct {
+	mu      sync.Mutex
+	current *GuardianSet
+
+	// Last heartbeat message received per guardian per p2p node. Maintained
+	// across guardian set updates - these values don't change.
+	lastHeartbeats map[common.Address]map[peer.ID]*gossipv1.Heartbeat
+}
+
+func NewGuardianSetState() *GuardianSetState {
+	return &GuardianSetState{
+		lastHeartbeats: map[common.Address]map[peer.ID]*gossipv1.Heartbeat{},
+	}
+}
+
+func (st *GuardianSetState) Set(set *GuardianSet) {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	st.current = set
+}
+
+func (st *GuardianSetState) Get() *GuardianSet {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	return st.current
+}
+
+// LastHeartbeat returns the most recent heartbeat message received for
+// a given guardian node, or nil if none have been received.
+func (st *GuardianSetState) LastHeartbeat(addr common.Address) map[peer.ID]*gossipv1.Heartbeat {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+	ret := make(map[peer.ID]*gossipv1.Heartbeat)
+	for k, v := range st.lastHeartbeats[addr] {
+		ret[k] = v
+	}
+	return ret
+}
+
+// SetHeartbeat stores a verified heartbeat observed by a given guardian.
+func (st *GuardianSetState) SetHeartbeat(addr common.Address, peerId peer.ID, hb *gossipv1.Heartbeat) error {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	v, ok := st.lastHeartbeats[addr]
+
+	if !ok {
+		v = make(map[peer.ID]*gossipv1.Heartbeat)
+		st.lastHeartbeats[addr] = v
+	} else {
+		if len(v) >= MaxNodesPerGuardian {
+			// TODO: age out old entries?
+			return fmt.Errorf("too many nodes (%d) for guardian, cannot store entry", len(v))
+		}
+	}
+
+	v[peerId] = hb
+	return nil
+}
+
+// GetAll returns all stored heartbeats.
+func (st *GuardianSetState) GetAll() map[common.Address]map[peer.ID]*gossipv1.Heartbeat {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	ret := make(map[common.Address]map[peer.ID]*gossipv1.Heartbeat)
+
+	// Deep copy
+	for addr, v := range st.lastHeartbeats {
+		ret[addr] = make(map[peer.ID]*gossipv1.Heartbeat)
+		for peerId, hb := range v {
+			ret[addr][peerId] = hb
+		}
+	}
+
+	return ret
+}
+
+// Cleanup removes expired entries from the state.
+func (st *GuardianSetState) Cleanup() {
+	st.mu.Lock()
+	defer st.mu.Unlock()
+
+	for addr, v := range st.lastHeartbeats {
+		for peerId, hb := range v {
+			ts := time.Unix(hb.Timestamp, 0)
+			if time.Since(ts) > MaxStateAge {
+				delete(st.lastHeartbeats[addr], peerId)
+			}
+		}
+	}
+}

+ 0 - 1
node/pkg/common/readiness.go

@@ -5,5 +5,4 @@ import "github.com/certusone/wormhole/node/pkg/readiness"
 const (
 	ReadinessEthSyncing    readiness.Component = "ethSyncing"
 	ReadinessSolanaSyncing readiness.Component = "solanaSyncing"
-	ReadinessTerraSyncing  readiness.Component = "terraSyncing"
 )

+ 2 - 25
node/pkg/devnet/constants.go

@@ -26,9 +26,8 @@ var (
 	GanacheBridgeContractAddress = common.HexToAddress("0x5b1869D9A4C187F2EAa108f3062412ecf0526b24")
 
 	// ERC20 example tokens.
-	GanacheExampleERC20Token        = common.HexToAddress("0xCfEB869F69431e42cdB54A4F4f105C19C080A601")
-	GanacheExampleERC20WrappedSOL   = common.HexToAddress("0xf5b1d8fab1054b9cf7db274126972f97f9d42a11")
-	GanacheExampleERC20WrappedTerra = common.HexToAddress("0x62b47a23cd900da982bdbe75aeb891d3ed18cc36")
+	GanacheExampleERC20Token      = common.HexToAddress("0xCfEB869F69431e42cdB54A4F4f105C19C080A601")
+	GanacheExampleERC20WrappedSOL = common.HexToAddress("0xf5b1d8fab1054b9cf7db274126972f97f9d42a11")
 )
 
 const (
@@ -62,28 +61,6 @@ const (
 
 	// ERC20 default precision.
 	ERC20DefaultPrecision = 1e18
-
-	// CW20 default precision.
-	TerraDefaultPrecision = 1e8
-
-	// Terra LCD url
-	TerraLCDURL = "http://localhost:1317"
-
-	// Terra test chain ID
-	TerraChainID = "localterra"
-
-	// Terra main test address to send/receive tokens
-	TerraMainTestAddress    = "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"
-	TerraMainTestAddressHex = "00000000000000000000000035743074956c710800e83198011ccbd4ddf1556d"
-
-	// Terra token address
-	TerraTokenAddress = "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
-
-	// Terra bridge contract address
-	TerraBridgeAddress = "terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc"
-
-	// Terra devnet fee payer mnemonic
-	TerraFeePayerKey = "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"
 )
 
 func DeriveAccount(accountIndex uint) accounts.Account {

+ 1 - 1
node/pkg/devnet/guardianset_vaa.go

@@ -78,7 +78,7 @@ func GetKeyedTransactor(ctx context.Context) *bind.TransactOpts {
 		panic(err)
 	}
 
-	kt := bind.NewKeyedTransactor(key)
+	kt := bind.NewKeyedTransactor(key) // nolint
 	kt.Context = ctx
 
 	return kt

+ 86 - 0
node/pkg/ethereum/by_transaction.go

@@ -0,0 +1,86 @@
+package ethereum
+
+import (
+	"context"
+	"fmt"
+	"github.com/certusone/wormhole/node/pkg/common"
+	"github.com/certusone/wormhole/node/pkg/ethereum/abi"
+	"github.com/certusone/wormhole/node/pkg/vaa"
+	eth_common "github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/ethclient"
+	"time"
+)
+
+var (
+	// SECURITY: Hardcoded ABI identifier for the LogTokensLocked topic. When using the watcher, we don't need this
+	// since the node will only hand us pre-filtered events. In this case, we need to manually verify it
+	// since ParseLogTokensLocked will only verify whether it parses.
+	logTokensLockedTopic = eth_common.HexToHash("0x6bbd554ad75919f71fd91bf917ca6e4f41c10f03ab25751596a22253bb39aab8")
+)
+
+// MessageEventsForTransaction returns the lockup events for a given transaction.
+// Returns the block number and a list of MessagePublication events.
+func MessageEventsForTransaction(
+	ctx context.Context,
+	c *ethclient.Client,
+	contract eth_common.Address,
+	tx eth_common.Hash) (uint64, []*common.ChainLock, error) {
+
+	f, err := abi.NewAbiFilterer(contract, c)
+	if err != nil {
+		return 0, nil, fmt.Errorf("failed to create ABI filterer: %w", err)
+	}
+
+	// Get transactions logs from transaction
+	receipt, err := c.TransactionReceipt(ctx, tx)
+	if err != nil {
+		return 0, nil, fmt.Errorf("failed to get transaction receipt: %w", err)
+	}
+
+	// Get block
+	block, err := c.BlockByHash(ctx, receipt.BlockHash)
+	if err != nil {
+		return 0, nil, fmt.Errorf("failed to get block: %w", err)
+	}
+
+	msgs := make([]*common.ChainLock, 0, len(receipt.Logs))
+
+	// Extract logs
+	for _, l := range receipt.Logs {
+		// SECURITY: Skip logs not produced by our contract.
+		if l.Address != contract {
+			continue
+		}
+
+		if l == nil {
+			continue
+		}
+
+		if l.Topics[0] != logTokensLockedTopic {
+			continue
+		}
+
+		ev, err := f.ParseLogTokensLocked(*l)
+		if err != nil {
+			return 0, nil, fmt.Errorf("failed to parse log: %w", err)
+		}
+
+		lock := &common.ChainLock{
+			TxHash:        tx,
+			Timestamp:     time.Unix(int64(block.Time()), 0),
+			Nonce:         ev.Nonce,
+			SourceAddress: ev.Sender,
+			TargetAddress: ev.Recipient,
+			SourceChain:   vaa.ChainIDEthereum,
+			TargetChain:   vaa.ChainID(ev.TargetChain),
+			TokenChain:    vaa.ChainID(ev.TokenChain),
+			TokenAddress:  ev.Token,
+			TokenDecimals: ev.TokenDecimals,
+			Amount:        ev.Amount,
+		}
+
+		msgs = append(msgs, lock)
+	}
+
+	return receipt.BlockNumber.Uint64(), msgs, nil
+}

+ 201 - 43
node/pkg/ethereum/watcher.go

@@ -5,8 +5,11 @@ import (
 	"fmt"
 	"github.com/certusone/wormhole/node/pkg/p2p"
 	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
+	"github.com/ethereum/go-ethereum/rpc"
+	"github.com/prometheus/client_golang/prometheus/promauto"
 	"math/big"
 	"sync"
+	"sync/atomic"
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
@@ -25,59 +28,65 @@ import (
 )
 
 var (
-	ethConnectionErrors = prometheus.NewCounterVec(
+	ethConnectionErrors = promauto.NewCounterVec(
 		prometheus.CounterOpts{
 			Name: "wormhole_eth_connection_errors_total",
 			Help: "Total number of Ethereum connection errors (either during initial connection or while watching)",
 		}, []string{"reason"})
 
-	ethLockupsFound = prometheus.NewCounter(
+	ethLockupsFound = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_eth_lockups_found_total",
 			Help: "Total number of Eth lockups found (pre-confirmation)",
 		})
-	ethLockupsConfirmed = prometheus.NewCounter(
+	ethLockupsConfirmed = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_eth_lockups_confirmed_total",
 			Help: "Total number of Eth lockups verified (post-confirmation)",
 		})
-	guardianSetChangesConfirmed = prometheus.NewCounter(
+	ethMessagesOrphaned = promauto.NewCounterVec(
+		prometheus.CounterOpts{
+			Name: "wormhole_eth_lockups_orphaned_total",
+			Help: "Total number of Eth lockups dropped (orphaned)",
+		}, []string{"reason"})
+	guardianSetChangesConfirmed = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_eth_guardian_set_changes_confirmed_total",
 			Help: "Total number of guardian set changes verified (we only see confirmed ones to begin with)",
 		})
-	currentEthHeight = prometheus.NewGauge(
+	currentEthHeight = promauto.NewGauge(
 		prometheus.GaugeOpts{
 			Name: "wormhole_eth_current_height",
 			Help: "Current Ethereum block height",
 		})
-	queryLatency = prometheus.NewHistogramVec(
+	queryLatency = promauto.NewHistogramVec(
 		prometheus.HistogramOpts{
 			Name: "wormhole_eth_query_latency",
 			Help: "Latency histogram for Ethereum calls (note that most interactions are streaming queries, NOT calls, and we cannot measure latency for those",
 		}, []string{"operation"})
 )
 
-func init() {
-	prometheus.MustRegister(ethConnectionErrors)
-	prometheus.MustRegister(ethLockupsFound)
-	prometheus.MustRegister(ethLockupsConfirmed)
-	prometheus.MustRegister(guardianSetChangesConfirmed)
-	prometheus.MustRegister(currentEthHeight)
-	prometheus.MustRegister(queryLatency)
-}
-
 type (
 	EthBridgeWatcher struct {
-		url              string
-		bridge           eth_common.Address
-		minConfirmations uint64
-
-		pendingLocks      map[eth_common.Hash]*pendingLock
-		pendingLocksGuard sync.Mutex
+		url                    string
+		bridge                 eth_common.Address
+		minLockupConfirmations uint64
 
 		lockChan chan *common.ChainLock
 		setChan  chan *common.GuardianSet
+
+		obsvReqC chan *gossipv1.ObservationRequest
+
+		pending   map[pendingKey]*pendingLock
+		pendingMu sync.Mutex
+
+		// 0 is a valid guardian set, so we need a nil value here
+		currentGuardianSet *uint32
+	}
+
+	pendingKey struct {
+		TxHash    eth_common.Hash
+		BlockHash eth_common.Hash
 	}
 
 	pendingLock struct {
@@ -86,14 +95,14 @@ type (
 	}
 )
 
-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 NewEthBridgeWatcher(url string, bridge eth_common.Address, minConfirmations uint64, lockEvents chan *common.ChainLock, setEvents chan *common.GuardianSet, obsvReqC chan *gossipv1.ObservationRequest) *EthBridgeWatcher {
+	return &EthBridgeWatcher{url: url, bridge: bridge, minLockupConfirmations: minConfirmations, lockChan: lockEvents, setChan: setEvents, pending: map[pendingKey]*pendingLock{}, obsvReqC: obsvReqC}
 }
 
 func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 	// Initialize gossip metrics (we want to broadcast the address even if we're not yet syncing)
 	p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDEthereum, &gossipv1.Heartbeat_Network{
-		BridgeAddress: e.bridge.Hex(),
+		ContractAddress: e.bridge.Hex(),
 	})
 
 	timeout, cancel := context.WithTimeout(ctx, 15*time.Second)
@@ -101,6 +110,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 	c, err := ethclient.DialContext(timeout, e.url)
 	if err != nil {
 		ethConnectionErrors.WithLabelValues("dial_error").Inc()
+		p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 		return fmt.Errorf("dialing eth client failed: %w", err)
 	}
 
@@ -123,6 +133,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 	tokensLockedSub, err := f.WatchLogTokensLocked(&bind.WatchOpts{Context: timeout}, tokensLockedC, nil, nil)
 	if err != nil {
 		ethConnectionErrors.WithLabelValues("subscribe_error").Inc()
+		p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 		return fmt.Errorf("failed to subscribe to token lockup events: %w", err)
 	}
 
@@ -144,6 +155,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 	idx, gs, err := FetchCurrentGuardianSet(timeout, e.url, e.bridge)
 	if err != nil {
 		ethConnectionErrors.WithLabelValues("guardian_set_fetch_error").Inc()
+		p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 		return fmt.Errorf("failed requesting guardian set from Ethereum: %w", err)
 	}
 	logger.Info("initial guardian set fetched", zap.Any("value", gs), zap.Uint32("index", idx))
@@ -152,6 +164,78 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 		Index: idx,
 	}
 
+	// Track the current block number so we can compare it to the block number of
+	// the message publication for observation requests.
+	var currentBlockNumber uint64
+
+	go func() {
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			case r := <-e.obsvReqC:
+				// This can't happen unless there is a programming error - the caller
+				// is expected to send us only requests for our chainID.
+				if vaa.ChainID(r.ChainId) != vaa.ChainIDEthereum {
+					panic("invalid chain ID")
+				}
+
+				tx := eth_common.BytesToHash(r.TxHash)
+				logger.Info("received observation request",
+					zap.String("tx_hash", tx.Hex()))
+
+				// SECURITY: Load the block number before requesting the transaction to avoid a
+				// race condition where requesting the tx succeeds and is then dropped due to a fork,
+				// but blockNumberU had already advanced beyond the required threshold.
+				//
+				// In the primary watcher flow, this is of no concern since we assume the node
+				// always sends the head before it sends the logs (implicit synchronization
+				// by relying on the same websocket connection).
+				blockNumberU := atomic.LoadUint64(&currentBlockNumber)
+				if blockNumberU == 0 {
+					logger.Error("no block number available, ignoring observation request")
+					continue
+				}
+
+				timeout, cancel := context.WithTimeout(ctx, 5*time.Second)
+				blockNumber, msgs, err := MessageEventsForTransaction(timeout, c, e.bridge, tx)
+				cancel()
+
+				if err != nil {
+					logger.Error("failed to process observation request")
+					continue
+				}
+
+				for _, msg := range msgs {
+					// SECURITY: In the recovery flow, we already know which transaction to
+					// observe, and we can assume that it has reached the expected finality
+					// level a long time ago. Therefore, the logic is much simpler than the
+					// primary watcher, which has to wait for finality.
+					//
+					// Instead, we can simply check if the transaction's block number is in
+					// the past by more than the expected confirmation number.
+					//
+					// Ensure that the current block number is at least expectedConfirmations
+					// larger than the message observation's block number.
+					if blockNumber+e.minLockupConfirmations <= blockNumberU {
+						logger.Info("re-observed message publication transaction",
+							zap.Stringer("tx", msg.TxHash),
+							zap.Uint64("current_block", blockNumberU),
+							zap.Uint64("observed_block", blockNumber),
+						)
+						e.lockChan <- msg
+					} else {
+						logger.Info("ignoring re-observed message publication transaction",
+							zap.Stringer("tx", msg.TxHash),
+							zap.Uint64("current_block", blockNumberU),
+							zap.Uint64("observed_block", blockNumber),
+						)
+					}
+				}
+			}
+		}
+	}()
+
 	go func() {
 		for {
 			select {
@@ -160,6 +244,7 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 			case e := <-tokensLockedSub.Err():
 				ethConnectionErrors.WithLabelValues("subscription_error").Inc()
 				errC <- fmt.Errorf("error while processing token lockup subscription: %w", e)
+				p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 				return
 			case e := <-guardianSetEvent.Err():
 				ethConnectionErrors.WithLabelValues("subscription_error").Inc()
@@ -168,13 +253,14 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 			case ev := <-tokensLockedC:
 				// Request timestamp for block
 				msm := time.Now()
-				timeout, cancel = context.WithTimeout(ctx, 15*time.Second)
+				timeout, cancel := context.WithTimeout(ctx, 15*time.Second)
 				b, err := c.BlockByNumber(timeout, big.NewInt(int64(ev.Raw.BlockNumber)))
 				cancel()
 				queryLatency.WithLabelValues("block_by_number").Observe(time.Since(msm).Seconds())
 
 				if err != nil {
 					ethConnectionErrors.WithLabelValues("block_by_number_error").Inc()
+					p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 					errC <- fmt.Errorf("failed to request timestamp for block %d: %w", ev.Raw.BlockNumber, err)
 					return
 				}
@@ -198,12 +284,17 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 
 				ethLockupsFound.Inc()
 
-				e.pendingLocksGuard.Lock()
-				e.pendingLocks[ev.Raw.TxHash] = &pendingLock{
+				key := pendingKey{
+					TxHash:    ev.Raw.TxHash,
+					BlockHash: ev.Raw.BlockHash,
+				}
+
+				e.pendingMu.Lock()
+				e.pending[key] = &pendingLock{
 					lock:   lock,
 					height: ev.Raw.BlockNumber,
 				}
-				e.pendingLocksGuard.Unlock()
+				e.pendingMu.Unlock()
 			case ev := <-guardianSetC:
 				logger.Info("guardian set has changed, fetching new value",
 					zap.Uint32("new_index", ev.NewGuardianIndex))
@@ -235,6 +326,8 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 	headSink := make(chan *types.Header, 2)
 	headerSubscription, err := c.SubscribeNewHead(ctx, headSink)
 	if err != nil {
+		ethConnectionErrors.WithLabelValues("header_subscribe_error").Inc()
+		p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 		return fmt.Errorf("failed to subscribe to header events: %w", err)
 	}
 
@@ -244,43 +337,108 @@ func (e *EthBridgeWatcher) Run(ctx context.Context) error {
 			case <-ctx.Done():
 				return
 			case e := <-headerSubscription.Err():
+				ethConnectionErrors.WithLabelValues("header_subscription_error").Inc()
 				errC <- fmt.Errorf("error while processing header subscription: %w", e)
+				p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDEthereum, 1)
 				return
 			case ev := <-headSink:
 				start := time.Now()
-				logger.Info("processing new header", zap.Stringer("block", ev.Number))
+				currentHash := ev.Hash()
+				logger.Info("processing new header",
+					zap.Stringer("current_block", ev.Number),
+					zap.Stringer("current_blockhash", currentHash))
 				currentEthHeight.Set(float64(ev.Number.Int64()))
 				readiness.SetReady(common.ReadinessEthSyncing)
 				p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDEthereum, &gossipv1.Heartbeat_Network{
-					Height:        ev.Number.Int64(),
-					BridgeAddress: e.bridge.Hex(),
+					Height:          ev.Number.Int64(),
+					ContractAddress: e.bridge.Hex(),
 				})
 
-				e.pendingLocksGuard.Lock()
+				e.pendingMu.Lock()
 
 				blockNumberU := ev.Number.Uint64()
-				for hash, pLock := range e.pendingLocks {
+				atomic.StoreUint64(&currentBlockNumber, blockNumberU)
+
+				for key, pLock := range e.pending {
 
 					// Transaction was dropped and never picked up again
-					if pLock.height+4*e.minConfirmations <= blockNumberU {
-						logger.Debug("lockup timed out", zap.Stringer("tx", pLock.lock.TxHash),
-							zap.Stringer("block", ev.Number))
-						delete(e.pendingLocks, hash)
+					if pLock.height+4*e.minLockupConfirmations <= blockNumberU {
+						logger.Info("lockup timed out",
+							zap.Stringer("tx", pLock.lock.TxHash),
+							zap.Stringer("blockhash", key.BlockHash),
+							zap.Stringer("current_block", ev.Number),
+							zap.Stringer("current_blockhash", currentHash))
+						ethMessagesOrphaned.WithLabelValues("timeout").Inc()
+						delete(e.pending, key)
 						continue
 					}
 
 					// Transaction is now ready
-					if pLock.height+e.minConfirmations <= ev.Number.Uint64() {
-						logger.Debug("lockup confirmed", zap.Stringer("tx", pLock.lock.TxHash),
-							zap.Stringer("block", ev.Number))
-						delete(e.pendingLocks, hash)
+					if pLock.height+e.minLockupConfirmations <= ev.Number.Uint64() {
+						timeout, cancel := context.WithTimeout(ctx, 5*time.Second)
+						tx, err := c.TransactionReceipt(timeout, pLock.lock.TxHash)
+						cancel()
+
+						// If the node returns an error after waiting expectedConfirmation blocks,
+						// it means the chain reorged and the transaction was orphaned. The
+						// TransactionReceipt call is using the same websocket connection than the
+						// head notifications, so it's guaranteed to be atomic.
+						//
+						// Check multiple possible error cases - the node seems to return a
+						// "not found" error most of the time, but it could conceivably also
+						// return a nil tx or rpc.ErrNoResult.
+						if tx == nil || err == rpc.ErrNoResult || (err != nil && err.Error() == "not found") {
+							logger.Warn("tx was orphaned",
+								zap.Stringer("tx", pLock.lock.TxHash),
+								zap.Stringer("blockhash", key.BlockHash),
+								zap.Stringer("current_block", ev.Number),
+								zap.Stringer("current_blockhash", currentHash),
+								zap.Error(err))
+							delete(e.pending, key)
+							ethMessagesOrphaned.WithLabelValues("not_found").Inc()
+							continue
+						}
+
+						// Any error other than "not found" is likely transient - we retry next block.
+						if err != nil {
+							logger.Warn("transaction could not be fetched",
+								zap.Stringer("tx", pLock.lock.TxHash),
+								zap.Stringer("blockhash", key.BlockHash),
+								zap.Stringer("current_block", ev.Number),
+								zap.Stringer("current_blockhash", currentHash),
+								zap.Error(err))
+							continue
+						}
+
+						// It's possible for a transaction to be orphaned and then included in a different block
+						// but with the same tx hash. Drop the observation (it will be re-observed and needs to
+						// wait for the full confirmation time again).
+						if tx.BlockHash != key.BlockHash {
+							logger.Info("tx got dropped and mined in a different block; the message should have been reobserved",
+								zap.Stringer("tx", pLock.lock.TxHash),
+								zap.Stringer("blockhash", key.BlockHash),
+								zap.Stringer("current_block", ev.Number),
+								zap.Stringer("current_blockhash", currentHash))
+							delete(e.pending, key)
+							ethMessagesOrphaned.WithLabelValues("blockhash_mismatch").Inc()
+							continue
+						}
+
+						logger.Info("lockup confirmed",
+							zap.Stringer("tx", pLock.lock.TxHash),
+							zap.Stringer("blockhash", key.BlockHash),
+							zap.Stringer("current_block", ev.Number),
+							zap.Stringer("current_blockhash", currentHash))
+						delete(e.pending, key)
 						e.lockChan <- pLock.lock
 						ethLockupsConfirmed.Inc()
 					}
 				}
 
-				e.pendingLocksGuard.Unlock()
-				logger.Info("processed new header", zap.Stringer("block", ev.Number),
+				e.pendingMu.Unlock()
+				logger.Info("processed new header",
+					zap.Stringer("current_block", ev.Number),
+					zap.Stringer("current_blockhash", currentHash),
 					zap.Duration("took", time.Since(start)))
 			}
 		}

+ 87 - 0
node/pkg/p2p/netmetrics.go

@@ -0,0 +1,87 @@
+package p2p
+
+import (
+	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
+	"github.com/certusone/wormhole/node/pkg/vaa"
+	"github.com/certusone/wormhole/node/pkg/version"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/libp2p/go-libp2p-core/peer"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
+	"math"
+	"regexp"
+	"strconv"
+)
+
+var (
+	wormholeNetworkNodeHeight = promauto.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "wormhole_network_node_height",
+			Help: "Network height of the given guardian node per network",
+		}, []string{"guardian_addr", "node_id", "node_name", "network"})
+	wormholeNetworkNodeErrors = promauto.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "wormhole_network_node_errors_count",
+			Help: "Number of errors the given guardian node encountered per network",
+		}, []string{"guardian_addr", "node_id", "node_name", "network"})
+	wormholeNetworkVersion = promauto.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "wormhole_network_node_version",
+			Help: "Network version of the given guardian node per network",
+		}, []string{"guardian_addr", "node_id", "node_name", "network", "version"})
+)
+
+func collectNodeMetrics(addr common.Address, peerId peer.ID, hb *gossipv1.Heartbeat) {
+	for _, n := range hb.Networks {
+		if n == nil {
+			continue
+		}
+
+		chain := vaa.ChainID(n.Id)
+
+		wormholeNetworkNodeHeight.WithLabelValues(
+			addr.Hex(), peerId.Pretty(), hb.NodeName, chain.String()).Set(float64(n.Height))
+
+		wormholeNetworkNodeErrors.WithLabelValues(
+			addr.Hex(), peerId.Pretty(), hb.NodeName, chain.String()).Set(float64(n.ErrorCount))
+
+		wormholeNetworkVersion.WithLabelValues(
+			addr.Hex(), peerId.Pretty(), hb.NodeName, chain.String(),
+			sanitizeVersion(hb.Version, version.Version())).Set(1)
+	}
+}
+
+var (
+	// Parse version string using regular expression.
+	// The version string should be in the format of "vX.Y.Z"
+	// where X, Y and Z are integers. Suffixes are ignored.
+	reVersion = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)`)
+)
+
+// sanitizeVersion cleans up the version string to prevent an attacker from executing a cardinality attack.
+func sanitizeVersion(version string, reference string) string {
+	// Match groups of reVersion
+	components := reVersion.FindStringSubmatch(version)
+	referenceComponents := reVersion.FindStringSubmatch(reference)
+
+	// Compare components of the version string with the reference and ensure
+	// that the distance is less than 5.
+	for i, c := range components {
+		if len(referenceComponents) <= i {
+			return "other"
+		}
+
+		cInt, _ := strconv.Atoi(c)
+		cRefInt, _ := strconv.Atoi(referenceComponents[i])
+
+		if math.Abs(float64(cInt-cRefInt)) > 5 {
+			return "other"
+		}
+	}
+
+	v := reVersion.FindString(version)
+	if v == "" {
+		return "other"
+	}
+	return v
+}

+ 33 - 0
node/pkg/p2p/netmetrics_test.go

@@ -0,0 +1,33 @@
+package p2p
+
+import (
+	"testing"
+)
+
+type sanitizeVersionCase struct {
+	version string
+	ref     string
+	want    string
+}
+
+func Test_sanitizeVersion(t *testing.T) {
+	cases := []sanitizeVersionCase{
+		{version: "v1.0.0", ref: "v1.0.0", want: "v1.0.0"},
+		{version: "v1.0.0-foo", ref: "v1.0.0", want: "v1.0.0"},
+		{version: "v1.0.0-foo", ref: "v1.0.0-bar", want: "v1.0.0"},
+		{version: "v6.0.0-foo", ref: "v1.0.0-bar", want: "v6.0.0"},
+		{version: "v6.1.0-foo", ref: "v1.0.0-bar", want: "v6.1.0"},
+		{version: "v6.1.0-foo", ref: "v4.5.0-bar", want: "v6.1.0"},
+		{version: "v6.1.0.1.1.1", ref: "v4.5.0.2.2.2", want: "v6.1.0"},
+		{version: "v10.1.0-foo", ref: "v1.0.0", want: "other"},
+		{version: "notaversion", ref: "v1.0.0", want: "other"},
+		{version: "v6.1.10000000", ref: "v1.0.0-bar", want: "other"},
+	}
+
+	for _, c := range cases {
+		got := sanitizeVersion(c.version, c.ref)
+		if got != c.want {
+			t.Errorf("sanitizeVersion(%q, %q) == %q, want %q", c.version, c.ref, got, c.want)
+		}
+	}
+}

+ 250 - 19
node/pkg/p2p/p2p.go

@@ -2,9 +2,16 @@ package p2p
 
 import (
 	"context"
+	"crypto/ecdsa"
+	"errors"
 	"fmt"
+	node_common "github.com/certusone/wormhole/node/pkg/common"
+	"github.com/certusone/wormhole/node/pkg/vaa"
 	"github.com/certusone/wormhole/node/pkg/version"
+	"github.com/ethereum/go-ethereum/common"
+	ethcrypto "github.com/ethereum/go-ethereum/crypto"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
 	"strings"
 	"time"
 
@@ -29,32 +36,42 @@ import (
 )
 
 var (
-	p2pHeartbeatsSent = prometheus.NewCounter(
+	p2pHeartbeatsSent = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_p2p_heartbeats_sent_total",
 			Help: "Total number of p2p heartbeats sent",
 		})
-	p2pMessagesSent = prometheus.NewCounter(
+	p2pMessagesSent = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_p2p_broadcast_messages_sent_total",
 			Help: "Total number of p2p pubsub broadcast messages sent",
 		})
-	p2pMessagesReceived = prometheus.NewCounterVec(
+	p2pMessagesReceived = promauto.NewCounterVec(
 		prometheus.CounterOpts{
 			Name: "wormhole_p2p_broadcast_messages_received_total",
 			Help: "Total number of p2p pubsub broadcast messages received",
 		}, []string{"type"})
 )
 
-func init() {
-	prometheus.MustRegister(p2pHeartbeatsSent)
-	prometheus.MustRegister(p2pMessagesSent)
-	prometheus.MustRegister(p2pMessagesReceived)
+var heartbeatMessagePrefix = []byte("heartbeat|")
+
+var signedObservationRequestPrefix = []byte("signed_observation_request|")
+
+func heartbeatDigest(b []byte) common.Hash {
+	return ethcrypto.Keccak256Hash(append(heartbeatMessagePrefix, b...))
+}
+
+func signedObservationRequestDigest(b []byte) common.Hash {
+	return ethcrypto.Keccak256Hash(append(signedObservationRequestPrefix, b...))
 }
 
 func Run(obsvC chan *gossipv1.SignedObservation,
 	sendC chan []byte,
+	obsvReqC chan *gossipv1.ObservationRequest,
+	obsvReqSendC chan *gossipv1.ObservationRequest,
 	priv crypto.PrivKey,
+	gk *ecdsa.PrivateKey,
+	gst *node_common.GuardianSetState,
 	port uint,
 	networkID string,
 	bootstrapPeers string,
@@ -94,7 +111,7 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 			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
+					// This intentionally makes us incompatible with the global IPFS DHT
 					dht.ProtocolPrefix(protocol.ID("/"+networkID)),
 				)
 				return idht, err
@@ -177,7 +194,28 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 		logger.Info("Node has been started", zap.String("peer_id", h.ID().String()),
 			zap.String("addrs", fmt.Sprintf("%v", h.Addrs())))
 
+		bootTime := time.Now()
+
+		// Periodically run guardian state set cleanup.
+		go func() {
+			ticker := time.NewTicker(15 * time.Second)
+			defer ticker.Stop()
+			for {
+				select {
+				case <-ticker.C:
+					gst.Cleanup()
+				case <-ctx.Done():
+					return
+				}
+			}
+		}()
+
 		go func() {
+			// Disable heartbeat when no node name is provided (spy mode)
+			if nodeName == "" {
+				return
+			}
+
 			ctr := int64(0)
 			tick := time.NewTicker(15 * time.Second)
 			defer tick.Stop()
@@ -190,25 +228,53 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 					DefaultRegistry.mu.Lock()
 					networks := make([]*gossipv1.Heartbeat_Network, 0, len(DefaultRegistry.networkStats))
 					for _, v := range DefaultRegistry.networkStats {
+						errCtr := DefaultRegistry.GetErrorCount(vaa.ChainID(v.Id))
+						v.ErrorCount = errCtr
 						networks = append(networks, v)
 					}
 
-					msg := gossipv1.GossipMessage{Message: &gossipv1.GossipMessage_Heartbeat{
-						Heartbeat: &gossipv1.Heartbeat{
-							NodeName:     nodeName,
-							Counter:      ctr,
-							Timestamp:    time.Now().UnixNano(),
-							Networks:     networks,
-							Version:      version.Version(),
-							GuardianAddr: DefaultRegistry.guardianAddress,
-						}}}
+					heartbeat := &gossipv1.Heartbeat{
+						NodeName:      nodeName,
+						Counter:       ctr,
+						Timestamp:     time.Now().UnixNano(),
+						Networks:      networks,
+						Version:       version.Version(),
+						GuardianAddr:  DefaultRegistry.guardianAddress,
+						BootTimestamp: bootTime.UnixNano(),
+					}
 
-					b, err := proto.Marshal(&msg)
+					ourAddr := ethcrypto.PubkeyToAddress(gk.PublicKey)
+					if err := gst.SetHeartbeat(ourAddr, h.ID(), heartbeat); err != nil {
+						panic(err)
+					}
+					collectNodeMetrics(ourAddr, h.ID(), heartbeat)
+
+					b, err := proto.Marshal(heartbeat)
 					if err != nil {
 						panic(err)
 					}
+
 					DefaultRegistry.mu.Unlock()
 
+					// Sign the heartbeat using our node's guardian key.
+					digest := heartbeatDigest(b)
+					sig, err := ethcrypto.Sign(digest.Bytes(), gk)
+					if err != nil {
+						panic(err)
+					}
+
+					msg := gossipv1.GossipMessage{Message: &gossipv1.GossipMessage_SignedHeartbeat{
+						SignedHeartbeat: &gossipv1.SignedHeartbeat{
+							Heartbeat:    b,
+							Signature:    sig,
+							GuardianAddr: ourAddr.Bytes(),
+						}}}
+
+					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))
@@ -231,6 +297,44 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 					if err != nil {
 						logger.Error("failed to publish message from queue", zap.Error(err))
 					}
+				case msg := <-obsvReqSendC:
+					b, err := proto.Marshal(msg)
+					if err != nil {
+						panic(err)
+					}
+
+					// Sign the observation request using our node's guardian key.
+					digest := signedObservationRequestDigest(b)
+					sig, err := ethcrypto.Sign(digest.Bytes(), gk)
+					if err != nil {
+						panic(err)
+					}
+
+					sReq := &gossipv1.SignedObservationRequest{
+						ObservationRequest: b,
+						Signature:          sig,
+						GuardianAddr:       ethcrypto.PubkeyToAddress(gk.PublicKey).Bytes(),
+					}
+
+					envelope := &gossipv1.GossipMessage{
+						Message: &gossipv1.GossipMessage_SignedObservationRequest{
+							SignedObservationRequest: sReq}}
+
+					b, err = proto.Marshal(envelope)
+					if err != nil {
+						panic(err)
+					}
+
+					// Send to local observation request queue (the loopback message is ignored)
+					obsvReqC <- msg
+
+					err = th.Publish(ctx, b)
+					p2pMessagesSent.Inc()
+					if err != nil {
+						logger.Error("failed to publish observation request", zap.Error(err))
+					} else {
+						logger.Info("published signed observation request", zap.Any("signed_observation_request", sReq))
+					}
 				}
 			}
 		}()
@@ -245,7 +349,7 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 			err = proto.Unmarshal(envelope.Data, &msg)
 			if err != nil {
 				logger.Info("received invalid message",
-					zap.String("data", string(envelope.Data)),
+					zap.Binary("data", envelope.Data),
 					zap.String("from", envelope.GetFrom().String()))
 				p2pMessagesReceived.WithLabelValues("invalid").Inc()
 				continue
@@ -269,9 +373,59 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 					zap.Any("value", m.Heartbeat),
 					zap.String("from", envelope.GetFrom().String()))
 				p2pMessagesReceived.WithLabelValues("heartbeat").Inc()
+			case *gossipv1.GossipMessage_SignedHeartbeat:
+				s := m.SignedHeartbeat
+				gs := gst.Get()
+				if gs == nil {
+					// No valid guardian set yet - dropping heartbeat
+					logger.Debug("skipping heartbeat - no guardian set",
+						zap.Any("value", s),
+						zap.String("from", envelope.GetFrom().String()))
+					break
+				}
+				if heartbeat, err := processSignedHeartbeat(envelope.GetFrom(), s, gs, gst, false); err != nil {
+					p2pMessagesReceived.WithLabelValues("invalid_heartbeat").Inc()
+					logger.Debug("invalid signed heartbeat received",
+						zap.Error(err),
+						zap.Any("payload", msg.Message),
+						zap.Any("value", s),
+						zap.Binary("raw", envelope.Data),
+						zap.String("from", envelope.GetFrom().String()))
+				} else {
+					p2pMessagesReceived.WithLabelValues("valid_heartbeat").Inc()
+					logger.Debug("valid signed heartbeat received",
+						zap.Any("value", heartbeat),
+						zap.String("from", envelope.GetFrom().String()))
+				}
 			case *gossipv1.GossipMessage_SignedObservation:
 				obsvC <- m.SignedObservation
 				p2pMessagesReceived.WithLabelValues("observation").Inc()
+			case *gossipv1.GossipMessage_SignedObservationRequest:
+				s := m.SignedObservationRequest
+				gs := gst.Get()
+				if gs == nil {
+					logger.Debug("dropping SignedObservationRequest - no guardian set",
+						zap.Any("value", s),
+						zap.String("from", envelope.GetFrom().String()))
+					break
+				}
+				r, err := processSignedObservationRequest(s, gs)
+				if err != nil {
+					p2pMessagesReceived.WithLabelValues("invalid_signed_observation_request").Inc()
+					logger.Debug("invalid signed observation request received",
+						zap.Error(err),
+						zap.Any("payload", msg.Message),
+						zap.Any("value", s),
+						zap.Binary("raw", envelope.Data),
+						zap.String("from", envelope.GetFrom().String()))
+				} else {
+					p2pMessagesReceived.WithLabelValues("signed_observation_request").Inc()
+					logger.Info("valid signed observation request received",
+						zap.Any("value", r),
+						zap.String("from", envelope.GetFrom().String()))
+
+					obsvReqC <- r
+				}
 			default:
 				p2pMessagesReceived.WithLabelValues("unknown").Inc()
 				logger.Warn("received unknown message type (running outdated software?)",
@@ -282,3 +436,80 @@ func Run(obsvC chan *gossipv1.SignedObservation,
 		}
 	}
 }
+
+func processSignedHeartbeat(from peer.ID, s *gossipv1.SignedHeartbeat, gs *node_common.GuardianSet, gst *node_common.GuardianSetState, disableVerify bool) (*gossipv1.Heartbeat, error) {
+	envelopeAddr := common.BytesToAddress(s.GuardianAddr)
+	idx, ok := gs.KeyIndex(envelopeAddr)
+	var pk common.Address
+	if !ok {
+		if !disableVerify {
+			return nil, fmt.Errorf("invalid message: %s not in guardian set", envelopeAddr)
+		}
+	} else {
+		pk = gs.Keys[idx]
+	}
+
+	digest := heartbeatDigest(s.Heartbeat)
+
+	pubKey, err := ethcrypto.Ecrecover(digest.Bytes(), s.Signature)
+	if err != nil {
+		return nil, errors.New("failed to recover public key")
+	}
+
+	signerAddr := common.BytesToAddress(ethcrypto.Keccak256(pubKey[1:])[12:])
+	if pk != signerAddr && !disableVerify {
+		return nil, fmt.Errorf("invalid signer: %v", signerAddr)
+	}
+
+	var h gossipv1.Heartbeat
+	err = proto.Unmarshal(s.Heartbeat, &h)
+	if err != nil {
+		return nil, fmt.Errorf("failed to unmarshal heartbeat: %w", err)
+	}
+
+	// Store verified heartbeat in global guardian set state.
+	if err := gst.SetHeartbeat(signerAddr, from, &h); err != nil {
+		return nil, fmt.Errorf("failed to store in guardian set state: %w", err)
+	}
+
+	collectNodeMetrics(signerAddr, from, &h)
+
+	return &h, nil
+}
+
+func processSignedObservationRequest(s *gossipv1.SignedObservationRequest, gs *node_common.GuardianSet) (*gossipv1.ObservationRequest, error) {
+	envelopeAddr := common.BytesToAddress(s.GuardianAddr)
+	idx, ok := gs.KeyIndex(envelopeAddr)
+	var pk common.Address
+	if !ok {
+		return nil, fmt.Errorf("invalid message: %s not in guardian set", envelopeAddr)
+	} else {
+		pk = gs.Keys[idx]
+	}
+
+	digest := signedObservationRequestDigest(s.ObservationRequest)
+
+	pubKey, err := ethcrypto.Ecrecover(digest.Bytes(), s.Signature)
+	if err != nil {
+		return nil, errors.New("failed to recover public key")
+	}
+
+	signerAddr := common.BytesToAddress(ethcrypto.Keccak256(pubKey[1:])[12:])
+	if pk != signerAddr {
+		return nil, fmt.Errorf("invalid signer: %v", signerAddr)
+	}
+
+	var h gossipv1.ObservationRequest
+	err = proto.Unmarshal(s.ObservationRequest, &h)
+	if err != nil {
+		return nil, fmt.Errorf("failed to unmarshal observation request: %w", err)
+	}
+
+	// TODO: implement per-guardian rate limiting
+
+	if h.ChainId != vaa.ChainIDEthereum {
+		return nil, fmt.Errorf("invalid chain id: %v", h.ChainId)
+	}
+
+	return &h, nil
+}

+ 18 - 1
node/pkg/p2p/registry.go

@@ -14,13 +14,18 @@ type registry struct {
 	// Mapping of chain IDs to network status messages.
 	networkStats map[vaa.ChainID]*gossipv1.Heartbeat_Network
 
+	// Per-chain error counters
+	errorCounters  map[vaa.ChainID]uint64
+	errorCounterMu sync.Mutex
+
 	// Value of Heartbeat.guardian_addr.
 	guardianAddress string
 }
 
 func NewRegistry() *registry {
 	return &registry{
-		networkStats: map[vaa.ChainID]*gossipv1.Heartbeat_Network{},
+		networkStats:  map[vaa.ChainID]*gossipv1.Heartbeat_Network{},
+		errorCounters: map[vaa.ChainID]uint64{},
 	}
 }
 
@@ -44,3 +49,15 @@ func (r *registry) SetNetworkStats(chain vaa.ChainID, data *gossipv1.Heartbeat_N
 	r.networkStats[chain] = data
 	r.mu.Unlock()
 }
+
+func (r *registry) AddErrorCount(chain vaa.ChainID, delta uint64) {
+	r.errorCounterMu.Lock()
+	defer r.errorCounterMu.Unlock()
+	r.errorCounters[chain] += 1
+}
+
+func (r *registry) GetErrorCount(chain vaa.ChainID) uint64 {
+	r.errorCounterMu.Lock()
+	defer r.errorCounterMu.Unlock()
+	return r.errorCounters[chain]
+}

+ 4 - 6
node/pkg/processor/broadcast.go

@@ -3,6 +3,7 @@ package processor
 import (
 	"encoding/hex"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
 	"time"
 
 	ethcommon "github.com/ethereum/go-ethereum/common"
@@ -14,18 +15,14 @@ import (
 )
 
 var (
-	observationsBroadcastTotal = prometheus.NewCounter(
+	observationsBroadcastTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_observations_broadcast_total",
 			Help: "Total number of signed observations queued for broadcast",
 		})
 )
 
-func init() {
-	prometheus.MustRegister(observationsBroadcastTotal)
-}
-
-func (p *Processor) broadcastSignature(v *vaa.VAA, signature []byte) {
+func (p *Processor) broadcastSignature(v *vaa.VAA, signature []byte, txhash []byte) {
 	digest, err := v.SigningMsg()
 	if err != nil {
 		panic(err)
@@ -35,6 +32,7 @@ func (p *Processor) broadcastSignature(v *vaa.VAA, signature []byte) {
 		Addr:      crypto.PubkeyToAddress(p.gk.PublicKey).Bytes(),
 		Hash:      digest.Bytes(),
 		Signature: signature,
+		TxHash:    txhash,
 	}
 
 	w := gossipv1.GossipMessage{Message: &gossipv1.GossipMessage_SignedObservation{SignedObservation: &obsv}}

+ 3 - 6
node/pkg/processor/injection.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"encoding/hex"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/promauto"
 
 	"github.com/ethereum/go-ethereum/crypto"
 	"go.uber.org/zap"
@@ -13,17 +14,13 @@ import (
 )
 
 var (
-	vaaInjectionsTotal = prometheus.NewCounter(
+	vaaInjectionsTotal = promauto.NewCounter(
 		prometheus.CounterOpts{
 			Name: "wormhole_vaa_injections_total",
 			Help: "Total number of injected VAA queued for broadcast",
 		})
 )
 
-func init() {
-	prometheus.MustRegister(vaaInjectionsTotal)
-}
-
 // handleInjection processes a pre-populated VAA injected locally.
 func (p *Processor) handleInjection(ctx context.Context, v *vaa.VAA) {
 	// Generate digest of the unsigned VAA.
@@ -47,5 +44,5 @@ func (p *Processor) handleInjection(ctx context.Context, v *vaa.VAA) {
 		zap.String("signature", hex.EncodeToString(s)))
 
 	vaaInjectionsTotal.Inc()
-	p.broadcastSignature(v, s)
+	p.broadcastSignature(v, s, nil)
 }

+ 3 - 1
node/pkg/processor/lockup.go

@@ -3,6 +3,7 @@ package processor
 import (
 	"context"
 	"encoding/hex"
+	"github.com/mr-tron/base58"
 	"github.com/prometheus/client_golang/prometheus"
 
 	"github.com/ethereum/go-ethereum/crypto"
@@ -95,6 +96,7 @@ func (p *Processor) handleLockup(ctx context.Context, k *common.ChainLock) {
 		zap.Stringer("source_chain", k.SourceChain),
 		zap.Stringer("target_chain", k.TargetChain),
 		zap.Stringer("txhash", k.TxHash),
+		zap.String("txhash_b58", base58.Encode(k.TxHash.Bytes())),
 		zap.String("digest", hex.EncodeToString(digest.Bytes())),
 		zap.String("signature", hex.EncodeToString(s)))
 
@@ -102,5 +104,5 @@ func (p *Processor) handleLockup(ctx context.Context, k *common.ChainLock) {
 		"source_chain": k.SourceChain.String(),
 		"target_chain": k.TargetChain.String()}).Add(1)
 
-	p.broadcastSignature(v, s)
+	p.broadcastSignature(v, s, k.TxHash.Bytes())
 }

+ 5 - 32
node/pkg/processor/observation.go

@@ -5,12 +5,11 @@ import (
 	"encoding/hex"
 	"fmt"
 	bridge_common "github.com/certusone/wormhole/node/pkg/common"
+	"github.com/mr-tron/base58"
 	"github.com/prometheus/client_golang/prometheus"
 	"strings"
 	"time"
 
-	"github.com/certusone/wormhole/node/pkg/terra"
-
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/crypto"
 	"go.uber.org/zap"
@@ -75,7 +74,10 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
 	p.logger.Info("received observation",
 		zap.String("digest", hash),
 		zap.String("signature", hex.EncodeToString(m.Signature)),
-		zap.String("addr", hex.EncodeToString(m.Addr)))
+		zap.String("addr", hex.EncodeToString(m.Addr)),
+		zap.String("txhash", hex.EncodeToString(m.TxHash)),
+		zap.String("txhash_b58", base58.Encode(m.TxHash)),
+	)
 
 	observationsReceivedTotal.Inc()
 
@@ -252,8 +254,6 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
 					// Ethereum is special because it's expensive, and guardians cannot
 					// be expected to pay the fees. We only submit to Ethereum in devnet mode.
 					p.devnetVAASubmission(ctx, signed, hash)
-				case vaa.ChainIDTerra:
-					go p.terraVAASubmission(ctx, signed, hash)
 				default:
 					p.logger.Error("unknown target chain ID",
 						zap.String("digest", hash),
@@ -266,7 +266,6 @@ func (p *Processor) handleObservation(ctx context.Context, m *gossipv1.SignedObs
 
 				// A guardian set update is broadcast to every chain that we talk to.
 				p.devnetVAASubmission(ctx, signed, hash)
-				p.terraVAASubmission(ctx, signed, hash)
 			case *vaa.BodyContractUpgrade:
 				p.state.vaaSignatures[hash].source = "contract_upgrade"
 
@@ -321,29 +320,3 @@ func (p *Processor) devnetVAASubmission(ctx context.Context, signed *vaa.VAA, ha
 		p.logger.Info("VAA submitted to Ethereum", zap.Any("tx", tx), zap.String("digest", hash))
 	}
 }
-
-// Submit VAA to Terra.
-func (p *Processor) terraVAASubmission(ctx context.Context, signed *vaa.VAA, hash string) {
-	if !p.devnetMode || !p.terraEnabled {
-		p.logger.Warn("ignoring terra VAA submission",
-			zap.String("digest", hash))
-		return
-	}
-
-	observationsDirectSubmissionsTotal.WithLabelValues("terra").Inc()
-
-	tx, err := terra.SubmitVAA(ctx, p.terraLCD, p.terraChainID, p.terraContract, p.terraFeePayer, signed)
-	if err != nil {
-		if strings.Contains(err.Error(), "VaaAlreadyExecuted") {
-			p.logger.Info("VAA already submitted to Terra by another node, ignoring",
-				zap.Error(err), zap.String("digest", hash))
-		} else {
-			p.logger.Error("failed to submit VAA to Terra",
-				zap.Error(err), zap.String("digest", hash))
-		}
-		return
-	}
-
-	observationsDirectSubmissionSuccessTotal.WithLabelValues("terra").Inc()
-	p.logger.Info("VAA submitted to Terra", zap.Any("tx", tx), zap.String("digest", hash))
-}

+ 10 - 39
node/pkg/processor/processor.go

@@ -14,7 +14,6 @@ import (
 	"github.com/certusone/wormhole/node/pkg/devnet"
 	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
 	"github.com/certusone/wormhole/node/pkg/supervisor"
-	"github.com/certusone/wormhole/node/pkg/terra"
 	"github.com/certusone/wormhole/node/pkg/vaa"
 )
 
@@ -75,18 +74,16 @@ type Processor struct {
 	devnetNumGuardians uint
 	devnetEthRPC       string
 
-	terraEnabled  bool
-	terraLCD      string
-	terraChainID  string
-	terraContract string
-	terraFeePayer string
-
 	logger *zap.Logger
 
 	// Runtime state
 
 	// gs is the currently valid guardian set
 	gs *common.GuardianSet
+	// gst is managed by the processor and allows concurrent access to the
+	// guardian set by other components.
+	gst *common.GuardianSetState
+
 	// state is the current runtime VAA view
 	state *aggregationState
 	// gk pk as eth address
@@ -104,14 +101,10 @@ func NewProcessor(
 	vaaC chan *vaa.VAA,
 	injectC chan *vaa.VAA,
 	gk *ecdsa.PrivateKey,
+	gst *common.GuardianSetState,
 	devnetMode bool,
 	devnetNumGuardians uint,
-	devnetEthRPC string,
-	terraEnabled bool,
-	terraLCD string,
-	terraChainID string,
-	terraContract string,
-	terraFeePayer string) *Processor {
+	devnetEthRPC string) *Processor {
 
 	return &Processor{
 		lockC:              lockC,
@@ -121,16 +114,11 @@ func NewProcessor(
 		vaaC:               vaaC,
 		injectC:            injectC,
 		gk:                 gk,
+		gst:                gst,
 		devnetMode:         devnetMode,
 		devnetNumGuardians: devnetNumGuardians,
 		devnetEthRPC:       devnetEthRPC,
 
-		terraEnabled:  terraEnabled,
-		terraLCD:      terraLCD,
-		terraChainID:  terraChainID,
-		terraContract: terraContract,
-		terraFeePayer: terraFeePayer,
-
 		logger:  supervisor.Logger(ctx),
 		state:   &aggregationState{vaaMap{}},
 		ourAddr: crypto.PubkeyToAddress(gk.PublicKey),
@@ -149,6 +137,8 @@ func (p *Processor) Run(ctx context.Context) error {
 				zap.Strings("set", p.gs.KeysAsHexStrings()),
 				zap.Uint32("index", p.gs.Index))
 
+			p.gst.Set(p.gs)
+
 			// Dev mode guardian set update check (no-op in production)
 			err := p.checkDevModeGuardianSetUpdate(ctx)
 			if err != nil {
@@ -181,31 +171,12 @@ func (p *Processor) checkDevModeGuardianSetUpdate(ctx context.Context) error {
 			if err != nil {
 				// Either Ethereum is not yet up, or another node has already submitted - bail
 				// and let another node handle it. We only check the guardian set on Ethereum,
-				// so we use that to sequence devnet creation for Terra and Solana as well.
+				// so we use that to sequence devnet creation Solana as well.
 				return fmt.Errorf("failed to submit Eth devnet guardian set change: %v", err)
 			}
 
 			p.logger.Info("devnet guardian set change submitted to Ethereum", zap.Any("trx", trx), zap.Any("vaa", v))
 
-			if p.terraEnabled {
-				// Submit to Terra
-				go func() {
-					for {
-						timeout, cancel := context.WithTimeout(ctx, 5*time.Second)
-						trxResponse, err := terra.SubmitVAA(timeout, p.terraLCD, p.terraChainID, p.terraContract, p.terraFeePayer, v)
-						if err != nil {
-							cancel()
-							p.logger.Error("failed to submit Terra devnet guardian set change, retrying", zap.Error(err))
-							time.Sleep(1 * time.Second)
-							continue
-						}
-						cancel()
-						p.logger.Info("devnet guardian set change submitted to Terra", zap.Any("trxResponse", trxResponse), zap.Any("vaa", v))
-						break
-					}
-				}()
-			}
-
 			// Submit VAA to Solana as well. This is asynchronous and can fail, leading to inconsistent devnet state.
 			p.vaaC <- v
 		}

+ 71 - 0
node/pkg/publicrpc/publicrpcserver.go

@@ -0,0 +1,71 @@
+package publicrpc
+
+import (
+	"context"
+	"github.com/certusone/wormhole/node/pkg/common"
+	publicrpcv1 "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1"
+	"go.uber.org/zap"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+type PublicrpcServer struct {
+	publicrpcv1.UnsafePublicRPCServiceServer
+	logger *zap.Logger
+	gst    *common.GuardianSetState
+}
+
+func NewPublicrpcServer(
+	logger *zap.Logger,
+	gst *common.GuardianSetState,
+) *PublicrpcServer {
+	return &PublicrpcServer{
+		logger: logger.Named("publicrpcserver"),
+		gst:    gst,
+	}
+}
+
+func (s *PublicrpcServer) GetLastHeartbeats(ctx context.Context, req *publicrpcv1.GetLastHeartbeatsRequest) (*publicrpcv1.GetLastHeartbeatsResponse, error) {
+	gs := s.gst.Get()
+	if gs == nil {
+		return nil, status.Error(codes.Unavailable, "guardian set not fetched from chain yet")
+	}
+
+	resp := &publicrpcv1.GetLastHeartbeatsResponse{
+		Entries: make([]*publicrpcv1.GetLastHeartbeatsResponse_Entry, 0),
+	}
+
+	// Fetch all heartbeats (including from nodes not in the guardian set - which
+	// can happen either with --disableHeartbeatVerify or when the guardian set changes)
+	for addr, v := range s.gst.GetAll() {
+		for peerId, hb := range v {
+			resp.Entries = append(resp.Entries, &publicrpcv1.GetLastHeartbeatsResponse_Entry{
+				VerifiedGuardianAddr: addr.Hex(),
+				P2PNodeAddr:          peerId.Pretty(),
+				RawHeartbeat:         hb,
+			})
+		}
+	}
+
+	return resp, nil
+}
+
+func (s *PublicrpcServer) GetCurrentGuardianSet(ctx context.Context, req *publicrpcv1.GetCurrentGuardianSetRequest) (*publicrpcv1.GetCurrentGuardianSetResponse, error) {
+	gs := s.gst.Get()
+	if gs == nil {
+		return nil, status.Error(codes.Unavailable, "guardian set not fetched from chain yet")
+	}
+
+	resp := &publicrpcv1.GetCurrentGuardianSetResponse{
+		GuardianSet: &publicrpcv1.GuardianSet{
+			Index:     gs.Index,
+			Addresses: make([]string, len(gs.Keys)),
+		},
+	}
+
+	for i, v := range gs.Keys {
+		resp.GuardianSet.Addresses[i] = v.Hex()
+	}
+
+	return resp, nil
+}

+ 7 - 1
node/pkg/readiness/health.go

@@ -48,9 +48,14 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		panic(err)
 	}
+	_, err = resp.Write([]byte("[these values update AT STARTUP ONLY - see https://github.com/certusone/wormhole/blob/dev.v2/docs/operations.md#readyz]\n\n"))
+	if err != nil {
+		panic(err)
+	}
 
+	mu.Lock()
 	for k, v := range registry {
-		_, err = fmt.Fprintln(resp, fmt.Sprintf("%s\t%v", k, v))
+		_, err = fmt.Fprintf(resp, "%s\t%v\n", k, v)
 		if err != nil {
 			panic(err)
 		}
@@ -59,6 +64,7 @@ func Handler(w http.ResponseWriter, r *http.Request) {
 			ready = false
 		}
 	}
+	mu.Unlock()
 
 	if !ready {
 		w.WriteHeader(http.StatusPreconditionFailed)

+ 11 - 9
node/pkg/solana/client.go

@@ -10,9 +10,9 @@ import (
 	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
 	"github.com/certusone/wormhole/node/pkg/supervisor"
 	"github.com/certusone/wormhole/node/pkg/vaa"
-	"github.com/dfuse-io/solana-go"
-	"github.com/dfuse-io/solana-go/rpc"
 	eth_common "github.com/ethereum/go-ethereum/common"
+	"github.com/gagliardetto/solana-go"
+	"github.com/gagliardetto/solana-go/rpc"
 	"github.com/mr-tron/base58"
 	"github.com/prometheus/client_golang/prometheus"
 	"go.uber.org/zap"
@@ -71,10 +71,10 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
 	// Initialize gossip metrics (we want to broadcast the address even if we're not yet syncing)
 	bridgeAddr := base58.Encode(s.bridge[:])
 	p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDSolana, &gossipv1.Heartbeat_Network{
-		BridgeAddress: bridgeAddr,
+		ContractAddress: bridgeAddr,
 	})
 
-	rpcClient := rpc.NewClient(s.rpcUrl)
+	rpcClient := rpc.New(s.rpcUrl)
 	logger := supervisor.Logger(ctx)
 	errC := make(chan error)
 
@@ -96,13 +96,14 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
 					queryLatency.WithLabelValues("get_slot").Observe(time.Since(start).Seconds())
 					if err != nil {
 						solanaConnectionErrors.WithLabelValues("get_slot_error").Inc()
+						p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDSolana, 1)
 						errC <- err
 						return
 					}
 					currentSolanaHeight.Set(float64(slot))
 					p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDSolana, &gossipv1.Heartbeat_Network{
-						Height:        int64(slot),
-						BridgeAddress: bridgeAddr,
+						Height:          int64(slot),
+						ContractAddress: bridgeAddr,
 					})
 
 					logger.Info("current Solana height", zap.Uint64("slot", uint64(slot)))
@@ -112,8 +113,8 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
 					defer cancel()
 					start = time.Now()
 
-					accounts, err := rpcClient.GetProgramAccounts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
-						Commitment: rpc.CommitmentMax, // TODO: deprecated, use Finalized
+					accounts, err := rpcClient.GetProgramAccountsWithOpts(rCtx, s.bridge, &rpc.GetProgramAccountsOpts{
+						Commitment: rpc.CommitmentFinalized,
 						Filters: []rpc.RPCFilter{
 							{
 								DataSize: 1184, // Search for TransferOutProposal accounts
@@ -129,6 +130,7 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
 					queryLatency.WithLabelValues("get_program_accounts").Observe(time.Since(start).Seconds())
 					if err != nil {
 						solanaConnectionErrors.WithLabelValues("get_program_account_error").Inc()
+						p2p.DefaultRegistry.AddErrorCount(vaa.ChainIDSolana, 1)
 						errC <- err
 						return
 					}
@@ -139,7 +141,7 @@ func (s *SolanaWatcher) Run(ctx context.Context) error {
 					)
 
 					for _, acc := range accounts {
-						proposal, err := ParseTransferOutProposal(acc.Account.Data)
+						proposal, err := ParseTransferOutProposal(acc.Account.Data.GetBinary())
 						if err != nil {
 							solanaAccountSkips.WithLabelValues("parse_transfer_out").Inc()
 							logger.Warn(

+ 0 - 107
node/pkg/terra/sender.go

@@ -1,107 +0,0 @@
-package terra
-
-import (
-	"context"
-	"encoding/json"
-	"io/ioutil"
-	"time"
-
-	"github.com/certusone/wormhole/node/pkg/devnet"
-
-	"github.com/certusone/wormhole/node/pkg/vaa"
-	"github.com/terra-project/terra.go/client"
-	"github.com/terra-project/terra.go/key"
-	"github.com/terra-project/terra.go/msg"
-	"github.com/terra-project/terra.go/tx"
-)
-
-type submitVAAMsg struct {
-	Params submitVAAParams `json:"submit_v_a_a"`
-}
-
-type submitVAAParams struct {
-	VAA []byte `json:"vaa"`
-}
-
-// SubmitVAA prepares transaction with signed VAA and sends it to the Terra blockchain
-func SubmitVAA(ctx context.Context, urlLCD string, chainID string, contractAddress string, feePayer string, signed *vaa.VAA) (*client.TxResponse, error) {
-
-	// Serialize VAA
-	vaaBytes, err := signed.Marshal()
-	if err != nil {
-		return nil, err
-	}
-
-	// Derive Raw Private Key
-	privKey, err := key.DerivePrivKey(feePayer, key.CreateHDPath(0, 0))
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate StdPrivKey
-	tmKey, err := key.StdPrivKeyGen(privKey)
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate Address from Public Key
-	addr := msg.AccAddress(tmKey.PubKey().Address())
-
-	// Create LCDClient
-	LCDClient := client.NewLCDClient(
-		urlLCD,
-		chainID,
-		msg.NewDecCoinFromDec("uusd", msg.NewDecFromIntWithPrec(msg.NewInt(15), 2)), // 0.15uusd
-		msg.NewDecFromIntWithPrec(msg.NewInt(15), 1), tmKey, time.Second*15,
-	)
-
-	contract, err := msg.AccAddressFromBech32(contractAddress)
-	if err != nil {
-		return nil, err
-	}
-
-	// Create tx
-	contractCall, err := json.Marshal(submitVAAMsg{
-		Params: submitVAAParams{
-			VAA: vaaBytes,
-		}})
-
-	if err != nil {
-		return nil, err
-	}
-
-	executeContract := msg.NewExecuteContract(addr, contract, contractCall, msg.NewCoins())
-
-	transaction, err := LCDClient.CreateAndSignTx(ctx, client.CreateTxOptions{
-		Msgs: []msg.Msg{
-			executeContract,
-		},
-		Fee: tx.StdFee{
-			Gas:    msg.NewInt(0),
-			Amount: msg.NewCoins(),
-		},
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	// Broadcast
-	return LCDClient.Broadcast(ctx, transaction)
-}
-
-// ReadKey reads file and returns its content as a string
-func ReadKey(path string) (string, error) {
-	b, err := ioutil.ReadFile(path)
-	if err != nil {
-		return "", err
-	}
-	return string(b), nil
-}
-
-// WriteDevnetKey writes default devnet key to the file
-func WriteDevnetKey(path string) {
-	err := ioutil.WriteFile(path, []byte(devnet.TerraFeePayerKey), 0600)
-	if err != nil {
-		panic("Cannot write Terra key file")
-	}
-}

+ 0 - 317
node/pkg/terra/watcher.go

@@ -1,317 +0,0 @@
-package terra
-
-import (
-	"context"
-	"encoding/hex"
-	"fmt"
-	"github.com/certusone/wormhole/node/pkg/p2p"
-	gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1"
-	"io/ioutil"
-	"math/big"
-	"net/http"
-	"time"
-
-	"github.com/prometheus/client_golang/prometheus"
-
-	eth_common "github.com/ethereum/go-ethereum/common"
-
-	"github.com/certusone/wormhole/node/pkg/common"
-	"github.com/certusone/wormhole/node/pkg/readiness"
-	"github.com/certusone/wormhole/node/pkg/supervisor"
-	"github.com/certusone/wormhole/node/pkg/vaa"
-	"github.com/gorilla/websocket"
-	"github.com/tidwall/gjson"
-	"go.uber.org/zap"
-)
-
-type (
-	// BridgeWatcher is responsible for looking over Terra blockchain and reporting new transactions to the bridge
-	BridgeWatcher struct {
-		urlWS  string
-		urlLCD string
-		bridge string
-
-		lockChan chan *common.ChainLock
-		setChan  chan *common.GuardianSet
-	}
-)
-
-var (
-	terraConnectionErrors = prometheus.NewCounterVec(
-		prometheus.CounterOpts{
-			Name: "wormhole_terra_connection_errors_total",
-			Help: "Total number of Terra connection errors",
-		}, []string{"reason"})
-	terraLockupsConfirmed = prometheus.NewCounter(
-		prometheus.CounterOpts{
-			Name: "wormhole_terra_lockups_confirmed_total",
-			Help: "Total number of verified terra lockups found",
-		})
-	currentTerraHeight = prometheus.NewGauge(
-		prometheus.GaugeOpts{
-			Name: "wormhole_terra_current_height",
-			Help: "Current terra slot height (at default commitment level, not the level used for lockups)",
-		})
-	queryLatency = prometheus.NewHistogramVec(
-		prometheus.HistogramOpts{
-			Name: "wormhole_terra_query_latency",
-			Help: "Latency histogram for terra RPC calls",
-		}, []string{"operation"})
-)
-
-func init() {
-	prometheus.MustRegister(terraConnectionErrors)
-	prometheus.MustRegister(terraLockupsConfirmed)
-	prometheus.MustRegister(currentTerraHeight)
-	prometheus.MustRegister(queryLatency)
-}
-
-type clientRequest struct {
-	JSONRPC string `json:"jsonrpc"`
-	// A String containing the name of the method to be invoked.
-	Method string `json:"method"`
-	// Object to pass as request parameter to the method.
-	Params [1]string `json:"params"`
-	// The request id. This can be of any type. It is used to match the
-	// response with the request that it is replying to.
-	ID uint64 `json:"id"`
-}
-
-// NewTerraBridgeWatcher creates a new terra bridge watcher
-func NewTerraBridgeWatcher(urlWS string, urlLCD string, bridge string, lockEvents chan *common.ChainLock, setEvents chan *common.GuardianSet) *BridgeWatcher {
-	return &BridgeWatcher{urlWS: urlWS, urlLCD: urlLCD, bridge: bridge, lockChan: lockEvents, setChan: setEvents}
-}
-
-// Run is the main Terra Bridge run cycle
-func (e *BridgeWatcher) Run(ctx context.Context) error {
-	p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDTerra, &gossipv1.Heartbeat_Network{
-		BridgeAddress: e.bridge,
-	})
-
-	errC := make(chan error)
-	logger := supervisor.Logger(ctx)
-
-	logger.Info("connecting to websocket", zap.String("url", e.urlWS))
-
-	c, _, err := websocket.DefaultDialer.DialContext(ctx, e.urlWS, nil)
-	if err != nil {
-		terraConnectionErrors.WithLabelValues("websocket_dial_error").Inc()
-		return fmt.Errorf("websocket dial failed: %w", err)
-	}
-	defer c.Close()
-
-	// Subscribe to smart contract transactions
-	params := [...]string{fmt.Sprintf("tm.event='Tx' AND execute_contract.contract_address='%s'", e.bridge)}
-	command := &clientRequest{
-		JSONRPC: "2.0",
-		Method:  "subscribe",
-		Params:  params,
-		ID:      1,
-	}
-	err = c.WriteJSON(command)
-	if err != nil {
-		terraConnectionErrors.WithLabelValues("websocket_subscription_error").Inc()
-		return fmt.Errorf("websocket subscription failed: %w", err)
-	}
-
-	// Wait for the success response
-	_, _, err = c.ReadMessage()
-	if err != nil {
-		terraConnectionErrors.WithLabelValues("event_subscription_error").Inc()
-		return fmt.Errorf("event subscription failed: %w", err)
-	}
-	logger.Info("subscribed to new transaction events")
-
-	readiness.SetReady(common.ReadinessTerraSyncing)
-
-	go func() {
-		t := time.NewTicker(5 * time.Second)
-		client := &http.Client{
-			Timeout: time.Second * 5,
-		}
-
-		for {
-			<-t.C
-
-			// Query and report height and set currentTerraHeight
-			resp, err := client.Get(fmt.Sprintf("%s/blocks/latest", e.urlLCD))
-			if err != nil {
-				logger.Error("query latest block response error", zap.Error(err))
-				continue
-			}
-			blocksBody, err := ioutil.ReadAll(resp.Body)
-			if err != nil {
-				logger.Error("query guardian set error", zap.Error(err))
-				errC <- err
-				resp.Body.Close()
-				continue
-			}
-			resp.Body.Close()
-
-			blockJSON := string(blocksBody)
-			latestBlock := gjson.Get(blockJSON, "block.header.height")
-			logger.Info("current Terra height", zap.Int64("block", latestBlock.Int()))
-			currentTerraHeight.Set(float64(latestBlock.Int()))
-			p2p.DefaultRegistry.SetNetworkStats(vaa.ChainIDTerra, &gossipv1.Heartbeat_Network{
-				Height:        latestBlock.Int(),
-				BridgeAddress: e.bridge,
-			})
-		}
-	}()
-
-	go func() {
-		defer close(errC)
-
-		for {
-			_, message, err := c.ReadMessage()
-			if err != nil {
-				terraConnectionErrors.WithLabelValues("channel_read_error").Inc()
-				logger.Error("error reading channel", zap.Error(err))
-				errC <- err
-				return
-			}
-
-			// Received a message from the blockchain
-			json := string(message)
-			targetChain := gjson.Get(json, "result.events.from_contract\\.locked\\.target_chain.0")
-			tokenChain := gjson.Get(json, "result.events.from_contract\\.locked\\.token_chain.0")
-			tokenDecimals := gjson.Get(json, "result.events.from_contract\\.locked\\.token_decimals.0")
-			token := gjson.Get(json, "result.events.from_contract\\.locked\\.token.0")
-			sender := gjson.Get(json, "result.events.from_contract\\.locked\\.sender.0")
-			recipient := gjson.Get(json, "result.events.from_contract\\.locked\\.recipient.0")
-			amount := gjson.Get(json, "result.events.from_contract\\.locked\\.amount.0")
-			nonce := gjson.Get(json, "result.events.from_contract\\.locked\\.nonce.0")
-			txHash := gjson.Get(json, "result.events.tx\\.hash.0")
-			blockTime := gjson.Get(json, "result.events.from_contract\\.locked\\.block_time.0")
-
-			if targetChain.Exists() && tokenChain.Exists() && tokenDecimals.Exists() && token.Exists() && sender.Exists() &&
-				recipient.Exists() && amount.Exists() && amount.Exists() && nonce.Exists() && txHash.Exists() && blockTime.Exists() {
-
-				logger.Info("token lock detected on Terra",
-					zap.String("txHash", txHash.String()),
-					zap.String("targetChain", targetChain.String()),
-					zap.String("tokenChain", tokenChain.String()),
-					zap.String("tokenDecimals", tokenDecimals.String()),
-					zap.String("token", token.String()),
-					zap.String("sender", sender.String()),
-					zap.String("recipient", recipient.String()),
-					zap.String("amount", amount.String()),
-					zap.String("nonce", nonce.String()),
-					zap.String("blockTime", blockTime.String()),
-				)
-
-				senderAddress, err := StringToAddress(sender.String())
-				if err != nil {
-					logger.Error("cannot decode hex", zap.String("value", sender.String()))
-					continue
-				}
-				recipientAddress, err := StringToAddress(recipient.String())
-				if err != nil {
-					logger.Error("cannot decode hex", zap.String("value", recipient.String()))
-					continue
-				}
-				tokenAddress, err := StringToAddress(token.String())
-				if err != nil {
-					logger.Error("cannot decode hex", zap.String("value", token.String()))
-					continue
-				}
-				txHashValue, err := StringToHash(txHash.String())
-				if err != nil {
-					logger.Error("cannot decode hex", zap.String("value", txHash.String()))
-					continue
-				}
-				lock := &common.ChainLock{
-					TxHash:        txHashValue,
-					Timestamp:     time.Unix(blockTime.Int(), 0),
-					Nonce:         uint32(nonce.Uint()),
-					SourceAddress: senderAddress,
-					TargetAddress: recipientAddress,
-					SourceChain:   vaa.ChainIDTerra,
-					TargetChain:   vaa.ChainID(uint8(targetChain.Uint())),
-					TokenChain:    vaa.ChainID(uint8(tokenChain.Uint())),
-					TokenAddress:  tokenAddress,
-					TokenDecimals: uint8(tokenDecimals.Uint()),
-					Amount:        new(big.Int).SetUint64(amount.Uint()),
-				}
-				e.lockChan <- lock
-				terraLockupsConfirmed.Inc()
-			}
-
-			client := &http.Client{
-				Timeout: time.Second * 15,
-			}
-
-			// Query and report guardian set status
-			requestURL := fmt.Sprintf("%s/wasm/contracts/%s/store?query_msg={\"guardian_set_info\":{}}", e.urlLCD, e.bridge)
-			req, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
-			if err != nil {
-				terraConnectionErrors.WithLabelValues("guardian_set_req_error").Inc()
-				logger.Error("query guardian set request error", zap.Error(err))
-				errC <- err
-				return
-			}
-
-			msm := time.Now()
-			resp, err := client.Do(req)
-			if err != nil {
-				logger.Error("query guardian set response error", zap.Error(err))
-				errC <- err
-				return
-			}
-
-			body, err := ioutil.ReadAll(resp.Body)
-			queryLatency.WithLabelValues("guardian_set_info").Observe(time.Since(msm).Seconds())
-			if err != nil {
-				logger.Error("query guardian set error", zap.Error(err))
-				errC <- err
-				resp.Body.Close()
-				return
-			}
-
-			json = string(body)
-			guardianSetIndex := gjson.Get(json, "result.guardian_set_index")
-			addresses := gjson.Get(json, "result.addresses.#.bytes")
-
-			logger.Debug("current guardian set on Terra",
-				zap.Any("guardianSetIndex", guardianSetIndex),
-				zap.Any("addresses", addresses))
-
-			resp.Body.Close()
-
-			// We do not send guardian changes to the processor - ETH guardians are the source of truth.
-		}
-	}()
-
-	select {
-	case <-ctx.Done():
-		err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-		if err != nil {
-			logger.Error("error on closing socket ", zap.Error(err))
-		}
-		return ctx.Err()
-	case err := <-errC:
-		return err
-	}
-}
-
-// StringToAddress convert string into address
-func StringToAddress(value string) (vaa.Address, error) {
-	var address vaa.Address
-	res, err := hex.DecodeString(value)
-	if err != nil {
-		return address, err
-	}
-	copy(address[:], res)
-	return address, nil
-}
-
-// StringToHash convert string into transaction hash
-func StringToHash(value string) (eth_common.Hash, error) {
-	var hash eth_common.Hash
-	res, err := hex.DecodeString(value)
-	if err != nil {
-		return hash, err
-	}
-	copy(hash[:], res)
-	return hash, nil
-}

+ 35 - 4
node/pkg/vaa/structs.go

@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"io"
 	"math/big"
+	"strings"
 	"time"
 
 	"github.com/ethereum/go-ethereum/common"
@@ -94,34 +95,53 @@ type (
 	}
 )
 
+func (a Address) MarshalJSON() ([]byte, error) {
+	return []byte(fmt.Sprintf(`"%s"`, a)), nil
+}
+
 func (a Address) String() string {
 	return hex.EncodeToString(a[:])
 }
 
+func (a Address) Bytes() []byte {
+	return a[:]
+}
+
 func (c ChainID) String() string {
 	switch c {
+	case ChainIDUnset:
+		return "unset"
 	case ChainIDSolana:
 		return "solana"
 	case ChainIDEthereum:
 		return "ethereum"
-	case ChainIDTerra:
-		return "terra"
 	default:
 		return fmt.Sprintf("unknown chain ID: %d", c)
 	}
 }
 
+func ChainIDFromString(s string) (ChainID, error) {
+	s = strings.ToLower(s)
+	switch s {
+	case "solana":
+		return ChainIDSolana, nil
+	case "ethereum":
+		return ChainIDEthereum, nil
+	default:
+		return ChainIDUnset, fmt.Errorf("unknown chain ID: %s", s)
+	}
+}
+
 const (
 	ActionGuardianSetUpdate Action = 0x01
 	ActionContractUpgrade   Action = 0x02
 	ActionTransfer          Action = 0x10
 
+	ChainIDUnset ChainID = 0
 	// ChainIDSolana is the ChainID of Solana
 	ChainIDSolana = 1
 	// ChainIDEthereum is the ChainID of Ethereum
 	ChainIDEthereum = 2
-	// ChainIDTerra is the ChainID of Terra
-	ChainIDTerra = 3
 
 	minVAALength        = 1 + 4 + 52 + 4 + 1 + 1
 	SupportedVAAVersion = 0x01
@@ -446,3 +466,14 @@ func MustWrite(w io.Writer, order binary.ByteOrder, data interface{}) {
 		panic(fmt.Errorf("failed to write binary data: %v", data).Error())
 	}
 }
+
+// StringToAddress converts a hex-encoded adress into a vaa.Address
+func StringToAddress(value string) (Address, error) {
+	var address Address
+	res, err := hex.DecodeString(value)
+	if err != nil {
+		return address, err
+	}
+	copy(address[:], res)
+	return address, nil
+}

+ 3 - 0
node/pkg/version/version.go

@@ -4,5 +4,8 @@ package version
 var version = "development"
 
 func Version() string {
+	if version == "" {
+		panic("binary compiled with empty version")
+	}
 	return version
 }

+ 25 - 2
node/tools/go.mod

@@ -1,5 +1,28 @@
 module github.com/certusone/wormhole/node/tools
 
-go 1.15
+go 1.17
 
-require github.com/go-delve/delve v1.5.0
+require github.com/go-delve/delve v1.7.2
+
+require (
+	github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac // indirect
+	github.com/cosiner/argv v0.1.0 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
+	github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 // indirect
+	github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987 // indirect
+	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/inconshreveable/mousetrap v1.0.0 // indirect
+	github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
+	github.com/mattn/go-colorable v0.0.9 // indirect
+	github.com/mattn/go-isatty v0.0.3 // indirect
+	github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b // indirect
+	github.com/russross/blackfriday/v2 v2.0.1 // indirect
+	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
+	github.com/sirupsen/logrus v1.6.0 // indirect
+	github.com/spf13/cobra v1.1.3 // indirect
+	github.com/spf13/pflag v1.0.5 // indirect
+	go.starlark.net v0.0.0-20200821142938-949cc6f4b097 // indirect
+	golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 // indirect
+	golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
+	gopkg.in/yaml.v2 v2.4.0 // indirect
+)

+ 300 - 21
node/tools/go.sum

@@ -1,56 +1,335 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac h1:oehMMAySC3p8eSwcwQ8lTXxeCkkPll+AwNesUNowbJ8=
+github.com/aquasecurity/libbpfgo v0.1.2-0.20210708203834-4928d36fafac/go.mod h1:/+clceXE103FaXvVTIY2HAkQjxNtkra4DRWvZYr2SKw=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cosiner/argv v0.1.0 h1:BVDiEL32lwHukgJKP87btEPenzrrHUjajs/8yzaqcXg=
 github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
-github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
-github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-delve/delve v1.5.0 h1:gQsRvFdR0BGk19NROQZsAv6iG4w5QIZoJlxJeEUBb0c=
-github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ=
-github.com/google/go-dap v0.2.0 h1:whjIGQRumwbR40qRU7CEKuFLmePUUc2s4Nt9DoXXxWk=
-github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
+github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 h1:G765iDCq7bP5opdrPkXk+4V3yfkgV9iGFuheWZ/X/zY=
+github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-delve/delve v1.7.2 h1:QTDJlgx9OwUVYVm7xthyf2XHKrZcTQu3wkRbovktidM=
+github.com/go-delve/delve v1.7.2/go.mod h1:CHdOd8kuHlQxtBJr1HmJX5h+KmmWd/7Lk5d+D1zHn4E=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987 h1:aghNk+kvabZ5I1OC3cNHWvfZ8svcoDLAGyKYimqyGVk=
+github.com/google/go-dap v0.5.1-0.20210713061233-c91b005e3987/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
+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/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+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.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=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561 h1:isR/L+BIZ+rqODWYR/f526ygrBMGKZYFhaaFRDGvuZ8=
-github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b h1:8uaXtUkxiy+T/zdLWuxa/PG4so0TPZDZfafFNNSaptE=
 github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
+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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+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/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
-github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372 h1:eRfW1vRS4th8IX2iQeyqQ8cOUNOySvAYJ0IUvTXGoYA=
-github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
-github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1 h1:7bozMfSdo41n2NOc0GsVTTVUiA+Ncaj6pXNpm4UHKys=
-github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-go.starlark.net v0.0.0-20190702223751-32f345186213 h1:lkYv5AKwvvduv5XWP6szk/bvvgO6aDeUujhZQXIFTes=
-go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.starlark.net v0.0.0-20200821142938-949cc6f4b097 h1:YiRMXXgG+Pg26t1fjq+iAjaauKWMC9cmGFrtOEuwDDg=
+go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 h1:QlVATYS7JBoZMVaf+cNjb90WD/beKVHnIxFKT4QaHVI=
 golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
-golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY=
 golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+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.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 3 - 0
node/tools/tools.go

@@ -1,3 +1,6 @@
+//go:build tools
+// +build tools
+
 package tools
 
 import (

+ 46 - 13
proto/gossip/v1/gossip.proto

@@ -2,16 +2,32 @@ syntax = "proto3";
 
 package gossip.v1;
 
-option go_package = "proto/gossip/v1;gossipv1";
+option go_package = "github.com/certusone/wormhole/node/pkg/proto/gossip/v1;gossipv1";
 
 message GossipMessage {
   oneof message {
     Heartbeat heartbeat = 1;
     SignedObservation signed_observation = 2;
+    SignedHeartbeat signed_heartbeat = 3;
+    // Reserved: 4 (signed_vaa_with_quorum)
+    SignedObservationRequest signed_observation_request = 5;
   }
 }
 
-// P2P gossip heartbeats for network introspection purposes. ALL FIELDS ARE UNTRUSTED.
+message SignedHeartbeat {
+  // Serialized Heartbeat message.
+  bytes heartbeat = 1;
+
+  // ECDSA signature using the node's guardian public key.
+  bytes signature = 2;
+
+  // Guardian address that signed this payload (truncated Eth address).
+  // This is already contained in Heartbeat, however, we want to verify
+  // the payload before we deserialize it.
+  bytes guardian_addr = 3;
+}
+
+// P2P gossip heartbeats for network introspection purposes.
 message Heartbeat {
   // The node's arbitrarily chosen, untrusted nodeName.
   string node_name = 1;
@@ -26,16 +42,9 @@ message Heartbeat {
     // Consensus height of the node.
     int64 height = 2;
     // Chain-specific human-readable representation of the bridge contract address.
-    string bridge_address = 3;
-
-    // Fee payer account for this network, if present. Some networks like Ethereum do not use fee payer accounts.
-    message FeePayer {
-      // The account's on-chain balance.
-      int64 balance = 1;
-      // Chain-specific human-readable representation of the fee payer account's address.
-      string address = 2;
-    }
-    FeePayer fee_payer = 4;
+    string contract_address = 3;
+    // Connection error count
+    uint64 error_count = 4;
   }
   repeated Network networks = 4;
 
@@ -45,7 +54,8 @@ message Heartbeat {
   // Human-readable representation of the guardian key's address.
   string guardian_addr = 6;
 
-  // TODO: include signed statement of gk public key?
+  // UNIX boot timestamp.
+  int64 boot_timestamp = 7;
 }
 
 // A SignedObservation is a signed statement by a given guardian node
@@ -68,4 +78,27 @@ message SignedObservation {
   bytes hash = 2;
   // ECSDA signature of the hash using the node's guardian key.
   bytes signature = 3;
+  // Transaction hash this observation was made from.
+  // Optional, included for observability.
+  bytes tx_hash = 4;
+}
+
+// Any guardian can send a SignedObservationRequest to the network to request
+// all guardians to re-observe a given transaction. This is rate-limited to one
+// request per second per guardian to prevent abuse.
+//
+// In the current implementation, this is only implemented for Solana.
+// For Solana, the tx_hash is the account address of the transaction's message account.
+message SignedObservationRequest {
+  // Serialized observation request.
+  bytes observation_request = 1;
+
+  // Signature
+  bytes signature = 2;
+  bytes guardian_addr = 3;
+}
+
+message ObservationRequest {
+  uint32 chain_id = 1;
+  bytes tx_hash = 2;
 }

+ 22 - 17
proto/node/v1/node.proto

@@ -2,13 +2,13 @@ syntax = "proto3";
 
 package node.v1;
 
-option go_package = "proto/node/v1;nodev1";
+option go_package = "github.com/certusone/wormhole/node/pkg/proto/node/v1;nodev1";
 
-import "google/api/annotations.proto";
+import "gossip/v1/gossip.proto";
 
-// NodePrivileged exposes an administrative API. It runs on a UNIX socket and is authenticated
+// NodePrivilegedService exposes an administrative API. It runs on a UNIX socket and is authenticated
 // using Linux filesystem permissions.
-service NodePrivileged {
+service NodePrivilegedService {
   // InjectGovernanceVAA injects a governance VAA into the guardian node.
   // The node will inject the VAA into the aggregator and sign/broadcast the VAA signature.
   //
@@ -16,23 +16,22 @@ service NodePrivileged {
   // VAA timeout window for it to reach consensus.
   //
   rpc InjectGovernanceVAA (InjectGovernanceVAARequest) returns (InjectGovernanceVAAResponse);
+
+  // SendObservationRequest broadcasts a signed observation request to the gossip network
+  // using the node's guardian key. The network rate limits these requests to one per second.
+  // Requests at higher rates will fail silently.
+  rpc SendObservationRequest (SendObservationRequestRequest) returns (SendObservationRequestResponse);
 }
 
 message InjectGovernanceVAARequest {
   // Index of the current guardian set.
   uint32 current_set_index = 1;
 
-  // UNIX timestamp (s) of the VAA to be created. The timestamp is informational and will be part
-  // of the VAA submitted to the chain. It's part of the VAA digest and has to be identical across nodes and
-  // is critical for replay protection - a given event may only ever be observed with the same timestamp,
-  // otherwise, it may be possible to execute it multiple times.
-  //
-  // For lockups, the timestamp identifies the block that the lockup belongs to.
+  // List of governance VAA messages to inject.
+  repeated GovernanceMessage messages = 2;
+}
 
-  // For governance VAAs, guardians inject the VAA manually. Best practice is to pick a timestamp which roughly matches
-  // the timing of the off-chain ceremony used to achieve consensus. For guardian set updates, the actual on-chain
-  // guardian set creation timestamp will be set when the VAA is accepted on each chain.
-  //
+message GovernanceMessage {
   // This is a uint32 to match the on-chain timestamp representation. This becomes a problem in 2106 (sorry).
   uint32 timestamp = 2;
 
@@ -43,8 +42,8 @@ message InjectGovernanceVAARequest {
 }
 
 message InjectGovernanceVAAResponse {
-  // Canonical digest of the submitted VAA.
-  bytes digest = 1;
+  // Canonical digests of the submitted VAAs.
+  repeated bytes digests = 1;
 }
 
 // GuardianSet represents a new guardian set to be submitted to and signed by the node.
@@ -67,7 +66,7 @@ message GuardianKey {
   // data is the binary representation of the secp256k1 private key.
   bytes data = 1;
   // Whether this key is deterministically generated and unsuitable for production mode.
-  bool unsafeDeterministicKey = 2;
+  bool unsafe_deterministic_key = 2;
 }
 
 // ContractUpgrade represents a Wormhole contract update to be submitted to and signed by the node.
@@ -78,3 +77,9 @@ message ContractUpgrade {
   // Address of the new program/contract.
   bytes new_contract = 2;
 }
+
+message SendObservationRequestRequest {
+  gossip.v1.ObservationRequest observation_request = 1;
+}
+
+message SendObservationRequestResponse {}

+ 59 - 0
proto/publicrpc/v1/publicrpc.proto

@@ -0,0 +1,59 @@
+syntax = "proto3";
+
+package publicrpc.v1;
+
+option go_package = "github.com/certusone/wormhole/node/pkg/proto/publicrpc/v1;publicrpcv1";
+
+import "gossip/v1/gossip.proto";
+
+enum ChainID {
+  CHAIN_ID_UNSPECIFIED = 0;
+  CHAIN_ID_SOLANA = 1;
+  CHAIN_ID_ETHEREUM = 2;
+}
+
+// PublicRPCService service exposes endpoints to be consumed externally; GUIs, historical record keeping, etc.
+service PublicRPCService {
+  // GetLastHeartbeats returns the last heartbeat received for each guardian node in the
+  // node's active guardian set. Heartbeats received by nodes not in the guardian set are ignored.
+  // The heartbeat value is null if no heartbeat has yet been received.
+  rpc GetLastHeartbeats (GetLastHeartbeatsRequest) returns (GetLastHeartbeatsResponse) {}
+
+  rpc GetCurrentGuardianSet (GetCurrentGuardianSetRequest) returns (GetCurrentGuardianSetResponse) {}
+}
+
+message GetLastHeartbeatsRequest {
+}
+
+message GetLastHeartbeatsResponse {
+  message Entry {
+    // Verified, hex-encoded (with leading 0x) guardian address. This is the guardian address
+    // which signed this heartbeat. The GuardianAddr field inside the heartbeat
+    // is NOT verified - remote nodes can put arbitrary data in it.
+    string verified_guardian_addr = 1;
+
+    // Base58-encoded libp2p node address that sent this heartbeat, used to
+    // distinguish between multiple nodes running for the same guardian.
+    string p2p_node_addr = 2;
+
+    // Raw heartbeat received from the network. Data is only as trusted
+    // as the guardian node that sent it - none of the fields are verified.
+    gossip.v1.Heartbeat raw_heartbeat = 3;
+  }
+
+  repeated Entry entries = 1;
+}
+
+message GetCurrentGuardianSetRequest {
+}
+
+message GetCurrentGuardianSetResponse {
+  GuardianSet guardian_set = 1;
+}
+
+message GuardianSet {
+  // Guardian set index
+  uint32 index = 1;
+  // List of guardian addresses as human-readable hex-encoded (leading 0x) addresses.
+  repeated string addresses = 2;
+}

+ 21 - 16
solana/Dockerfile

@@ -1,27 +1,32 @@
-# syntax=docker.io/docker/dockerfile:experimental@sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44
-FROM docker.io/library/rust:1.49@sha256:a50165ea96983c21832578afb1c8c028674c965bc1ed43b607871b1f362e06a5
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/library/rust:1.58@sha256:e4979d36d5d30838126ea5ef05eb59c4c25ede7f064985e676feb21402d0661b
 
 RUN apt-get update && apt-get install -y libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang
 RUN rustup component add rustfmt
 
 WORKDIR /usr/src/bridge
 
-RUN sh -c "$(curl -sSfL https://release.solana.com/v1.5.5/install)"
+RUN sh -c "$(curl -sSfL https://release.solana.com/v1.9.4/install)"
 
 ENV PATH="/root/.local/share/solana/install/active_release/bin:$PATH"
 ENV RUST_LOG="solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug"
 
-COPY bridge bridge
-COPY agent agent
-COPY cli cli
-COPY Cargo.toml .
-COPY Cargo.lock .
+# TODO: Solana build no longer works - instead, we cheat and copy the binary artifact
 
-RUN --mount=type=cache,target=/usr/local,from=rust,source=/usr/local \
-    --mount=type=cache,target=/root/.cache \
-    --mount=type=cache,target=bridge/target \
-    --mount=type=cache,target=target \
-    --mount=type=cache,target=bin,from=rust,source=bin \
-    cargo build-bpf --manifest-path "bridge/Cargo.toml" && \
-    mkdir -p /opt/solana/deps && \
-    cp target/deploy/spl_bridge.so /opt/solana/deps/spl_bridge.so
+RUN mkdir -p /opt/solana/deps && \
+	curl -Lo /opt/solana/deps/spl_bridge.so https://github.com/certusone/wormhole-networks/raw/master/mainnetv1/artifacts/solana/bridge.so
+
+# COPY bridge bridge
+# COPY agent agent
+# COPY cli cli
+# COPY Cargo.toml .
+# COPY Cargo.lock .
+#
+# RUN --mount=type=cache,target=/usr/local,from=rust,source=/usr/local \
+#     --mount=type=cache,target=/root/.cache \
+#     --mount=type=cache,target=bridge/target \
+#     --mount=type=cache,target=target \
+#     --mount=type=cache,target=bin,from=rust,source=bin \
+#     cargo build-bpf --manifest-path "bridge/Cargo.toml" && \
+#     mkdir -p /opt/solana/deps && \
+#     cp target/deploy/spl_bridge.so /opt/solana/deps/spl_bridge.so

+ 0 - 6
solana/devnet_setup.sh

@@ -49,12 +49,6 @@ echo "Created wrapped token $wrapped_token"
 wrapped_account=$(cli create-account --seed=934893 "$wrapped_token" | grep 'Creating account' | awk '{ print $3 }')
 echo "Created wrapped token account $wrapped_account"
 
-# Create wrapped asset and token account for Terra tokens (3 for Terra, 8 for precision)
-wrapped_terra_token=$(cli create-wrapped "$bridge_address" 3 8 0000000000000000000000003b1a7485c6162c5883ee45fb2d7477a87d8a4ce5 | grep 'Wrapped Mint address' | awk '{ print $4 }')
-echo "Created wrapped token for Terra $wrapped_terra_token"
-wrapped_terra_account=$(cli create-account --seed=736251 "$wrapped_terra_token" | grep 'Creating account' | awk '{ print $3 }')
-echo "Created wrapped token account for Terra $wrapped_terra_account"
-
 # Let k8s startup probe succeed
 nc -l -p 2000
 

+ 0 - 3
terra/.dockerignore

@@ -1,3 +0,0 @@
-target
-tools/node_modules
-tools/dist

+ 0 - 1236
terra/Cargo.lock

@@ -1,1236 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "addr2line"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
-dependencies = [
- "gimli 0.23.0",
-]
-
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
-[[package]]
-name = "arrayref"
-version = "0.3.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
-
-[[package]]
-name = "arrayvec"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
-
-[[package]]
-name = "autocfg"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
-
-[[package]]
-name = "backtrace"
-version = "0.3.56"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
-dependencies = [
- "addr2line",
- "cfg-if 1.0.0",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
-]
-
-[[package]]
-name = "base64"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
-
-[[package]]
-name = "bincode"
-version = "1.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
-dependencies = [
- "byteorder",
- "serde",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-
-[[package]]
-name = "bitvec"
-version = "0.18.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98fcd36dda4e17b7d7abc64cb549bf0201f4ab71e00700c798ca7e62ed3761fa"
-dependencies = [
- "funty",
- "radium",
- "wyz",
-]
-
-[[package]]
-name = "blake3"
-version = "0.3.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f"
-dependencies = [
- "arrayref",
- "arrayvec",
- "cc",
- "cfg-if 0.1.10",
- "constant_time_eq",
- "crypto-mac 0.8.0",
- "digest 0.9.0",
-]
-
-[[package]]
-name = "block-buffer"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
-dependencies = [
- "block-padding",
- "generic-array 0.14.4",
-]
-
-[[package]]
-name = "block-padding"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
-
-[[package]]
-name = "byteorder"
-version = "1.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
-
-[[package]]
-name = "cc"
-version = "1.0.67"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
-
-[[package]]
-name = "cfg-if"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "constant_time_eq"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
-
-[[package]]
-name = "cosmwasm-std"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f85908a2696117c8f2c1b3ce201d34a1aa9a6b3c1583a65cfb794ec66e1cfde4"
-dependencies = [
- "base64",
- "schemars",
- "serde",
- "serde-json-wasm",
- "snafu",
-]
-
-[[package]]
-name = "cosmwasm-storage"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e103531a2ce636e86b7639cec25d348c4d360832ab8e0e7f9a6e00f08aac1379"
-dependencies = [
- "cosmwasm-std",
- "serde",
-]
-
-[[package]]
-name = "cosmwasm-vm"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12d56a7ad7bbbf04b94a782f25fe50a9372067737f661931acf9d30668003efd"
-dependencies = [
- "cosmwasm-std",
- "hex",
- "memmap",
- "parity-wasm",
- "schemars",
- "serde",
- "serde_json",
- "sha2",
- "snafu",
- "wasmer-clif-backend",
- "wasmer-middleware-common",
- "wasmer-runtime-core",
-]
-
-[[package]]
-name = "cpuid-bool"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
-
-[[package]]
-name = "cranelift-bforest"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818"
-dependencies = [
- "cranelift-entity",
-]
-
-[[package]]
-name = "cranelift-codegen"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e"
-dependencies = [
- "byteorder",
- "cranelift-bforest",
- "cranelift-codegen-meta",
- "cranelift-codegen-shared",
- "cranelift-entity",
- "gimli 0.20.0",
- "log",
- "smallvec",
- "target-lexicon",
- "thiserror",
-]
-
-[[package]]
-name = "cranelift-codegen-meta"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9"
-dependencies = [
- "cranelift-codegen-shared",
- "cranelift-entity",
-]
-
-[[package]]
-name = "cranelift-codegen-shared"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d"
-
-[[package]]
-name = "cranelift-entity"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38"
-
-[[package]]
-name = "cranelift-native"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b"
-dependencies = [
- "cranelift-codegen",
- "raw-cpuid",
- "target-lexicon",
-]
-
-[[package]]
-name = "crossbeam-channel"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
-dependencies = [
- "cfg-if 1.0.0",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-deque"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
-dependencies = [
- "cfg-if 1.0.0",
- "crossbeam-epoch",
- "crossbeam-utils",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
-dependencies = [
- "cfg-if 1.0.0",
- "crossbeam-utils",
- "lazy_static",
- "memoffset",
- "scopeguard",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
-dependencies = [
- "autocfg",
- "cfg-if 1.0.0",
- "lazy_static",
-]
-
-[[package]]
-name = "crypto-mac"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
-dependencies = [
- "generic-array 0.14.4",
- "subtle",
-]
-
-[[package]]
-name = "crypto-mac"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca"
-dependencies = [
- "generic-array 0.14.4",
- "subtle",
-]
-
-[[package]]
-name = "cw0"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b469bb5d63a036339cbb3042fb1254016b5d4889b15f420fea1c1339497b19de"
-dependencies = [
- "cosmwasm-std",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw2"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1a5dd960277ae9180e0077fcc8769928da2d9c1c42cc8c8c734c625c6f2a90b"
-dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw20"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffd85ddcac2bf5c1899dd13e52985d260dae02db21a618d9d4c2c36dab63a915"
-dependencies = [
- "cosmwasm-std",
- "cw0",
- "schemars",
- "serde",
-]
-
-[[package]]
-name = "cw20-base"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c09657718df5243810b10d49359a352eed197966f578366624198fe1740fb52"
-dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
- "cw0",
- "cw2",
- "cw20",
- "schemars",
- "serde",
- "snafu",
-]
-
-[[package]]
-name = "cw20-wrapped"
-version = "0.1.0"
-dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
- "cosmwasm-vm",
- "cw20",
- "cw20-base",
- "schemars",
- "serde",
- "thiserror",
-]
-
-[[package]]
-name = "digest"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
-dependencies = [
- "generic-array 0.12.4",
-]
-
-[[package]]
-name = "digest"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
-dependencies = [
- "generic-array 0.14.4",
-]
-
-[[package]]
-name = "doc-comment"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
-
-[[package]]
-name = "ecdsa"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87bf8bfb05ea8a6f74ddf48c7d1774851ba77bbe51ac984fdfa6c30310e1ff5f"
-dependencies = [
- "elliptic-curve",
- "hmac",
- "signature",
-]
-
-[[package]]
-name = "either"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
-
-[[package]]
-name = "elliptic-curve"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "396db09c483e7fca5d4fdb9112685632b3e76c9a607a2649c1bf904404a01366"
-dependencies = [
- "bitvec",
- "digest 0.9.0",
- "ff",
- "generic-array 0.14.4",
- "group",
- "rand_core",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "errno"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe"
-dependencies = [
- "errno-dragonfly",
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
-dependencies = [
- "gcc",
- "libc",
-]
-
-[[package]]
-name = "ff"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef"
-dependencies = [
- "bitvec",
- "rand_core",
- "subtle",
-]
-
-[[package]]
-name = "funty"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
-
-[[package]]
-name = "gcc"
-version = "0.3.55"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
-
-[[package]]
-name = "generic-array"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
-dependencies = [
- "typenum",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.14.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
-dependencies = [
- "typenum",
- "version_check",
-]
-
-[[package]]
-name = "gimli"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633"
-dependencies = [
- "byteorder",
- "indexmap",
-]
-
-[[package]]
-name = "gimli"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
-
-[[package]]
-name = "group"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1"
-dependencies = [
- "ff",
- "rand_core",
- "subtle",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
-name = "hmac"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff"
-dependencies = [
- "crypto-mac 0.9.1",
- "digest 0.9.0",
-]
-
-[[package]]
-name = "indexmap"
-version = "1.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
-dependencies = [
- "autocfg",
- "hashbrown",
- "serde",
-]
-
-[[package]]
-name = "itoa"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
-
-[[package]]
-name = "k256"
-version = "0.5.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3934640b1efbc660af5889d041854b6985d403771dc4d5fee984e13e8f82f313"
-dependencies = [
- "cfg-if 1.0.0",
- "ecdsa",
- "elliptic-curve",
-]
-
-[[package]]
-name = "keccak"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
-[[package]]
-name = "libc"
-version = "0.2.91"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
-
-[[package]]
-name = "lock_api"
-version = "0.3.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
-dependencies = [
- "scopeguard",
-]
-
-[[package]]
-name = "log"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if 1.0.0",
-]
-
-[[package]]
-name = "memmap"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "memoffset"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
-dependencies = [
- "adler",
- "autocfg",
-]
-
-[[package]]
-name = "nix"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
-dependencies = [
- "bitflags",
- "cc",
- "cfg-if 0.1.10",
- "libc",
- "void",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
-[[package]]
-name = "object"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
-
-[[package]]
-name = "opaque-debug"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
-
-[[package]]
-name = "page_size"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"
-dependencies = [
- "libc",
- "winapi",
-]
-
-[[package]]
-name = "parity-wasm"
-version = "0.41.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865"
-
-[[package]]
-name = "parking_lot"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
-dependencies = [
- "lock_api",
- "parking_lot_core",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
-dependencies = [
- "cfg-if 0.1.10",
- "cloudabi",
- "libc",
- "redox_syscall",
- "smallvec",
- "winapi",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "radium"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
-
-[[package]]
-name = "rand_core"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-
-[[package]]
-name = "raw-cpuid"
-version = "7.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "beb71f708fe39b2c5e98076204c3cc094ee5a4c12c4cdb119a2b72dc34164f41"
-dependencies = [
- "bitflags",
- "cc",
- "rustc_version",
-]
-
-[[package]]
-name = "rayon"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
-dependencies = [
- "autocfg",
- "crossbeam-deque",
- "either",
- "rayon-core",
-]
-
-[[package]]
-name = "rayon-core"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
-dependencies = [
- "crossbeam-channel",
- "crossbeam-deque",
- "crossbeam-utils",
- "lazy_static",
- "num_cpus",
-]
-
-[[package]]
-name = "redox_syscall"
-version = "0.1.57"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
-
-[[package]]
-name = "rustc-demangle"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
-
-[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
-
-[[package]]
-name = "schemars"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be77ed66abed6954aabf6a3e31a84706bedbf93750d267e92ef4a6d90bbd6a61"
-dependencies = [
- "schemars_derive",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "schemars_derive"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11af7a475c9ee266cfaa9e303a47c830ebe072bf3101ab907a7b7b9d816fa01d"
-dependencies = [
- "proc-macro2",
- "quote",
- "serde_derive_internals",
- "syn",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-
-[[package]]
-name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-
-[[package]]
-name = "serde"
-version = "1.0.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde-bench"
-version = "0.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d733da87e79faaac25616e33d26299a41143fd4cd42746cbb0e91d8feea243fd"
-dependencies = [
- "byteorder",
- "serde",
-]
-
-[[package]]
-name = "serde-json-wasm"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "120bad73306616e91acd7ceed522ba96032a51cffeef3cc813de7f367df71e37"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde_bytes"
-version = "0.11.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
-dependencies = [
- "serde",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.125"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_derive_internals"
-version = "0.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
-dependencies = [
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "sha2"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
-dependencies = [
- "block-buffer",
- "cfg-if 1.0.0",
- "cpuid-bool",
- "digest 0.9.0",
- "opaque-debug",
-]
-
-[[package]]
-name = "sha3"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
-dependencies = [
- "block-buffer",
- "digest 0.9.0",
- "keccak",
- "opaque-debug",
-]
-
-[[package]]
-name = "signature"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
-dependencies = [
- "digest 0.9.0",
- "rand_core",
-]
-
-[[package]]
-name = "smallvec"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
-
-[[package]]
-name = "snafu"
-version = "0.6.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
-dependencies = [
- "backtrace",
- "doc-comment",
- "snafu-derive",
-]
-
-[[package]]
-name = "snafu-derive"
-version = "0.6.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "subtle"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
-
-[[package]]
-name = "syn"
-version = "1.0.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "target-lexicon"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d"
-
-[[package]]
-name = "thiserror"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "typenum"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
-
-[[package]]
-name = "version_check"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
-
-[[package]]
-name = "void"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
-
-[[package]]
-name = "wasmer-clif-backend"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "691ea323652d540a10722066dbf049936f4367bb22a96f8992a262a942a8b11b"
-dependencies = [
- "byteorder",
- "cranelift-codegen",
- "cranelift-entity",
- "cranelift-native",
- "libc",
- "nix",
- "rayon",
- "serde",
- "serde-bench",
- "serde_bytes",
- "serde_derive",
- "target-lexicon",
- "wasmer-clif-fork-frontend",
- "wasmer-clif-fork-wasm",
- "wasmer-runtime-core",
- "wasmer-win-exception-handler",
- "wasmparser",
- "winapi",
-]
-
-[[package]]
-name = "wasmer-clif-fork-frontend"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c23f2824f354a00a77e4b040eef6e1d4c595a8a3e9013bad65199cc8dade9a5a"
-dependencies = [
- "cranelift-codegen",
- "log",
- "smallvec",
- "target-lexicon",
-]
-
-[[package]]
-name = "wasmer-clif-fork-wasm"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a35e21d3aebc51cc6ebc0e830cf8458a9891c3482fb3c65ad18d408102929ae5"
-dependencies = [
- "cranelift-codegen",
- "cranelift-entity",
- "log",
- "thiserror",
- "wasmer-clif-fork-frontend",
- "wasmparser",
-]
-
-[[package]]
-name = "wasmer-middleware-common"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd94068186b25fbe5213442648ffe0fa65ee77389bed020404486fd22056cc87"
-dependencies = [
- "wasmer-runtime-core",
-]
-
-[[package]]
-name = "wasmer-runtime-core"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45d4253f097502423d8b19d54cb18745f61b984b9dbce32424cba7945cfef367"
-dependencies = [
- "bincode",
- "blake3",
- "cc",
- "digest 0.8.1",
- "errno",
- "hex",
- "indexmap",
- "lazy_static",
- "libc",
- "nix",
- "page_size",
- "parking_lot",
- "rustc_version",
- "serde",
- "serde-bench",
- "serde_bytes",
- "serde_derive",
- "smallvec",
- "target-lexicon",
- "wasmparser",
- "winapi",
-]
-
-[[package]]
-name = "wasmer-win-exception-handler"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf22ce6dc66d893099aac853d451bf9443fa8f5443f5bf4fc63f3aebd7b592b1"
-dependencies = [
- "cc",
- "libc",
- "wasmer-runtime-core",
- "winapi",
-]
-
-[[package]]
-name = "wasmparser"
-version = "0.51.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aeb1956b19469d1c5e63e459d29e7b5aa0f558d9f16fcef09736f8a265e6c10a"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "wormhole"
-version = "0.1.0"
-dependencies = [
- "cosmwasm-std",
- "cosmwasm-storage",
- "cosmwasm-vm",
- "cw20",
- "cw20-base",
- "cw20-wrapped",
- "generic-array 0.14.4",
- "hex",
- "k256",
- "lazy_static",
- "schemars",
- "serde",
- "serde_json",
- "sha3",
- "thiserror",
-]
-
-[[package]]
-name = "wyz"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
-
-[[package]]
-name = "zeroize"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"

+ 0 - 13
terra/Cargo.toml

@@ -1,13 +0,0 @@
-[workspace]
-members = ["contracts/cw20-wrapped", "contracts/wormhole"]
-
-[profile.release]
-opt-level = 3
-debug = false
-rpath = false
-lto = true
-debug-assertions = false
-codegen-units = 1
-panic = 'abort'
-incremental = false
-overflow-checks = true

+ 0 - 26
terra/Dockerfile

@@ -1,26 +0,0 @@
-# This is a multi-stage docker file, first stage builds contracts
-# And the second one creates node.js environment to deploy them
-FROM cosmwasm/workspace-optimizer:0.10.4@sha256:a976db4ee7add887a6af26724b804bbd9e9d534554506447e72ac57e65357db9 AS builder
-ADD Cargo.lock /code/
-ADD Cargo.toml /code/
-ADD contracts /code/contracts
-RUN optimize_workspace.sh
-
-# Contract deployment stage
-FROM node:14@sha256:04a33dac55af8d3170bffc91ca31fe8000b96ae1bab1a090deb920ca2ca2a38e
-
-RUN npm update && npm i -g typescript ts-node
-
-WORKDIR /app/tools
-
-COPY --from=builder /code/artifacts /app/artifacts
-ADD ./artifacts/cw20_base.wasm /app/artifacts/
-ADD ./tools /app/tools
-
-RUN chmod +x /app/tools/deploy.sh
-
-RUN npm install
-
-RUN ts-node --version
-
-ENTRYPOINT /app/tools/deploy.sh

+ 0 - 25
terra/Tiltfile

@@ -1,25 +0,0 @@
-# Smaller environment only with terra and no bridge
-
-docker_build(
-    ref = "terra-image",
-    context = "./devnet",
-    dockerfile = "./devnet/Dockerfile",
-)
-
-docker_build(
-    ref = "terra-contracts",
-    context = ".",
-    dockerfile = "./Dockerfile",
-)
-
-k8s_yaml("../devnet/terra-devnet.yaml")
-
-k8s_resource(
-    "terra-lcd",
-    port_forwards=[port_forward(1317, name="Terra LCD interface [:1317]")]
-)
-
-k8s_resource(
-    "terra-terrad",
-    port_forwards=[port_forward(26657, name="Terra RPC [:26657]")]
-)

BIN
terra/artifacts/cw20_base.wasm


+ 0 - 50
terra/contracts/README.md

@@ -1,50 +0,0 @@
-# Terra Wormhole Contracts
-
-The Wormhole Terra integration is developed and maintained by Everstake / @ysavchenko.
-
-## Summary
-
-To facilitate token exchange via the Wormhole bridge blockchains must provide a set of smart contracts to process bridge commands on chain. Here are such contracts for the Terra blockchain.
-
-The first contract, `cw20-wrapped` is basically a `cw20-base` contract ([see here](https://github.com/CosmWasm/cosmwasm-plus/tree/master/contracts/cw20-base)) instantiated for every new token type issued on the blockchain by the bridge. And the second one, `wormhole` provides the bridge functionality itself:
-
-- It locks tokens on the Terra blockchain when they are sent out to other blockchains
-- It sends out wrapped or original tokens (depending on the blockchain origin of the token) to recipients when receiving tokens from the other blockchains
-
-## Details
-
-### `cw20-wrapped`
-
-This contract mostly wraps functionality of the `cw20-base` contract with the following differences:
-
-- It stores `WrappedAssetInfo` state with information about the source blockchain, asset address on this blockchain and the `wormhole` contract address
-- Once initialized it calls the hook action specified in the initialization params (`init_hook` field). It is used to record newly instantiated contract's address in the `wormhole` contract
-- Full mint authority is provided to the `wormhole` contract
-
-### `wormhole`
-
-This contract controls token transfers, minting and burning as well as maintaining the list of guardians: off-chain
-entities identified by their public key hashes, majority of whom can issue commands to the contract.
-
-`wormhole` bridge processes the following instructions.
-
-#### `SubmitVAA`
-
-Receives VAAs from the guardians (read about VAAs [here](../../docs/protocol.md)), verifies and processes them. In the current bridge implementation VAAs can trigger the following actions:
-
-- Send token to the Terra recipient
-- Update the list of guardians
-
-Sending tokens to the Terra recipient is handled by the `vaa_transfer` method. For the native Terra tokens it simply transfers the corresponding amount from its balance. For the non-native tokens `wormhole` either mints the corresponding amount from the already deployed `cw20-wrapped` contract or deploys a new one with the mint amount in the initialization message.
-
-#### `RegisterAssetHook`
-
-Gets called from the `cw20-wrapped` constructor to record its address in the contract's directory of wrapped assets. It is used later to check whether the wrapped contract for the asset is already deployed on Terra blockchain or not.
-
-#### `LockAssets`
-
-Called to initiate token transfer from the Terra blockchain to other blockchains. Caller must provide allowance to the `wormhole` contract to spend tokens, then the contract either transfers (if it is a native token) or burns it (if it is a wrapped token from the different blockchain). Then the information is logged to be read by the guardians operating the bridge, which triggers sending VAAs to the destination blockchain.
-
-#### `SetActive`
-
-Safety feature to turn off the `wormhole` contract in the case of any issues found in production. Only the owner can send this message, once the contract is inactive it stops processing token transfer commands.

+ 0 - 5
terra/contracts/cw20-wrapped/.cargo/config

@@ -1,5 +0,0 @@
-[alias]
-wasm = "build --release --target wasm32-unknown-unknown"
-wasm-debug = "build --target wasm32-unknown-unknown"
-unit-test = "test --lib --features backtraces"
-integration-test = "test --test integration"

+ 0 - 26
terra/contracts/cw20-wrapped/Cargo.toml

@@ -1,26 +0,0 @@
-[package]
-name = "cw20-wrapped"
-version = "0.1.0"
-authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
-edition = "2018"
-description = "Wrapped CW20 token contract"
-
-[lib]
-crate-type = ["cdylib", "rlib"]
-
-[features]
-backtraces = ["cosmwasm-std/backtraces"]
-# use library feature to disable all init/handle/query exports
-library = []
-
-[dependencies]
-cosmwasm-std = { version = "0.10.0" }
-cosmwasm-storage = { version = "0.10.0" }
-schemars = "0.7"
-serde = { version = "1.0.103", default-features = false, features = ["derive"] }
-cw20 = "0.2.0"
-cw20-base = { version = "0.2.0", features = ["library"] }
-thiserror = { version = "1.0.20" }
-
-[dev-dependencies]
-cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }

+ 0 - 302
terra/contracts/cw20-wrapped/src/contract.rs

@@ -1,302 +0,0 @@
-use cosmwasm_std::{
-    to_binary, Api, Binary, CosmosMsg, Env, Extern, HandleResponse, HumanAddr, InitResponse,
-    Querier, StdError, StdResult, Storage, Uint128, WasmMsg,
-};
-
-use cw20_base::allowances::{
-    handle_burn_from, handle_decrease_allowance, handle_increase_allowance, handle_send_from,
-    handle_transfer_from, query_allowance,
-};
-use cw20_base::contract::{
-    handle_mint, handle_send, handle_transfer, query_balance, query_token_info,
-};
-use cw20_base::state::{token_info, MinterData, TokenInfo};
-
-use crate::msg::{HandleMsg, InitMsg, QueryMsg, WrappedAssetInfoResponse};
-use crate::state::{wrapped_asset_info, wrapped_asset_info_read, WrappedAssetInfo};
-use std::string::String;
-
-pub fn init<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    msg: InitMsg,
-) -> StdResult<InitResponse> {
-    // store token info using cw20-base format
-    let data = TokenInfo {
-        name: String::from("Wormhole Wrapped"),
-        symbol: String::from("WWT"),
-        decimals: msg.decimals,
-        total_supply: Uint128(0),
-        // set creator as minter
-        mint: Some(MinterData {
-            minter: deps.api.canonical_address(&env.message.sender)?,
-            cap: None,
-        }),
-    };
-    token_info(&mut deps.storage).save(&data)?;
-
-    // save wrapped asset info
-    let data = WrappedAssetInfo {
-        asset_chain: msg.asset_chain,
-        asset_address: msg.asset_address,
-        bridge: deps.api.canonical_address(&env.message.sender)?,
-    };
-    wrapped_asset_info(&mut deps.storage).save(&data)?;
-
-    if let Some(mint_info) = msg.mint {
-        handle_mint(deps, env, mint_info.recipient, mint_info.amount)?;
-    }
-
-    if let Some(hook) = msg.init_hook {
-        Ok(InitResponse {
-            messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr: hook.contract_addr,
-                msg: hook.msg,
-                send: vec![],
-            })],
-            log: vec![],
-        })
-    } else {
-        Ok(InitResponse::default())
-    }
-}
-
-pub fn handle<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    msg: HandleMsg,
-) -> StdResult<HandleResponse> {
-    match msg {
-        // these all come from cw20-base to implement the cw20 standard
-        HandleMsg::Transfer { recipient, amount } => {
-            Ok(handle_transfer(deps, env, recipient, amount)?)
-        }
-        HandleMsg::Burn { account, amount } => Ok(handle_burn_from(deps, env, account, amount)?),
-        HandleMsg::Send {
-            contract,
-            amount,
-            msg,
-        } => Ok(handle_send(deps, env, contract, amount, msg)?),
-        HandleMsg::Mint { recipient, amount } => handle_mint_wrapped(deps, env, recipient, amount),
-        HandleMsg::IncreaseAllowance {
-            spender,
-            amount,
-            expires,
-        } => Ok(handle_increase_allowance(
-            deps, env, spender, amount, expires,
-        )?),
-        HandleMsg::DecreaseAllowance {
-            spender,
-            amount,
-            expires,
-        } => Ok(handle_decrease_allowance(
-            deps, env, spender, amount, expires,
-        )?),
-        HandleMsg::TransferFrom {
-            owner,
-            recipient,
-            amount,
-        } => Ok(handle_transfer_from(deps, env, owner, recipient, amount)?),
-        HandleMsg::BurnFrom { owner, amount } => Ok(handle_burn_from(deps, env, owner, amount)?),
-        HandleMsg::SendFrom {
-            owner,
-            contract,
-            amount,
-            msg,
-        } => Ok(handle_send_from(deps, env, owner, contract, amount, msg)?),
-    }
-}
-
-fn handle_mint_wrapped<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    recipient: HumanAddr,
-    amount: Uint128,
-) -> StdResult<HandleResponse> {
-    // Only bridge can mint
-    let wrapped_info = wrapped_asset_info_read(&deps.storage).load()?;
-    if wrapped_info.bridge != deps.api.canonical_address(&env.message.sender)? {
-        return Err(StdError::unauthorized());
-    }
-
-    Ok(handle_mint(deps, env, recipient, amount)?)
-}
-
-pub fn query<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-    msg: QueryMsg,
-) -> StdResult<Binary> {
-    match msg {
-        QueryMsg::WrappedAssetInfo {} => to_binary(&query_wrapped_asset_info(deps)?),
-        // inherited from cw20-base
-        QueryMsg::TokenInfo {} => to_binary(&query_token_info(deps)?),
-        QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
-        QueryMsg::Allowance { owner, spender } => {
-            to_binary(&query_allowance(deps, owner, spender)?)
-        }
-    }
-}
-
-pub fn query_wrapped_asset_info<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-) -> StdResult<WrappedAssetInfoResponse> {
-    let info = wrapped_asset_info_read(&deps.storage).load()?;
-    let res = WrappedAssetInfoResponse {
-        asset_chain: info.asset_chain,
-        asset_address: info.asset_address,
-        bridge: deps.api.human_address(&info.bridge)?,
-    };
-    Ok(res)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use cosmwasm_std::testing::{mock_dependencies, mock_env};
-    use cosmwasm_std::HumanAddr;
-    use cw20::TokenInfoResponse;
-
-    const CANONICAL_LENGTH: usize = 20;
-
-    fn get_balance<S: Storage, A: Api, Q: Querier, T: Into<HumanAddr>>(
-        deps: &Extern<S, A, Q>,
-        address: T,
-    ) -> Uint128 {
-        query_balance(&deps, address.into()).unwrap().balance
-    }
-
-    fn do_init<S: Storage, A: Api, Q: Querier>(deps: &mut Extern<S, A, Q>, creator: &HumanAddr) {
-        let init_msg = InitMsg {
-            asset_chain: 1,
-            asset_address: vec![1; 32].into(),
-            decimals: 10,
-            mint: None,
-            init_hook: None,
-        };
-        let env = mock_env(creator, &[]);
-        let res = init(deps, env, init_msg).unwrap();
-        assert_eq!(0, res.messages.len());
-
-        assert_eq!(
-            query_token_info(&deps).unwrap(),
-            TokenInfoResponse {
-                name: "Wormhole Wrapped".to_string(),
-                symbol: "WWT".to_string(),
-                decimals: 10,
-                total_supply: Uint128::from(0u128),
-            }
-        );
-
-        assert_eq!(
-            query_wrapped_asset_info(&deps).unwrap(),
-            WrappedAssetInfoResponse {
-                asset_chain: 1,
-                asset_address: vec![1; 32].into(),
-                bridge: creator.clone(),
-            }
-        );
-    }
-
-    fn do_init_and_mint<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        creator: &HumanAddr,
-        mint_to: &HumanAddr,
-        amount: Uint128,
-    ) {
-        do_init(deps, creator);
-
-        let msg = HandleMsg::Mint {
-            recipient: mint_to.clone(),
-            amount,
-        };
-
-        let env = mock_env(&creator, &[]);
-        let res = handle(deps, env, msg.clone()).unwrap();
-        assert_eq!(0, res.messages.len());
-        assert_eq!(get_balance(deps, mint_to), amount);
-
-        assert_eq!(
-            query_token_info(&deps).unwrap(),
-            TokenInfoResponse {
-                name: "Wormhole Wrapped".to_string(),
-                symbol: "WWT".to_string(),
-                decimals: 10,
-                total_supply: amount,
-            }
-        );
-    }
-
-    #[test]
-    fn can_mint_by_minter() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let minter = HumanAddr::from("minter");
-        let recipient = HumanAddr::from("recipient");
-        let amount = Uint128(222_222_222);
-        do_init_and_mint(&mut deps, &minter, &recipient, amount);
-    }
-
-    #[test]
-    fn others_cannot_mint() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let minter = HumanAddr::from("minter");
-        let recipient = HumanAddr::from("recipient");
-        do_init(&mut deps, &minter);
-
-        let amount = Uint128(222_222_222);
-        let msg = HandleMsg::Mint {
-            recipient: recipient.clone(),
-            amount,
-        };
-
-        let other_address = HumanAddr::from("other");
-        let env = mock_env(&other_address, &[]);
-        let res = handle(&mut deps, env, msg);
-        assert_eq!(
-            format!("{}", res.unwrap_err()),
-            format!("{}", crate::error::ContractError::Unauthorized {})
-        );
-    }
-
-    #[test]
-    fn transfer_balance_success() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let minter = HumanAddr::from("minter");
-        let owner = HumanAddr::from("owner");
-        let amount_initial = Uint128(222_222_222);
-        do_init_and_mint(&mut deps, &minter, &owner, amount_initial);
-
-        // Transfer
-        let recipient = HumanAddr::from("recipient");
-        let amount_transfer = Uint128(222_222);
-        let msg = HandleMsg::Transfer {
-            recipient: recipient.clone(),
-            amount: amount_transfer,
-        };
-
-        let env = mock_env(&owner, &[]);
-        let res = handle(&mut deps, env, msg.clone()).unwrap();
-        assert_eq!(0, res.messages.len());
-        assert_eq!(get_balance(&deps, owner), Uint128(222_000_000));
-        assert_eq!(get_balance(&deps, recipient), amount_transfer);
-    }
-
-    #[test]
-    fn transfer_balance_not_enough() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let minter = HumanAddr::from("minter");
-        let owner = HumanAddr::from("owner");
-        let amount_initial = Uint128(222_221);
-        do_init_and_mint(&mut deps, &minter, &owner, amount_initial);
-
-        // Transfer
-        let recipient = HumanAddr::from("recipient");
-        let amount_transfer = Uint128(222_222);
-        let msg = HandleMsg::Transfer {
-            recipient: recipient.clone(),
-            amount: amount_transfer,
-        };
-
-        let env = mock_env(&owner, &[]);
-        let _ = handle(&mut deps, env, msg.clone()).unwrap_err(); // Will panic if no error
-    }
-}

+ 0 - 27
terra/contracts/cw20-wrapped/src/error.rs

@@ -1,27 +0,0 @@
-use cosmwasm_std::StdError;
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum ContractError {
-    // CW20 errors
-    #[error("{0}")]
-    Std(#[from] StdError),
-
-    #[error("Unauthorized")]
-    Unauthorized {},
-
-    #[error("Cannot set to own account")]
-    CannotSetOwnAccount {},
-
-    #[error("Invalid zero amount")]
-    InvalidZeroAmount {},
-
-    #[error("Allowance is expired")]
-    Expired {},
-
-    #[error("No allowance for this account")]
-    NoAllowance {},
-
-    #[error("Minting cannot exceed the cap")]
-    CannotExceedCap {},
-}

+ 0 - 9
terra/contracts/cw20-wrapped/src/lib.rs

@@ -1,9 +0,0 @@
-pub mod contract;
-mod error;
-pub mod msg;
-pub mod state;
-
-pub use crate::error::ContractError;
-
-#[cfg(all(target_arch = "wasm32", not(feature = "library")))]
-cosmwasm_std::create_entry_points!(contract);

+ 0 - 111
terra/contracts/cw20-wrapped/src/msg.rs

@@ -1,111 +0,0 @@
-#![allow(clippy::field_reassign_with_default)]
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use cosmwasm_std::{Binary, HumanAddr, Uint128};
-use cw20::Expiration;
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct InitMsg {
-    pub asset_chain: u8,
-    pub asset_address: Binary,
-    pub decimals: u8,
-    pub mint: Option<InitMint>,
-    pub init_hook: Option<InitHook>,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct InitHook {
-    pub msg: Binary,
-    pub contract_addr: HumanAddr,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct InitMint {
-    pub recipient: HumanAddr,
-    pub amount: Uint128,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum HandleMsg {
-    /// Implements CW20. Transfer is a base message to move tokens to another account without triggering actions
-    Transfer {
-        recipient: HumanAddr,
-        amount: Uint128,
-    },
-    /// Slightly different than CW20. Burn is a base message to destroy tokens forever
-    Burn { account: HumanAddr, amount: Uint128 },
-    /// Implements CW20. Send is a base message to transfer tokens to a contract and trigger an action
-    /// on the receiving contract.
-    Send {
-        contract: HumanAddr,
-        amount: Uint128,
-        msg: Option<Binary>,
-    },
-    /// Implements CW20 "mintable" extension. If authorized, creates amount new tokens
-    /// and adds to the recipient balance.
-    Mint {
-        recipient: HumanAddr,
-        amount: Uint128,
-    },
-    /// Implements CW20 "approval" extension. Allows spender to access an additional amount tokens
-    /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
-    /// expiration with this one.
-    IncreaseAllowance {
-        spender: HumanAddr,
-        amount: Uint128,
-        expires: Option<Expiration>,
-    },
-    /// Implements CW20 "approval" extension. Lowers the spender's access of tokens
-    /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
-    /// allowance expiration with this one.
-    DecreaseAllowance {
-        spender: HumanAddr,
-        amount: Uint128,
-        expires: Option<Expiration>,
-    },
-    /// Implements CW20 "approval" extension. Transfers amount tokens from owner -> recipient
-    /// if `env.sender` has sufficient pre-approval.
-    TransferFrom {
-        owner: HumanAddr,
-        recipient: HumanAddr,
-        amount: Uint128,
-    },
-    /// Implements CW20 "approval" extension. Sends amount tokens from owner -> contract
-    /// if `env.sender` has sufficient pre-approval.
-    SendFrom {
-        owner: HumanAddr,
-        contract: HumanAddr,
-        amount: Uint128,
-        msg: Option<Binary>,
-    },
-    /// Implements CW20 "approval" extension. Destroys tokens forever
-    BurnFrom { owner: HumanAddr, amount: Uint128 },
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum QueryMsg {
-    // Generic information about the wrapped asset
-    WrappedAssetInfo {},
-    /// Implements CW20. Returns the current balance of the given address, 0 if unset.
-    Balance {
-        address: HumanAddr,
-    },
-    /// Implements CW20. Returns metadata on the contract - name, decimals, supply, etc.
-    TokenInfo {},
-    /// Implements CW20 "allowance" extension.
-    /// Returns how much spender can use from owner account, 0 if unset.
-    Allowance {
-        owner: HumanAddr,
-        spender: HumanAddr,
-    },
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct WrappedAssetInfoResponse {
-    pub asset_chain: u8,        // Asset chain id
-    pub asset_address: Binary, // Asset smart contract address in the original chain
-    pub bridge: HumanAddr,      // Bridge address, authorized to mint and burn wrapped tokens
-}

+ 0 - 25
terra/contracts/cw20-wrapped/src/state.rs

@@ -1,25 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use cosmwasm_std::{CanonicalAddr, ReadonlyStorage, Storage, Binary};
-use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton};
-
-pub const KEY_WRAPPED_ASSET: &[u8] = b"wrappedAsset";
-
-// Created at initialization and reference original asset and bridge address
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct WrappedAssetInfo {
-    pub asset_chain: u8,        // Asset chain id
-    pub asset_address: Binary, // Asset smart contract address on the original chain
-    pub bridge: CanonicalAddr,  // Bridge address, authorized to mint and burn wrapped tokens
-}
-
-pub fn wrapped_asset_info<S: Storage>(storage: &mut S) -> Singleton<S, WrappedAssetInfo> {
-    singleton(storage, KEY_WRAPPED_ASSET)
-}
-
-pub fn wrapped_asset_info_read<S: ReadonlyStorage>(
-    storage: &S,
-) -> ReadonlySingleton<S, WrappedAssetInfo> {
-    singleton_read(storage, KEY_WRAPPED_ASSET)
-}

+ 0 - 227
terra/contracts/cw20-wrapped/tests/integration.rs

@@ -1,227 +0,0 @@
-static WASM: &[u8] =
-    include_bytes!("../../../target/wasm32-unknown-unknown/release/cw20_wrapped.wasm");
-
-use cosmwasm_std::{
-    from_slice, Env, HandleResponse, HandleResult, HumanAddr, InitResponse, Uint128, Binary,
-};
-use cosmwasm_storage::to_length_prefixed;
-use cosmwasm_vm::testing::{
-    handle, init, mock_env, mock_instance, query, MockApi, MockQuerier, MockStorage,
-};
-use cosmwasm_vm::{Api, Instance, Storage};
-use cw20_wrapped::msg::{HandleMsg, InitMsg, QueryMsg};
-use cw20_wrapped::state::WrappedAssetInfo;
-use cw20_wrapped::state::KEY_WRAPPED_ASSET;
-use cw20_wrapped::ContractError;
-
-enum TestAddress {
-    INITIALIZER,
-    RECIPIENT,
-    SENDER,
-}
-
-impl TestAddress {
-    fn value(&self) -> HumanAddr {
-        match self {
-            TestAddress::INITIALIZER => HumanAddr::from("addr0000"),
-            TestAddress::RECIPIENT => HumanAddr::from("addr2222"),
-            TestAddress::SENDER => HumanAddr::from("addr3333"),
-        }
-    }
-}
-
-fn mock_env_height(signer: &HumanAddr, height: u64, time: u64) -> Env {
-    let mut env = mock_env(signer, &[]);
-    env.block.height = height;
-    env.block.time = time;
-    env
-}
-
-fn get_wrapped_asset_info<S: Storage>(storage: &S) -> WrappedAssetInfo {
-    let key = to_length_prefixed(KEY_WRAPPED_ASSET);
-    let data = storage
-        .get(&key)
-        .0
-        .expect("error getting data")
-        .expect("data should exist");
-    from_slice(&data).expect("invalid data")
-}
-
-fn do_init(height: u64) -> Instance<MockStorage, MockApi, MockQuerier> {
-    let mut deps = mock_instance(WASM, &[]);
-    let init_msg = InitMsg {
-        asset_chain: 1,
-        asset_address: vec![1; 32].into(),
-        decimals: 10,
-        mint: None,
-        init_hook: None,
-    };
-    let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
-    let res: InitResponse = init(&mut deps, env, init_msg).unwrap();
-    assert_eq!(0, res.messages.len());
-
-    // query the store directly
-    let api = deps.api;
-    deps.with_storage(|storage| {
-        assert_eq!(
-            get_wrapped_asset_info(storage),
-            WrappedAssetInfo {
-                asset_chain: 1,
-                asset_address: vec![1; 32].into(),
-                bridge: api.canonical_address(&TestAddress::INITIALIZER.value()).0?,
-            }
-        );
-        Ok(())
-    })
-    .unwrap();
-    deps
-}
-
-fn do_mint(
-    deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
-    height: u64,
-    recipient: &HumanAddr,
-    amount: &Uint128,
-) {
-    let mint_msg = HandleMsg::Mint {
-        recipient: recipient.clone(),
-        amount: amount.clone(),
-    };
-    let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
-    let handle_response: HandleResponse = handle(deps, env, mint_msg).unwrap();
-    assert_eq!(0, handle_response.messages.len());
-}
-
-fn do_transfer(
-    deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
-    height: u64,
-    sender: &HumanAddr,
-    recipient: &HumanAddr,
-    amount: &Uint128,
-) {
-    let transfer_msg = HandleMsg::Transfer {
-        recipient: recipient.clone(),
-        amount: amount.clone(),
-    };
-    let env = mock_env_height(sender, height, 0);
-    let handle_response: HandleResponse = handle(deps, env, transfer_msg).unwrap();
-    assert_eq!(0, handle_response.messages.len());
-}
-
-fn check_balance(
-    deps: &mut Instance<MockStorage, MockApi, MockQuerier>,
-    address: &HumanAddr,
-    amount: &Uint128,
-) {
-    let query_response = query(
-        deps,
-        QueryMsg::Balance {
-            address: address.clone(),
-        },
-    )
-    .unwrap();
-    assert_eq!(
-        query_response.as_slice(),
-        format!("{{\"balance\":\"{}\"}}", amount.u128()).as_bytes()
-    );
-}
-
-fn check_token_details(deps: &mut Instance<MockStorage, MockApi, MockQuerier>, supply: &Uint128) {
-    let query_response = query(deps, QueryMsg::TokenInfo {}).unwrap();
-    assert_eq!(
-        query_response.as_slice(),
-        format!(
-            "{{\"name\":\"Wormhole Wrapped\",\
-        \"symbol\":\"WWT\",\
-        \"decimals\":10,\
-        \"total_supply\":\"{}\"}}",
-            supply.u128()
-        )
-        .as_bytes()
-    );
-}
-
-#[test]
-fn init_works() {
-    let mut deps = do_init(111);
-    check_token_details(&mut deps, &Uint128(0));
-}
-
-#[test]
-fn query_works() {
-    let mut deps = do_init(111);
-
-    let query_response = query(&mut deps, QueryMsg::WrappedAssetInfo {}).unwrap();
-    assert_eq!(
-        query_response.as_slice(),
-        format!(
-            "{{\"asset_chain\":1,\
-        \"asset_address\":\"{}\",\
-        \"bridge\":\"{}\"}}",
-            Binary::from(vec![1; 32]).to_base64(),
-            TestAddress::INITIALIZER.value().as_str()
-        )
-        .as_bytes()
-    );
-}
-
-#[test]
-fn mint_works() {
-    let mut deps = do_init(111);
-
-    do_mint(
-        &mut deps,
-        112,
-        &TestAddress::RECIPIENT.value(),
-        &Uint128(123_123_123),
-    );
-
-    check_balance(
-        &mut deps,
-        &TestAddress::RECIPIENT.value(),
-        &Uint128(123_123_123),
-    );
-    check_token_details(&mut deps, &Uint128(123_123_123));
-}
-
-#[test]
-fn others_cannot_mint() {
-    let mut deps = do_init(111);
-
-    let mint_msg = HandleMsg::Mint {
-        recipient: TestAddress::RECIPIENT.value(),
-        amount: Uint128(123_123_123),
-    };
-    let env = mock_env_height(&TestAddress::RECIPIENT.value(), 112, 0);
-    let handle_result: HandleResult<HandleResponse> = handle(&mut deps, env, mint_msg);
-    assert_eq!(
-        format!("{}", handle_result.unwrap_err()),
-        format!("{}", ContractError::Unauthorized {})
-    );
-}
-
-#[test]
-fn transfer_works() {
-    let mut deps = do_init(111);
-
-    do_mint(
-        &mut deps,
-        112,
-        &TestAddress::SENDER.value(),
-        &Uint128(123_123_123),
-    );
-    do_transfer(
-        &mut deps,
-        113,
-        &TestAddress::SENDER.value(),
-        &TestAddress::RECIPIENT.value(),
-        &Uint128(123_123_000),
-    );
-
-    check_balance(&mut deps, &TestAddress::SENDER.value(), &Uint128(123));
-    check_balance(
-        &mut deps,
-        &TestAddress::RECIPIENT.value(),
-        &Uint128(123_123_000),
-    );
-}

+ 0 - 5
terra/contracts/wormhole/.cargo/config

@@ -1,5 +0,0 @@
-[alias]
-wasm = "build --release --target wasm32-unknown-unknown"
-wasm-debug = "build --target wasm32-unknown-unknown"
-unit-test = "test --lib --features backtraces"
-integration-test = "test --test integration"

+ 0 - 33
terra/contracts/wormhole/Cargo.toml

@@ -1,33 +0,0 @@
-[package]
-name = "wormhole"
-version = "0.1.0"
-authors = ["Yuriy Savchenko <yuriy.savchenko@gmail.com>"]
-edition = "2018"
-description = "Wormhole contract"
-
-[lib]
-crate-type = ["cdylib", "rlib"]
-
-[features]
-backtraces = ["cosmwasm-std/backtraces"]
-# use library feature to disable all init/handle/query exports
-library = []
-
-[dependencies]
-cosmwasm-std = { version = "0.10.0" }
-cosmwasm-storage = { version = "0.10.0" }
-schemars = "0.7"
-serde = { version = "1.0.103", default-features = false, features = ["derive"] }
-cw20 = "0.2.2"
-cw20-base = { version = "0.2.2", features = ["library"] }
-cw20-wrapped = { path = "../cw20-wrapped", features = ["library"] }
-thiserror = { version = "1.0.20" }
-k256 = { version = "0.5.9", default-features = false, features = ["ecdsa"] }
-sha3 = { version = "0.9.1", default-features = false }
-generic-array = { version = "0.14.4" }
-hex = "0.4.2"
-lazy_static = "1.4.0"
-
-[dev-dependencies]
-cosmwasm-vm = { version = "0.10.0", default-features = false, features = ["default-cranelift"] }
-serde_json = "1.0"

+ 0 - 43
terra/contracts/wormhole/src/byte_utils.rs

@@ -1,43 +0,0 @@
-use cosmwasm_std::CanonicalAddr;
-
-pub trait ByteUtils {
-    fn get_u8(&self, index: usize) -> u8;
-    fn get_u32(&self, index: usize) -> u32;
-    fn get_u128_be(&self, index: usize) -> u128;
-    /// High 128 then low 128
-    fn get_u256(&self, index: usize) -> (u128, u128);
-    fn get_address(&self, index: usize) -> CanonicalAddr;
-    fn get_bytes32(&self, index: usize) -> &[u8];
-}
-
-impl ByteUtils for &[u8] {
-    fn get_u8(&self, index: usize) -> u8 {
-        self[index]
-    }
-    fn get_u32(&self, index: usize) -> u32 {
-        let mut bytes: [u8; 32 / 8] = [0; 32 / 8];
-        bytes.copy_from_slice(&self[index..index + 4]);
-        u32::from_be_bytes(bytes)
-    }
-    fn get_u128_be(&self, index: usize) -> u128 {
-        let mut bytes: [u8; 128 / 8] = [0; 128 / 8];
-        bytes.copy_from_slice(&self[index..index + 128 / 8]);
-        u128::from_be_bytes(bytes)
-    }
-    fn get_u256(&self, index: usize) -> (u128, u128) {
-        (self.get_u128_be(index), self.get_u128_be(index + 128 / 8))
-    }
-    fn get_address(&self, index: usize) -> CanonicalAddr {
-        // 32 bytes are reserved for addresses, but only the last 20 bytes are taken by the actual address
-        CanonicalAddr::from(&self[index + 32 - 20..index + 32])
-    }
-    fn get_bytes32(&self, index: usize) -> &[u8] {
-        &self[index..index + 32]
-    }
-}
-
-pub fn extend_address_to_32(addr: &CanonicalAddr) -> Vec<u8> {
-    let mut result: Vec<u8> = vec![0; 12];
-    result.extend(addr.as_slice());
-    result
-}

+ 0 - 1335
terra/contracts/wormhole/src/contract.rs

@@ -1,1335 +0,0 @@
-use crate::msg::WrappedRegistryResponse;
-use cosmwasm_std::{
-    log, to_binary, Api, Binary, CanonicalAddr, CosmosMsg, BankMsg, Env, Extern, HandleResponse, HumanAddr,
-    InitResponse, Querier, QueryRequest, StdResult, Storage, Uint128, WasmMsg, WasmQuery, Coin, has_coins,
-};
-
-use crate::byte_utils::extend_address_to_32;
-use crate::byte_utils::ByteUtils;
-use crate::error::ContractError;
-use crate::msg::{GuardianSetInfoResponse, HandleMsg, InitMsg, QueryMsg, GetStateResponse};
-use crate::state::{
-    config, config_read, guardian_set_get, guardian_set_set, vaa_archive_add, vaa_archive_check,
-    wrapped_asset, wrapped_asset_address, wrapped_asset_address_read, wrapped_asset_read,
-    ConfigInfo, GuardianAddress, GuardianSetInfo, ParsedVAA,
-};
-
-use cw20_base::msg::HandleMsg as TokenMsg;
-use cw20_base::msg::QueryMsg as TokenQuery;
-
-use cw20::TokenInfoResponse;
-
-use cw20_wrapped::msg::HandleMsg as WrappedMsg;
-use cw20_wrapped::msg::InitMsg as WrappedInit;
-use cw20_wrapped::msg::QueryMsg as WrappedQuery;
-use cw20_wrapped::msg::{InitHook, InitMint, WrappedAssetInfoResponse};
-
-use k256::ecdsa::recoverable::Id as RecoverableId;
-use k256::ecdsa::recoverable::Signature as RecoverableSignature;
-use k256::ecdsa::Signature;
-use k256::ecdsa::VerifyKey;
-use k256::EncodedPoint;
-use sha3::{Digest, Keccak256};
-
-use generic_array::GenericArray;
-
-use std::convert::TryFrom;
-
-// Chain ID of Terra
-const CHAIN_ID: u8 = 3;
-
-// Lock assets fee amount and denomination
-const FEE_AMOUNT: u128 = 10000;
-const FEE_DENOMINATION: &str = "uluna";
-
-const WRAPPED_ASSET_UPDATING: &str = "updating";
-
-pub fn init<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    msg: InitMsg,
-) -> StdResult<InitResponse> {
-    // Save general wormhole info
-    let state = ConfigInfo {
-        guardian_set_index: 0,
-        guardian_set_expirity: msg.guardian_set_expirity,
-        wrapped_asset_code_id: msg.wrapped_asset_code_id,
-        owner: deps.api.canonical_address(&env.message.sender)?,
-        fee: Coin::new(FEE_AMOUNT, FEE_DENOMINATION),  // 0.01 Luna (or 10000 uluna) fee by default
-    };
-    config(&mut deps.storage).save(&state)?;
-
-    // Add initial guardian set to storage
-    guardian_set_set(
-        &mut deps.storage,
-        state.guardian_set_index,
-        &msg.initial_guardian_set,
-    )?;
-
-    Ok(InitResponse::default())
-}
-
-pub fn handle<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    msg: HandleMsg,
-) -> StdResult<HandleResponse> {
-    match msg {
-        HandleMsg::SubmitVAA { vaa } => handle_submit_vaa(deps, env, &vaa.as_slice()),
-        HandleMsg::RegisterAssetHook { asset_id } => {
-            handle_register_asset(deps, env, &asset_id.as_slice())
-        }
-        HandleMsg::LockAssets {
-            asset,
-            recipient,
-            amount,
-            target_chain,
-            nonce,
-        } => handle_lock_assets(
-            deps,
-            env,
-            asset,
-            amount,
-            recipient.as_slice(),
-            target_chain,
-            nonce,
-        ),
-        HandleMsg::TransferFee { amount, recipient } => handle_transfer_fee(deps, env, amount, recipient),
-    }
-}
-
-/// Process VAA message signed by quardians
-fn handle_submit_vaa<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    data: &[u8],
-) -> StdResult<HandleResponse> {
-    let state = config_read(&deps.storage).load()?;
-    
-    let vaa = parse_and_verify_vaa(&deps.storage, data, env.block.time)?;
-
-    let result = match vaa.action {
-        0x01 => {
-            if vaa.guardian_set_index != state.guardian_set_index {
-                return ContractError::NotCurrentGuardianSet.std_err();
-            }
-            vaa_update_guardian_set(deps, env, vaa.payload.as_slice())
-        }
-        0x10 => vaa_transfer(deps, env, vaa.payload.as_slice()),
-        _ => ContractError::InvalidVAAAction.std_err(),
-    };
-
-    if result.is_ok() {
-        vaa_archive_add(&mut deps.storage, vaa.hash.as_slice())?;
-    }
-
-    result
-}
-
-/// Parses raw VAA data into a struct and verifies whether it contains sufficient signatures of an
-/// active guardian set i.e. is valid according to Wormhole consensus rules
-fn parse_and_verify_vaa<S: Storage>(
-    storage: &S,
-    data: &[u8],
-    block_time: u64,
-) -> StdResult<ParsedVAA> {
-    let vaa = ParsedVAA::deserialize(data)?;
-
-    if vaa.version != 1 {
-        return ContractError::InvalidVersion.std_err();
-    }
-
-    // Check if VAA with this hash was already accepted
-    if vaa_archive_check(storage, vaa.hash.as_slice()) {
-        return ContractError::VaaAlreadyExecuted.std_err();
-    }
-
-    // Load and check guardian set
-    let guardian_set = guardian_set_get(storage, vaa.guardian_set_index);
-    let guardian_set: GuardianSetInfo =
-        guardian_set.or_else(|_| ContractError::InvalidGuardianSetIndex.std_err())?;
-
-    if guardian_set.expiration_time != 0 && guardian_set.expiration_time < block_time {
-        return ContractError::GuardianSetExpired.std_err();
-    }
-    if vaa.len_signers < guardian_set.quorum() {
-        return ContractError::NoQuorum.std_err();
-    }
-
-    // Verify guardian signatures
-    let mut last_index: i32 = -1;
-    let mut pos = ParsedVAA::HEADER_LEN;
-
-    for _ in 0..vaa.len_signers {
-        if pos + ParsedVAA::SIGNATURE_LEN > data.len() {
-            return ContractError::InvalidVAA.std_err();
-        }
-        let index = data.get_u8(pos) as i32;
-        if index <= last_index {
-            return ContractError::WrongGuardianIndexOrder.std_err();
-        }
-        last_index = index;
-
-        let signature = Signature::try_from(
-            &data[pos + ParsedVAA::SIG_DATA_POS
-                ..pos + ParsedVAA::SIG_DATA_POS + ParsedVAA::SIG_DATA_LEN],
-        )
-        .or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
-        let id = RecoverableId::new(data.get_u8(pos + ParsedVAA::SIG_RECOVERY_POS))
-            .or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
-        let recoverable_signature = RecoverableSignature::new(&signature, id)
-            .or_else(|_| ContractError::CannotDecodeSignature.std_err())?;
-
-        let verify_key = recoverable_signature
-            .recover_verify_key_from_digest_bytes(GenericArray::from_slice(vaa.hash.as_slice()))
-            .or_else(|_| ContractError::CannotRecoverKey.std_err())?;
-
-        let index = index as usize;
-        if index >= guardian_set.addresses.len() {
-            return ContractError::TooManySignatures.std_err();
-        }
-        if !keys_equal(&verify_key, &guardian_set.addresses[index]) {
-            return ContractError::GuardianSignatureError.std_err();
-        }
-        pos += ParsedVAA::SIGNATURE_LEN;
-    }
-
-    Ok(vaa)
-}
-
-/// Handle wrapped asset registration messages
-fn handle_register_asset<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    asset_id: &[u8],
-) -> StdResult<HandleResponse> {
-    let mut bucket = wrapped_asset(&mut deps.storage);
-    let result = bucket.load(asset_id);
-    let result = result.map_err(|_| ContractError::RegistrationForbidden.std())?;
-    if result != HumanAddr::from(WRAPPED_ASSET_UPDATING) {
-        return ContractError::AssetAlreadyRegistered.std_err();
-    }
-    
-    bucket.save(asset_id, &env.message.sender)?;
-
-    let contract_address: CanonicalAddr =
-        deps.api.canonical_address(&env.message.sender)?;
-    wrapped_asset_address(&mut deps.storage)
-        .save(contract_address.as_slice(), &asset_id.to_vec())?;
-
-    Ok(HandleResponse {
-        messages: vec![],
-        log: vec![
-            log("action", "register_asset"),
-            log("asset_id", format!("{:?}", asset_id)),
-            log("contract_addr", env.message.sender),
-        ],
-        data: None,
-    })
-}
-
-fn vaa_update_guardian_set<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    data: &[u8],
-) -> StdResult<HandleResponse> {
-    /* Payload format
-    0   uint32 new_index
-    4   uint8 len(keys)
-    5   [][20]uint8 guardian addresses
-    */
-
-    const GUARDIAN_INDEX_POS: usize = 0;
-    const LENGTH_POS: usize = 4;
-    const ADDRESS_POS: usize = 5;
-    const ADDRESS_LEN: usize = 20;
-
-    if ADDRESS_POS >= data.len() {
-        return ContractError::InvalidVAA.std_err();
-    }
-
-    let mut state = config_read(&deps.storage).load()?;
-
-    let new_guardian_set_index = data.get_u32(GUARDIAN_INDEX_POS);
-
-    if new_guardian_set_index != state.guardian_set_index + 1 {
-        return ContractError::GuardianSetIndexIncreaseError.std_err();
-    }
-    let len = data.get_u8(LENGTH_POS);
-
-    let mut new_guardian_set = GuardianSetInfo {
-        addresses: vec![],
-        expiration_time: 0,
-    };
-    let mut pos = ADDRESS_POS;
-    for _ in 0..len {
-        if pos + ADDRESS_LEN > data.len() {
-            return ContractError::InvalidVAA.std_err();
-        }
-
-        new_guardian_set.addresses.push(GuardianAddress {
-            bytes: data[pos..pos + ADDRESS_LEN].to_vec().into(),
-        });
-        pos += ADDRESS_LEN;
-    }
-
-    let old_guardian_set_index = state.guardian_set_index;
-    state.guardian_set_index = new_guardian_set_index;
-
-    guardian_set_set(
-        &mut deps.storage,
-        state.guardian_set_index,
-        &new_guardian_set,
-    )?;
-    config(&mut deps.storage).save(&state)?;
-
-    let mut old_guardian_set = guardian_set_get(&deps.storage, old_guardian_set_index)?;
-    old_guardian_set.expiration_time = env.block.time + state.guardian_set_expirity;
-    guardian_set_set(&mut deps.storage, old_guardian_set_index, &old_guardian_set)?;
-
-    Ok(HandleResponse {
-        messages: vec![],
-        log: vec![
-            log("action", "guardian_set_change"),
-            log("old", old_guardian_set_index),
-            log("new", state.guardian_set_index),
-        ],
-        data: None,
-    })
-}
-
-fn vaa_transfer<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    data: &[u8],
-) -> StdResult<HandleResponse> {
-    /* Payload format:
-    0   uint32 nonce
-    4   uint8 source_chain
-    5   uint8 target_chain
-    6   [32]uint8 source_address
-    38  [32]uint8 target_address
-    70  uint8 token_chain
-    71  [32]uint8 token_address
-    103 uint8 decimals
-    104 uint256 amount */
-
-    const SOURCE_CHAIN_POS: usize = 4;
-    const TARGET_CHAIN_POS: usize = 5;
-    const TARGET_ADDRESS_POS: usize = 38;
-    const TOKEN_CHAIN_POS: usize = 70;
-    const TOKEN_ADDRESS_POS: usize = 71;
-    const DECIMALS_POS: usize = 103;
-    const AMOUNT_POS: usize = 104;
-    const PAYLOAD_LEN: usize = 136;
-
-    if PAYLOAD_LEN > data.len() {
-        return ContractError::InvalidVAA.std_err();
-    }
-
-    let source_chain = data.get_u8(SOURCE_CHAIN_POS);
-    let target_chain = data.get_u8(TARGET_CHAIN_POS);
-
-    let target_address = data.get_address(TARGET_ADDRESS_POS);
-
-    let token_chain = data.get_u8(TOKEN_CHAIN_POS);
-    let (not_supported_amount, amount) = data.get_u256(AMOUNT_POS);
-
-    // Check high 128 bit of amount value to be empty
-    if not_supported_amount != 0 {
-        return ContractError::AmountTooHigh.std_err();
-    }
-
-    // Check if source and target chains are different
-    if source_chain == target_chain {
-        return ContractError::SameSourceAndTarget.std_err();
-    }
-
-    // Check if transfer is incoming
-    if target_chain != CHAIN_ID {
-        return ContractError::WrongTargetChain.std_err();
-    }
-
-    if token_chain != CHAIN_ID {
-        let asset_address = data.get_bytes32(TOKEN_ADDRESS_POS);
-        let asset_id = build_asset_id(token_chain, asset_address);
-
-        let mut messages: Vec<CosmosMsg> = vec![];
-
-        // Check if this asset is already deployed
-        let contract_addr = wrapped_asset_read(&deps.storage).load(&asset_id).ok();
-        let contract_addr = contract_addr.filter(|addr| addr != &HumanAddr::from(WRAPPED_ASSET_UPDATING));
-
-        if let Some(contract_addr) = contract_addr {
-            // Asset already deployed, just mint
-            messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr,
-                msg: to_binary(&WrappedMsg::Mint {
-                    recipient: deps
-                        .api
-                        .human_address(&target_address)
-                        .or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
-                    amount: Uint128::from(amount),
-                })?,
-                send: vec![],
-            }));
-        } else {
-            // Asset is not deployed yet, deploy and mint
-            wrapped_asset(&mut deps.storage).save(&asset_id, &HumanAddr::from(WRAPPED_ASSET_UPDATING))?;
-
-            let state = config_read(&deps.storage).load()?;
-            messages.push(CosmosMsg::Wasm(WasmMsg::Instantiate {
-                code_id: state.wrapped_asset_code_id,
-                msg: to_binary(&WrappedInit {
-                    asset_chain: token_chain,
-                    asset_address: asset_address.to_vec().into(),
-                    decimals: data.get_u8(DECIMALS_POS),
-                    mint: Some(InitMint {
-                        recipient: deps
-                            .api
-                            .human_address(&target_address)
-                            .or_else(|_| ContractError::WrongTargetAddressFormat.std_err())?,
-                        amount: Uint128::from(amount),
-                    }),
-                    init_hook: Some(InitHook {
-                        contract_addr: env.contract.address,
-                        msg: to_binary(&HandleMsg::RegisterAssetHook {
-                            asset_id: asset_id.to_vec().into(),
-                        })?,
-                    }),
-                })?,
-                send: vec![],
-                label: None,
-            }));
-        }
-
-        Ok(HandleResponse {
-            messages,
-            log: vec![],
-            data: None,
-        })
-    } else {
-        let token_address = data.get_address(TOKEN_ADDRESS_POS);
-
-        Ok(HandleResponse {
-            messages: vec![CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr: deps.api.human_address(&token_address)?,
-                msg: to_binary(&TokenMsg::Transfer {
-                    recipient: deps.api.human_address(&target_address)?,
-                    amount: Uint128::from(amount),
-                })?,
-                send: vec![],
-            })],
-            log: vec![],
-            data: None,
-        })
-    }
-}
-
-fn handle_lock_assets<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    asset: HumanAddr,
-    amount: Uint128,
-    recipient: &[u8],
-    target_chain: u8,
-    nonce: u32,
-) -> StdResult<HandleResponse> {
-    if target_chain == CHAIN_ID {
-        return ContractError::SameSourceAndTarget.std_err();
-    }
-
-    if amount.is_zero() {
-        return ContractError::AmountTooLow.std_err();
-    }
-
-    let state = config_read(&deps.storage).load()?;
-    
-    // Check fee
-    if !has_coins(env.message.sent_funds.as_ref(), &state.fee) {
-        return ContractError::FeeTooLow.std_err();
-    }
-
-    let asset_chain: u8;
-    let asset_address: Vec<u8>;
-
-    // Query asset details
-    let request = QueryRequest::<()>::Wasm(WasmQuery::Smart {
-        contract_addr: asset.clone(),
-        msg: to_binary(&TokenQuery::TokenInfo {})?,
-    });
-    let token_info: TokenInfoResponse = deps.querier.custom_query(&request)?;
-
-    let decimals: u8 = token_info.decimals;
-
-    let asset_canonical: CanonicalAddr = deps.api.canonical_address(&asset)?;
-
-    let mut messages: Vec<CosmosMsg> = vec![];
-
-    match wrapped_asset_address_read(&deps.storage).load(asset_canonical.as_slice()) {
-        Ok(_) => {
-            // This is a deployed wrapped asset, burn it
-            messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr: asset.clone(),
-                msg: to_binary(&WrappedMsg::Burn {
-                    account: env.message.sender.clone(),
-                    amount,
-                })?,
-                send: vec![],
-            }));
-            let request = QueryRequest::<()>::Wasm(WasmQuery::Smart {
-                contract_addr: asset,
-                msg: to_binary(&WrappedQuery::WrappedAssetInfo {})?,
-            });
-            let wrapped_token_info: WrappedAssetInfoResponse =
-                deps.querier.custom_query(&request)?;
-            asset_chain = wrapped_token_info.asset_chain;
-            asset_address = wrapped_token_info.asset_address.as_slice().to_vec();
-        }
-        Err(_) => {
-            // This is a regular asset, transfer its balance
-            messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
-                contract_addr: asset,
-                msg: to_binary(&TokenMsg::TransferFrom {
-                    owner: env.message.sender.clone(),
-                    recipient: env.contract.address.clone(),
-                    amount,
-                })?,
-                send: vec![],
-            }));
-            asset_address = extend_address_to_32(&asset_canonical);
-            asset_chain = CHAIN_ID;
-        }
-    };
-
-    Ok(HandleResponse {
-        messages,
-        log: vec![
-            log("locked.target_chain", target_chain),
-            log("locked.token_chain", asset_chain),
-            log("locked.token_decimals", decimals),
-            log("locked.token", hex::encode(asset_address)),
-            log(
-                "locked.sender",
-                hex::encode(extend_address_to_32(
-                    &deps.api.canonical_address(&env.message.sender)?,
-                )),
-            ),
-            log("locked.recipient", hex::encode(recipient)),
-            log("locked.amount", amount),
-            log("locked.nonce", nonce),
-            log("locked.block_time", env.block.time),
-        ],
-        data: None,
-    })
-}
-
-pub fn handle_transfer_fee<S: Storage, A: Api, Q: Querier>(
-    deps: &mut Extern<S, A, Q>,
-    env: Env,
-    amount: Coin,
-    recipient: HumanAddr,
-) -> StdResult<HandleResponse> {
-    let state = config_read(&deps.storage).load()?;
-
-    if deps.api.canonical_address(&env.message.sender)? != state.owner {
-        return ContractError::PermissionDenied.std_err();
-    }
-
-    Ok(HandleResponse {
-        messages: vec![CosmosMsg::Bank(BankMsg::Send{
-            from_address: env.contract.address,
-            to_address: recipient,
-            amount: vec![amount],
-        })],
-        log: vec![],
-        data: None,
-    })
-}
-
-pub fn query<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-    msg: QueryMsg,
-) -> StdResult<Binary> {
-    match msg {
-        QueryMsg::GuardianSetInfo {} => to_binary(&query_guardian_set_info(deps)?),
-        QueryMsg::WrappedRegistry { chain, address } => {
-            to_binary(&query_wrapped_registry(deps, chain, address.as_slice())?)
-        }
-        QueryMsg::VerifyVAA { vaa, block_time } => {
-            to_binary(&query_parse_and_verify_vaa(deps, &vaa.as_slice(), block_time)?)
-        },
-        QueryMsg::GetState {} => to_binary(&query_state(deps)?),
-    }
-}
-
-pub fn query_guardian_set_info<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-) -> StdResult<GuardianSetInfoResponse> {
-    let state = config_read(&deps.storage).load()?;
-    let guardian_set = guardian_set_get(&deps.storage, state.guardian_set_index)?;
-    let res = GuardianSetInfoResponse {
-        guardian_set_index: state.guardian_set_index,
-        addresses: guardian_set.addresses,
-    };
-    Ok(res)
-}
-
-pub fn query_wrapped_registry<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-    chain: u8,
-    address: &[u8],
-) -> StdResult<WrappedRegistryResponse> {
-    let asset_id = build_asset_id(chain, address);
-    // Check if this asset is already deployed
-    match wrapped_asset_read(&deps.storage).load(&asset_id) {
-        Ok(address) => Ok(WrappedRegistryResponse { address }),
-        Err(_) => ContractError::AssetNotFound.std_err(),
-    }
-}
-
-pub fn query_parse_and_verify_vaa<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-    data: &[u8],
-    block_time: u64,
-) -> StdResult<ParsedVAA> {
-    parse_and_verify_vaa(&deps.storage, data, block_time)
-}
-
-pub fn query_state<S: Storage, A: Api, Q: Querier>(
-    deps: &Extern<S, A, Q>,
-) -> StdResult<GetStateResponse> {
-    let state = config_read(&deps.storage).load()?;
-    let res = GetStateResponse {
-        fee: state.fee,
-    };
-    Ok(res)
-}
-
-fn keys_equal(a: &VerifyKey, b: &GuardianAddress) -> bool {
-    let mut hasher = Keccak256::new();
-
-    let point: EncodedPoint = EncodedPoint::from(a);
-    let point = point.decompress();
-    if bool::from(point.is_none()) {
-        return false;
-    }
-    let point = point.unwrap();
-
-    hasher.update(&point.as_bytes()[1..]);
-    let a = &hasher.finalize()[12..];
-
-    let b = &b.bytes;
-    if a.len() != b.len() {
-        return false;
-    }
-    for (ai, bi) in a.iter().zip(b.as_slice().iter()) {
-        if ai != bi {
-            return false;
-        }
-    }
-    true
-}
-
-fn build_asset_id(chain: u8, address: &[u8]) -> Vec<u8> {
-    let mut asset_id: Vec<u8> = vec![];
-    asset_id.push(chain);
-    asset_id.extend_from_slice(address);
-
-    let mut hasher = Keccak256::new();
-    hasher.update(asset_id);
-    hasher.finalize().to_vec()
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::state::GuardianSetInfo;
-    use cosmwasm_std::testing::{mock_dependencies, mock_env, MOCK_CONTRACT_ADDR};
-    use cosmwasm_std::{HumanAddr, QuerierResult};
-    use serde_json;
-
-    // Constants generated by bridge/cmd/vaa-test-terra/main.go
-    const ADDR_1: &str = "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe";
-    const ADDR_2: &str = "E06A9ADfeB38a8eE4D00E89307C016D0749679bD";
-    const ADDR_3: &str = "8575Df9b3c97B4E267Deb92d93137844A97A0132";
-    const ADDR_4: &str = "0427cDA59902Dc6EB0c1bd2b6D38F87c5552b348";
-    const ADDR_5: &str = "bFEa822F75c42e1764c791B8fE04a7B10DDB3857";
-    const ADDR_6: &str = "2F5FE0B158147e7260f14062556AfC94Eece55fF";
-    const VAA_VALID_TRANSFER_1_SIG: &str = "01000000000100d106d4f363c6e3d0bf8ebf3cf8ef1ba35e66687b7613a826b5f5b68e0c346e1e0fdd6ceb332c87dad7d170ee6736571c0b75173787a8dcf41a492075e18a9a9601000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_TRANSFER_2_SIGS: &str = "0100000000020040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a59220001efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e600000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_TRANSFER_3_SIGS: &str = "0100000000030040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a59220001efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e60002a5fb92ff2b5a5eed98e2909ed932e5d9328cb2527027cce8f40c4f5677c341c83fe9fac7bf39af60fe47ecfb6f52b22b9d817d24d4147684b08e2fe19ff3a3ef01000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0: &str = "01000000000100a33c022217ccb87a5bc83b71e6377fff6639e7904d9e9995a42dc0867dc2b0bc5d1aacc3752ea71cf4d85278526b5dd40b0343667a2d4434a44cbf7844181a1000000007d0010000000101e06a9adfeb38a8ee4d00e89307c016d0749679bd";
-    const VAA_ERROR_SIGNATURE_SEQUENCE: &str = "01000000000201efb8a4825c87ab68190e1b184eeda5c45f82b22450ff113f2581a2f1bd3aeca60798392405cd4d3b523a5c3426d09b963c195c842a0040e93651cb700785d0e6000040d91705d211c52c9f120adb1b794355ba10ec1ff855295e677c5b341b2e5449684179f8ca4087e88de2cba0e6cbf6e0c7a353529800ccf96e5fdd80a85a592200000007d0100000003801030201040000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040506070809000102030405060708090002000000000000000000000000d833215cbcc3f914bd1c9ece3ee7bf8b14f841bb080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_ERROR_WRONG_SIGNATURE_1_SIG: &str = "0100000000010075c1b20fb59adc55a08f9778bc525507a36a29d1f0e2cb3fcc9c90f7331786263c4bd53ce5d3865b4f63cddeafb2c1026b5e13f1b66af7dabbd1f1af9f34fd3f01000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0_DIFF: &str = "01000000000100d90d6f9cbc0458599cbe4d267bc9221b54955b94cb5cb338aeb845bdc9dd275f558871ea479de9cc0b44cfb2a07344431a3adbd2f98aa86f4e12ff4aba061b7f00000007d00100000001018575df9b3c97b4e267deb92d93137844a97a0132";
-    const VAA_ERROR_INVALID_TARGET_ADDRESS: &str = "0100000000010092f32c76aa3a8d83de59b3f2281cfbf70af33d9bcfbaa78bd3e9cafc512335ab40b126a894f0182ee8c69f5324496eb681c1780ed39bcc80f589cfc0a5df144a01000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_GUARDIAN_SET_CHANGE_JUMP: &str = "010000000001004b179853b36b76446c72944d50551be814ab34f23da2124615315da71505df801b38355d741cdd65e856792e2a1435270abfe52ae005c4e3671c0b7aac36445a01000007d00100000002018575df9b3c97b4e267deb92d93137844a97a0132";
-    const VAA_ERROR_AMOUNT_TOO_HIGH: &str = "0100000000010055fdf76a64b779ac5b7a54dc181cf430f4d14a499b7933049d8bc94db529ed0a2d12d50ec2026883e59a5c64f2189b60c84a53b66113e8b52da66fd89f70495f00000007d01000000038010302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000100000000000000000000000000000000";
-    const VAA_ERROR_SAME_SOURCE_AND_TARGET: &str = "010000000001004c53dfce8fc9e781f0cfdc6592c00c337c1e109168ff17ee3bf4cf69ddb8a0a52a3c215093301d5459d282d625dc5125592609f06f14a57f61121e668b0ec10500000003e81000000038030302010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_ERROR_WRONG_TARGET: &str = "01000000000100b19a265b1407e9619ffc29be9562161ed2c155db5ba68e01265a250a677eb0c62bb91e468da827e9ec4c1e9428ade97129126f56500c4a3c9f9803cc85f656d200000003e81000000038010202010400000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-    const VAA_VALID_GUARDIAN_SET_CHANGE_TO_6: &str = "01000000000100a5defbd912ef327d07afff71e0da9c2e2a13e5516255c62e249a6761afe2465c7b6fc1032451559551e76eb4a029474fd791b2250c4fd40a8b3f5d4f5f58e5a30000000fa0010000000106befa429d57cd18b7f8a4d91a2da9ab4af05d0fbee06a9adfeb38a8ee4d00e89307c016d0749679bd8575df9b3c97b4e267deb92d93137844a97a01320427cda59902dc6eb0c1bd2b6d38f87c5552b348bfea822f75c42e1764c791b8fe04a7b10ddb38572f5fe0b158147e7260f14062556afc94eece55ff";
-    const VAA_VALID_TRANSFER_5_SIGS_GS_1: &str = "01000000010500027eb7e87a9d0ab91ec53bb073c0f0acf189900139daa652666fd4cfe32a4ee42383c1a66e3a397c2de8ae485225357feb52f665952b1e384ef6dfcea1ba9f920001cfcacfad444ac3202f8f0d2252c69ee90d18c9105f7be3b5d361b7fcb0fbf7fa7287bac5de9cb02f86a28fdd7f24015991020431b0048aa3bbb29daed625e416000372f6c239ddeccded04a95a0cf0bfefe6e168148f1fe3b93e797eb2e74e098b890f2be341dd0f3c8172c2050154407cfdd1ea7bd6cce0b31f020ec7530ffb6109000449c025fe0630268983d57c4bd1546497788f810e427b6fd436cb1f048152375e1063422b4d1cc668a0612814c550ea7e3d1aa93404a0b6e089d210d4c937023a000548bf474fb350d5e482378c37404fb4d1421e262d13ebf6b11977214c789a246a6c278a522a9be4beba008f3d481b1ee35c5b0559bef474eb34b9e3e681947c230100000fa01000000039010302010500000000000000000000000000000000000000000000000000000000000000000000000000000000000102030405060708090001020304050607080900010000000000000000000000000347ef34687bdc9f189e87a9200658d9c40e9988080000000000000000000000000000000000000000000000000de0b6b3a7640000";
-
-    const CANONICAL_LENGTH: usize = 20;
-
-    const CREATOR_ADDR: &str = "creator";
-    const SENDER_ADDR: &str = "sender";
-    const SENDER_ADDR_HEX: &str = "73656e6465720000000000000000000000000000"; // Extended to 20 bytes
-
-    lazy_static! {
-        static ref ALL_GUARDIANS: Vec<GuardianAddress> = vec![
-            GuardianAddress::from(ADDR_1),
-            GuardianAddress::from(ADDR_2),
-            GuardianAddress::from(ADDR_3),
-            GuardianAddress::from(ADDR_4),
-            GuardianAddress::from(ADDR_5),
-            GuardianAddress::from(ADDR_6)
-        ];
-    }
-
-    fn unix_timestamp() -> u64 {
-        1608803487u64 // Use deterministic timestamp
-    }
-
-    fn do_init_with_guardians<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        number_of_guardians: usize,
-    ) {
-        let expiration_time = unix_timestamp() + 1000;
-        do_init(deps, &ALL_GUARDIANS[..number_of_guardians], expiration_time);
-    }
-
-    fn do_init<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        guardians: &[GuardianAddress],
-        expiration_time: u64,
-    ) {
-        let init_msg = InitMsg {
-            initial_guardian_set: GuardianSetInfo {
-                addresses: guardians.to_vec(),
-                expiration_time,
-            },
-            guardian_set_expirity: 50,
-            wrapped_asset_code_id: 999,
-        };
-        let mut env = mock_env(&HumanAddr::from(CREATOR_ADDR), &[]);
-        env.block.time = unix_timestamp();
-        let res = init(deps, env, init_msg).unwrap();
-        assert_eq!(0, res.messages.len());
-    }
-
-    fn submit_msg<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        msg: HandleMsg,
-    ) -> StdResult<HandleResponse> {
-        submit_msg_with_sender(deps, msg, &HumanAddr::from(SENDER_ADDR), None)
-    }
-
-    fn submit_msg_with_fee<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        msg: HandleMsg,
-        fee: Coin,
-    ) -> StdResult<HandleResponse> {
-        submit_msg_with_sender(deps, msg, &HumanAddr::from(SENDER_ADDR), Some(fee))
-    }
-
-    fn submit_msg_with_sender<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        msg: HandleMsg,
-        sender: &HumanAddr,
-        fee: Option<Coin>,
-    ) -> StdResult<HandleResponse> {
-        let mut env = mock_env(sender, &[]);
-        env.block.time = unix_timestamp();
-        if let Some(fee) = fee {
-            env.message.sent_funds = vec![fee];
-        }
-
-        handle(deps, env, msg)
-    }
-
-    fn submit_vaa<S: Storage, A: Api, Q: Querier>(
-        deps: &mut Extern<S, A, Q>,
-        vaa: &str,
-    ) -> StdResult<HandleResponse> {
-        submit_msg(
-            deps,
-            HandleMsg::SubmitVAA {
-                vaa: hex::decode(vaa).expect("Decoding failed").into(),
-            },
-        )
-    }
-
-    #[test]
-    fn can_init() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-    }
-
-    #[test]
-    fn valid_vaa_token_transfer() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let messages = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG)
-            .unwrap()
-            .messages;
-        assert_eq!(1, messages.len());
-        let msg = &messages[0];
-        match msg {
-            CosmosMsg::Wasm(wasm_msg) => match wasm_msg {
-                WasmMsg::Instantiate {
-                    code_id,
-                    msg: _,
-                    send,
-                    label,
-                } => {
-                    assert_eq!(*code_id, 999);
-                    assert_eq!(*label, None);
-                    assert_eq!(*send, vec![]);
-                }
-                _ => panic!("Wrong message type"),
-            },
-            _ => panic!("Wrong message type"),
-        }
-    }
-
-    #[test]
-    fn valid_vaa_2_signatures() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 2);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_2_SIGS);
-        assert!(result.is_ok());
-    }
-
-    #[test]
-    fn valid_vaa_non_expiring_guardians() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init(&mut deps, &vec![GuardianAddress::from(ADDR_1)], 0);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
-        assert!(result.is_ok());
-    }
-
-    #[test]
-    fn error_vaa_same_vaa_twice() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let _ = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG).unwrap();
-        let e = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG).unwrap_err();
-        assert_eq!(e, ContractError::VaaAlreadyExecuted.std());
-    }
-
-    #[test]
-    fn valid_vaa_guardian_set_change() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let messages = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0)
-            .unwrap()
-            .messages;
-        assert_eq!(0, messages.len());
-
-        // Check storage
-        let state = config_read(&deps.storage)
-            .load()
-            .expect("Cannot load config storage");
-        assert_eq!(state.guardian_set_index, 1);
-        let guardian_set_info = guardian_set_get(&deps.storage, state.guardian_set_index)
-            .expect("Cannot find guardian set");
-        assert_eq!(
-            guardian_set_info,
-            GuardianSetInfo {
-                addresses: vec![GuardianAddress::from(ADDR_2)],
-                expiration_time: 0
-            }
-        );
-    }
-
-    #[test]
-    fn error_vaa_guardian_set_expired() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        // Expiration time 1 second in the past
-        let expiration_time = unix_timestamp() - 1;
-        do_init(
-            &mut deps,
-            &vec![GuardianAddress::from(ADDR_1)],
-            expiration_time,
-        );
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
-        assert_eq!(result, ContractError::GuardianSetExpired.std_err());
-    }
-
-    #[test]
-    fn error_vaa_no_quorum() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 2);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_1_SIG);
-        assert_eq!(result, ContractError::NoQuorum.std_err());
-    }
-
-    #[test]
-    fn valid_partial_quorum() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 4);
-
-        // 3 signatures on 4-guardian set is quorum
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_3_SIGS);
-        assert!(result.is_ok());
-    }
-
-    #[test]
-    fn error_vaa_wrong_guardian_index_order() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 2);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_SIGNATURE_SEQUENCE);
-        assert_eq!(result, ContractError::WrongGuardianIndexOrder.std_err());
-    }
-
-    #[test]
-    fn error_vaa_too_many_signatures() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_2_SIGS);
-        assert_eq!(result, ContractError::TooManySignatures.std_err());
-    }
-
-    #[test]
-    fn error_vaa_invalid_signature() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init(
-            &mut deps,
-            // Use 1-2-4 guardians
-            &vec![
-                GuardianAddress::from(ADDR_1),
-                GuardianAddress::from(ADDR_2),
-                GuardianAddress::from(ADDR_4),
-            ],
-            unix_timestamp(),
-        );
-        // Sign by 1-2-3 guardians
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_3_SIGS);
-        assert_eq!(result, ContractError::GuardianSignatureError.std_err());
-
-        // Single signature, wrong key
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_WRONG_SIGNATURE_1_SIG);
-        assert_eq!(result, ContractError::GuardianSignatureError.std_err());
-    }
-
-    #[test]
-    fn error_vaa_not_current_quardian_set() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0);
-        assert!(result.is_ok());
-
-        // Submit another valid change, which will fail, because now set #1 is active
-        // (we need to send a different VAA, because otherwise it will be blocked by duplicate check)
-        let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_FROM_0_DIFF);
-        assert_eq!(result, ContractError::NotCurrentGuardianSet.std_err());
-    }
-
-    #[test]
-    fn error_vaa_wrong_target_address_format() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_INVALID_TARGET_ADDRESS);
-        assert_eq!(result, ContractError::WrongTargetAddressFormat.std_err());
-    }
-
-    #[test]
-    fn error_vaa_guardian_set_change_index_not_increasing() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_JUMP);
-        assert_eq!(
-            result,
-            ContractError::GuardianSetIndexIncreaseError.std_err()
-        );
-    }
-
-    #[test]
-    fn error_vaa_transfer_amount_too_high() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_AMOUNT_TOO_HIGH);
-        assert_eq!(result, ContractError::AmountTooHigh.std_err());
-    }
-
-    #[test]
-    fn error_vaa_transfer_same_source_and_target() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_SAME_SOURCE_AND_TARGET);
-        assert_eq!(result, ContractError::SameSourceAndTarget.std_err());
-    }
-
-    #[test]
-    fn error_vaa_transfer_wrong_target_chain() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_ERROR_WRONG_TARGET);
-        assert_eq!(result, ContractError::WrongTargetChain.std_err());
-    }
-
-    #[test]
-    fn valid_transfer_after_guardian_set_change() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_5_SIGS_GS_1);
-        assert_eq!(result, ContractError::InvalidGuardianSetIndex.std_err());
-
-        let result = submit_vaa(&mut deps, VAA_VALID_GUARDIAN_SET_CHANGE_TO_6);
-        assert!(result.is_ok());
-
-        let result = submit_vaa(&mut deps, VAA_VALID_TRANSFER_5_SIGS_GS_1);
-        assert!(result.is_ok());
-    }
-
-    const LOCK_ASSET_ADDR: &str = "lockassetaddr";
-    const LOCK_ASSET_ADDR_HEX: &str = "6c6f636b61737365746164647200000000000000"; // Extended to 20 bytes
-    const LOCK_NONCE: u32 = 105;
-    const LOCK_AMOUNT: u128 = 10000000000;
-    const LOCK_RECIPIENT: &str = "0000000000000000000011223344556677889900";
-    const LOCK_TARGET: u8 = 1;
-    const LOCK_WRAPPED_CHAIN: u8 = 2;
-    const LOCK_WRAPPED_ASSET: &str = "112233445566ff";
-    const LOCKED_DECIMALS: u8 = 11;
-    const ADDRESS_EXTENSION: &str = "000000000000000000000000";
-    const LOCK_ASSET_ID: &[u8] = b"testassetid";
-    struct LockAssetQuerier {}
-
-    lazy_static! {
-        static ref MSG_LOCK: HandleMsg = HandleMsg::LockAssets {
-            asset: HumanAddr::from(LOCK_ASSET_ADDR),
-            amount: Uint128::from(LOCK_AMOUNT),
-            recipient: Binary::from(hex::decode(LOCK_RECIPIENT).unwrap()),
-            target_chain: LOCK_TARGET,
-            nonce: LOCK_NONCE,
-        };
-    }
-
-    impl Querier for LockAssetQuerier {
-        fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
-            let query_request: QueryRequest<()> = serde_json::from_slice(bin_request).unwrap();
-            let query = if let QueryRequest::Wasm(wasm_query) = query_request {
-                wasm_query
-            } else {
-                panic!("Wrong request type");
-            };
-            let msg: Binary = if let WasmQuery::Smart { contract_addr, msg } = query {
-                assert_eq!(contract_addr, HumanAddr::from(LOCK_ASSET_ADDR));
-                msg
-            } else {
-                panic!("Wrong query type");
-            };
-            let msg: WrappedQuery = serde_json::from_slice(msg.as_slice()).unwrap();
-            let response = match msg {
-                WrappedQuery::TokenInfo {} => serde_json::to_string(&TokenInfoResponse {
-                    name: String::from("Test"),
-                    symbol: String::from("TST"),
-                    decimals: LOCKED_DECIMALS,
-                    total_supply: Uint128::from(1000000000000u128),
-                })
-                .unwrap(),
-                WrappedQuery::WrappedAssetInfo {} => {
-                    serde_json::to_string(&WrappedAssetInfoResponse {
-                        asset_chain: LOCK_WRAPPED_CHAIN,
-                        asset_address: Binary::from(hex::decode(LOCK_WRAPPED_ASSET).unwrap()),
-                        bridge: HumanAddr::from("bridgeaddr"),
-                    })
-                    .unwrap()
-                }
-                _ => panic!("Wrong msg type"),
-            };
-            Ok(Ok(Binary::from(response.as_bytes())))
-        }
-    }
-
-    #[test]
-    fn error_lock_fee_too_low() {
-        let deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let mut deps = Extern {
-            storage: deps.storage,
-            api: deps.api,
-            querier: LockAssetQuerier {},
-        };
-        do_init_with_guardians(&mut deps, 1);
-
-        // No fee
-        let result = submit_msg(&mut deps, MSG_LOCK.clone());
-        assert_eq!(result, ContractError::FeeTooLow.std_err());
-
-        // Amount too low
-        let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(9999, "uluna"));
-        assert_eq!(result, ContractError::FeeTooLow.std_err());
-
-        // Wrong denomination
-        let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uusd"));
-        assert_eq!(result, ContractError::FeeTooLow.std_err());
-    }
-
-    #[test]
-    fn valid_lock_regular_asset() {
-        let deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let mut deps = Extern {
-            storage: deps.storage,
-            api: deps.api,
-            querier: LockAssetQuerier {},
-        };
-        do_init_with_guardians(&mut deps, 1);
-
-        let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
-
-        let expected_logs = vec![
-            log("locked.target_chain", LOCK_TARGET),
-            log("locked.token_chain", CHAIN_ID), // Regular asset is Terra-based
-            log("locked.token_decimals", LOCKED_DECIMALS),
-            log(
-                "locked.token",
-                format!("{}{}", ADDRESS_EXTENSION, LOCK_ASSET_ADDR_HEX),
-            ),
-            log(
-                "locked.sender",
-                format!("{}{}", ADDRESS_EXTENSION, SENDER_ADDR_HEX),
-            ),
-            log("locked.recipient", LOCK_RECIPIENT),
-            log("locked.amount", LOCK_AMOUNT),
-            log("locked.nonce", LOCK_NONCE),
-            log("locked.block_time", unix_timestamp()),
-        ];
-        assert_eq!(result.log, expected_logs);
-        assert_eq!(result.messages.len(), 1);
-        let msg = &result.messages[0];
-        let wasm_msg = if let CosmosMsg::Wasm(wasm_msg) = msg {
-            wasm_msg
-        } else {
-            panic!("Wrong msg type");
-        };
-        let command_msg = if let WasmMsg::Execute {
-            contract_addr, msg, ..
-        } = wasm_msg
-        {
-            assert_eq!(*contract_addr, HumanAddr::from(LOCK_ASSET_ADDR));
-            msg
-        } else {
-            panic!("Wrong wasm msg type");
-        };
-        let command_msg: TokenMsg = serde_json::from_slice(command_msg.as_slice()).unwrap();
-        if let TokenMsg::TransferFrom {
-            owner,
-            recipient,
-            amount,
-        } = command_msg
-        {
-            assert_eq!(owner, HumanAddr::from(SENDER_ADDR));
-            assert_eq!(recipient, HumanAddr::from(MOCK_CONTRACT_ADDR));
-            assert_eq!(amount, Uint128::from(LOCK_AMOUNT));
-        } else {
-            panic!("Wrong command type");
-        }
-    }
-
-    #[test]
-    fn error_lock_deployed_asset() {
-        let deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let mut deps = Extern {
-            storage: deps.storage,
-            api: deps.api,
-            querier: LockAssetQuerier {},
-        };
-        do_init_with_guardians(&mut deps, 1);
-
-        let register_msg = HandleMsg::RegisterAssetHook {
-            asset_id: Binary::from(LOCK_ASSET_ID),
-        };
-        
-        let result = submit_msg_with_sender(
-            &mut deps,
-            register_msg.clone(),
-            &HumanAddr::from(LOCK_ASSET_ADDR),
-            None,
-        );
-
-        assert_eq!(result, ContractError::RegistrationForbidden.std_err());
-    }
-
-    #[test]
-    fn valid_lock_deployed_asset() {
-        let deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        let mut deps = Extern {
-            storage: deps.storage,
-            api: deps.api,
-            querier: LockAssetQuerier {},
-        };
-        do_init_with_guardians(&mut deps, 1);
-
-        let register_msg = HandleMsg::RegisterAssetHook {
-            asset_id: Binary::from(LOCK_ASSET_ID),
-        };
-
-        wrapped_asset(&mut deps.storage).save(&LOCK_ASSET_ID, &HumanAddr::from(WRAPPED_ASSET_UPDATING)).unwrap();
-
-        let result = submit_msg_with_sender(
-            &mut deps,
-            register_msg.clone(),
-            &HumanAddr::from(LOCK_ASSET_ADDR),
-            None,
-        );
-
-        assert!(result.is_ok());
-
-        let result = submit_msg_with_fee(&mut deps, MSG_LOCK.clone(), Coin::new(10000, "uluna")).unwrap();
-
-        let expected_logs = vec![
-            log("locked.target_chain", LOCK_TARGET),
-            log("locked.token_chain", LOCK_WRAPPED_CHAIN),
-            log("locked.token_decimals", LOCKED_DECIMALS),
-            log("locked.token", LOCK_WRAPPED_ASSET),
-            log(
-                "locked.sender",
-                format!("{}{}", ADDRESS_EXTENSION, SENDER_ADDR_HEX),
-            ),
-            log("locked.recipient", LOCK_RECIPIENT),
-            log("locked.amount", LOCK_AMOUNT),
-            log("locked.nonce", LOCK_NONCE),
-            log("locked.block_time", unix_timestamp()),
-        ];
-        assert_eq!(result.log, expected_logs);
-        assert_eq!(result.messages.len(), 1);
-
-        let msg = &result.messages[0];
-        let wasm_msg = if let CosmosMsg::Wasm(wasm_msg) = msg {
-            wasm_msg
-        } else {
-            panic!("Wrong msg type");
-        };
-        let command_msg = if let WasmMsg::Execute {
-            contract_addr, msg, ..
-        } = wasm_msg
-        {
-            assert_eq!(*contract_addr, HumanAddr::from(LOCK_ASSET_ADDR));
-            msg
-        } else {
-            panic!("Wrong wasm msg type");
-        };
-        let command_msg: WrappedMsg = serde_json::from_slice(command_msg.as_slice()).unwrap();
-        if let WrappedMsg::Burn { account, amount } = command_msg {
-            assert_eq!(account, HumanAddr::from(SENDER_ADDR));
-            assert_eq!(amount, Uint128::from(LOCK_AMOUNT));
-        } else {
-            panic!("Wrong command type");
-        }
-    }
-
-    #[test]
-    fn error_lock_same_source_and_target() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let mut msg = MSG_LOCK.clone();
-        if let HandleMsg::LockAssets {
-            ref mut target_chain,
-            ..
-        } = msg
-        {
-            *target_chain = CHAIN_ID;
-        }
-        let result = submit_msg(&mut deps, msg);
-        assert_eq!(result, ContractError::SameSourceAndTarget.std_err());
-    }
-
-    #[test]
-    fn error_lock_amount_too_low() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 1);
-
-        let mut msg = MSG_LOCK.clone();
-        if let HandleMsg::LockAssets { ref mut amount, .. } = msg {
-            *amount = Uint128::zero();
-        }
-        let result = submit_msg(&mut deps, msg);
-        assert_eq!(result, ContractError::AmountTooLow.std_err());
-    }
-
-    #[test]
-    fn valid_query_guardian_set() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 3);
-
-        let result = query(&deps, QueryMsg::GuardianSetInfo {}).unwrap();
-        let result: GuardianSetInfoResponse = serde_json::from_slice(result.as_slice()).unwrap();
-
-        assert_eq!(
-            result,
-            GuardianSetInfoResponse {
-                guardian_set_index: 0,
-                addresses: vec![
-                    GuardianAddress {
-                        bytes: Binary::from(hex::decode(ADDR_1).unwrap())
-                    },
-                    GuardianAddress {
-                        bytes: Binary::from(hex::decode(ADDR_2).unwrap())
-                    },
-                    GuardianAddress {
-                        bytes: Binary::from(hex::decode(ADDR_3).unwrap())
-                    },
-                ],
-            }
-        )
-    }
-
-    #[test]
-    fn valid_query_verify_vaa() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init_with_guardians(&mut deps, 4);
-        let env = mock_env(&HumanAddr::from(SENDER_ADDR), &[]);
-
-        let decoded_vaa: Binary = hex::decode(VAA_VALID_TRANSFER_3_SIGS)
-            .expect("Decoding failed")
-            .into();
-        let result = query(&deps, QueryMsg::VerifyVAA { vaa: decoded_vaa, block_time: env.block.time });
-
-        assert!(result.is_ok());
-    }
-
-    #[test]
-    fn error_query_verify_vaa() {
-        let mut deps = mock_dependencies(CANONICAL_LENGTH, &[]);
-        do_init(
-            &mut deps,
-            // Use 1-2-4 guardians
-            &vec![
-                GuardianAddress::from(ADDR_1),
-                GuardianAddress::from(ADDR_2),
-                GuardianAddress::from(ADDR_4),
-            ],
-            unix_timestamp(),
-        );
-        let env = mock_env(&HumanAddr::from(SENDER_ADDR), &[]);
-        // Sign by 1-2-3 guardians
-        let decoded_vaa: Binary = hex::decode(VAA_VALID_TRANSFER_3_SIGS)
-            .expect("Decoding failed")
-            .into();
-        let result = query(&deps, QueryMsg::VerifyVAA { vaa: decoded_vaa, block_time: env.block.time });
-
-        assert_eq!(result, ContractError::GuardianSignatureError.std_err());
-    }
-}

+ 0 - 114
terra/contracts/wormhole/src/error.rs

@@ -1,114 +0,0 @@
-use cosmwasm_std::StdError;
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum ContractError {
-    /// Invalid VAA version
-    #[error("InvalidVersion")]
-    InvalidVersion,
-
-    /// Guardian set with this index does not exist
-    #[error("InvalidGuardianSetIndex")]
-    InvalidGuardianSetIndex,
-
-    /// Guardian set expiration date is zero or in the past
-    #[error("GuardianSetExpired")]
-    GuardianSetExpired,
-
-    /// Not enough signers on the VAA
-    #[error("NoQuorum")]
-    NoQuorum,
-
-    /// Wrong guardian index order, order must be ascending
-    #[error("WrongGuardianIndexOrder")]
-    WrongGuardianIndexOrder,
-
-    /// Some problem with signature decoding from bytes
-    #[error("CannotDecodeSignature")]
-    CannotDecodeSignature,
-
-    /// Some problem with public key recovery from the signature
-    #[error("CannotRecoverKey")]
-    CannotRecoverKey,
-
-    /// Recovered pubkey from signature does not match guardian address
-    #[error("GuardianSignatureError")]
-    GuardianSignatureError,
-
-    /// VAA action code not recognized
-    #[error("InvalidVAAAction")]
-    InvalidVAAAction,
-
-    /// VAA guardian set is not current
-    #[error("NotCurrentGuardianSet")]
-    NotCurrentGuardianSet,
-
-    /// Only 128-bit amounts are supported
-    #[error("AmountTooHigh")]
-    AmountTooHigh,
-
-    /// Amount should be higher than zero
-    #[error("AmountTooLow")]
-    AmountTooLow,
-
-    /// Source and target chain ids must be different
-    #[error("SameSourceAndTarget")]
-    SameSourceAndTarget,
-
-    /// Target chain id must be the same as the current CHAIN_ID
-    #[error("WrongTargetChain")]
-    WrongTargetChain,
-
-    /// Wrapped asset init hook sent twice for the same asset id
-    #[error("AssetAlreadyRegistered")]
-    AssetAlreadyRegistered,
-
-    /// Guardian set must increase in steps of 1
-    #[error("GuardianSetIndexIncreaseError")]
-    GuardianSetIndexIncreaseError,
-
-    /// VAA was already executed
-    #[error("VaaAlreadyExecuted")]
-    VaaAlreadyExecuted,
-
-    /// Message sender not permitted to execute this operation
-    #[error("PermissionDenied")]
-    PermissionDenied,
-
-    /// Could not decode target address from canonical to human-readable form
-    #[error("WrongTargetAddressFormat")]
-    WrongTargetAddressFormat,
-
-    /// More signatures than active guardians found
-    #[error("TooManySignatures")]
-    TooManySignatures,
-
-    /// Wrapped asset not found in the registry
-    #[error("AssetNotFound")]
-    AssetNotFound,
-
-    /// Generic error when there is a problem with VAA structure
-    #[error("InvalidVAA")]
-    InvalidVAA,
-
-    /// Thrown when fee is enabled for the action, but was not sent with the transaction
-    #[error("FeeTooLow")]
-    FeeTooLow,
-
-    /// Registering asset outside of the wormhole
-    #[error("RegistrationForbidden")]
-    RegistrationForbidden,
-}
-
-impl ContractError {
-    pub fn std(&self) -> StdError {
-        StdError::GenericErr {
-            msg: format!("{}", self),
-            backtrace: None,
-        }
-    }
-
-    pub fn std_err<T>(&self) -> Result<T, StdError> {
-        Err(self.std())
-    }
-}

+ 0 - 14
terra/contracts/wormhole/src/lib.rs

@@ -1,14 +0,0 @@
-#[cfg(test)]
-#[macro_use]
-extern crate lazy_static;
-
-mod byte_utils;
-pub mod contract;
-mod error;
-pub mod msg;
-pub mod state;
-
-pub use crate::error::ContractError;
-
-#[cfg(all(target_arch = "wasm32", not(feature = "library")))]
-cosmwasm_std::create_entry_points!(contract);

+ 0 - 62
terra/contracts/wormhole/src/msg.rs

@@ -1,62 +0,0 @@
-use cosmwasm_std::{Binary, HumanAddr, Uint128, Coin};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use crate::state::{GuardianAddress, GuardianSetInfo};
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct InitMsg {
-    pub initial_guardian_set: GuardianSetInfo,
-    pub guardian_set_expirity: u64,
-    pub wrapped_asset_code_id: u64,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum HandleMsg {
-    SubmitVAA {
-        vaa: Binary,
-    },
-    RegisterAssetHook {
-        asset_id: Binary,
-    },
-    LockAssets {
-        asset: HumanAddr,
-        amount: Uint128,
-        recipient: Binary,
-        target_chain: u8,
-        nonce: u32,
-    },
-    TransferFee {
-        amount: Coin,
-        recipient: HumanAddr,
-    },
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum QueryMsg {
-    GuardianSetInfo {},
-    WrappedRegistry { chain: u8, address: Binary },
-    VerifyVAA { vaa: Binary, block_time: u64 },
-    GetState {},
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct GuardianSetInfoResponse {
-    pub guardian_set_index: u32,         // Current guardian set index
-    pub addresses: Vec<GuardianAddress>, // List of querdian addresses
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct WrappedRegistryResponse {
-    pub address: HumanAddr,
-}
-
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct GetStateResponse {
-    pub fee: Coin,
-}

+ 0 - 238
terra/contracts/wormhole/src/state.rs

@@ -1,238 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-
-use cosmwasm_std::{Binary, CanonicalAddr, HumanAddr, StdResult, Storage, Coin};
-use cosmwasm_storage::{
-    bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
-    Singleton,
-};
-
-use crate::byte_utils::ByteUtils;
-use crate::error::ContractError;
-
-use sha3::{Digest, Keccak256};
-
-pub static CONFIG_KEY: &[u8] = b"config";
-pub static GUARDIAN_SET_KEY: &[u8] = b"guardian_set";
-pub static WRAPPED_ASSET_KEY: &[u8] = b"wrapped_asset";
-pub static WRAPPED_ASSET_ADDRESS_KEY: &[u8] = b"wrapped_asset_address";
-
-// Guardian set information
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct ConfigInfo {
-    // Current active guardian set
-    pub guardian_set_index: u32,
-
-    // Period for which a guardian set stays active after it has been replaced
-    pub guardian_set_expirity: u64,
-
-    // Code id for wrapped asset contract
-    pub wrapped_asset_code_id: u64,
-
-    // Contract owner address, it can make contract active/inactive
-    pub owner: CanonicalAddr,
-
-    // Asset locking fee
-    pub fee: Coin,
-}
-
-// Validator Action Approval(VAA) data
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct ParsedVAA {
-    pub version: u8,
-    pub guardian_set_index: u32,
-    pub len_signers: usize,
-    pub hash: Vec<u8>,
-    pub action: u8,
-    pub payload: Vec<u8>,
-}
-
-impl ParsedVAA {
-    /* VAA format:
-
-    header (length 6):
-    0   uint8   version (0x01)
-    1   uint32  guardian set index
-    5   uint8   len signatures
-
-    per signature (length 66):
-    0   uint8       index of the signer (in guardian keys)
-    1   [65]uint8   signature
-
-    body:
-    0   uint32  unix seconds
-    4   uint8   action
-    5   [payload_size]uint8 payload */
-
-    pub const HEADER_LEN: usize = 6;
-    pub const SIGNATURE_LEN: usize = 66;
-
-    pub const GUARDIAN_SET_INDEX_POS: usize = 1;
-    pub const LEN_SIGNER_POS: usize = 5;
-
-    pub const VAA_ACTION_POS: usize = 4;
-    pub const VAA_PAYLOAD_POS: usize = 5;
-
-    // Signature data offsets in the signature block
-    pub const SIG_DATA_POS: usize = 1;
-    // Signature length minus recovery id at the end
-    pub const SIG_DATA_LEN: usize = 64;
-    // Recovery byte is last after the main signature
-    pub const SIG_RECOVERY_POS: usize = Self::SIG_DATA_POS + Self::SIG_DATA_LEN;
-
-    pub fn deserialize(data: &[u8]) -> StdResult<Self> {
-        let version = data.get_u8(0);
-
-        // Load 4 bytes starting from index 1
-        let guardian_set_index: u32 = data.get_u32(Self::GUARDIAN_SET_INDEX_POS);
-        let len_signers = data.get_u8(Self::LEN_SIGNER_POS) as usize;
-        let body_offset: usize = Self::HEADER_LEN + Self::SIGNATURE_LEN * len_signers as usize;
-
-        // Hash the body
-        if body_offset >= data.len() {
-            return ContractError::InvalidVAA.std_err();
-        }
-        let body = &data[body_offset..];
-        let mut hasher = Keccak256::new();
-        hasher.update(body);
-        let hash = hasher.finalize().to_vec();
-
-        // Signatures valid, apply VAA
-        if body_offset + Self::VAA_PAYLOAD_POS > data.len() {
-            return ContractError::InvalidVAA.std_err();
-        }
-        let action = data.get_u8(body_offset + Self::VAA_ACTION_POS);
-        let payload = &data[body_offset + Self::VAA_PAYLOAD_POS..];
-
-        Ok(ParsedVAA {
-            version,
-            guardian_set_index,
-            len_signers,
-            hash,
-            action,
-            payload: payload.to_vec(),
-        })
-    }
-}
-
-// Guardian address
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct GuardianAddress {
-    pub bytes: Binary, // 20-byte addresses
-}
-
-#[cfg(test)]
-use hex;
-#[cfg(test)]
-impl GuardianAddress {
-    pub fn from(string: &str) -> GuardianAddress {
-        GuardianAddress {
-            bytes: hex::decode(string).expect("Decoding failed").into(),
-        }
-    }
-}
-
-// Guardian set information
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct GuardianSetInfo {
-    pub addresses: Vec<GuardianAddress>, // List of guardian addresses
-    pub expiration_time: u64,            // Guardian set expiration time
-}
-
-impl GuardianSetInfo {
-    pub fn quorum(&self) -> usize {
-        ((self.addresses.len() * 10 / 3) * 2) / 10 + 1
-    }
-}
-
-// Wormhole contract generic information
-#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
-pub struct WormholeInfo {
-    // Period for which a guardian set stays active after it has been replaced
-    pub guardian_set_expirity: u64,
-}
-
-pub fn config<S: Storage>(storage: &mut S) -> Singleton<S, ConfigInfo> {
-    singleton(storage, CONFIG_KEY)
-}
-
-pub fn config_read<S: Storage>(storage: &S) -> ReadonlySingleton<S, ConfigInfo> {
-    singleton_read(storage, CONFIG_KEY)
-}
-
-pub fn guardian_set_set<S: Storage>(
-    storage: &mut S,
-    index: u32,
-    data: &GuardianSetInfo,
-) -> StdResult<()> {
-    bucket(GUARDIAN_SET_KEY, storage).save(&index.to_le_bytes(), data)
-}
-
-pub fn guardian_set_get<S: Storage>(storage: &S, index: u32) -> StdResult<GuardianSetInfo> {
-    bucket_read(GUARDIAN_SET_KEY, storage).load(&index.to_le_bytes())
-}
-
-pub fn vaa_archive_add<S: Storage>(storage: &mut S, hash: &[u8]) -> StdResult<()> {
-    bucket(GUARDIAN_SET_KEY, storage).save(hash, &true)
-}
-
-pub fn vaa_archive_check<S: Storage>(storage: &S, hash: &[u8]) -> bool {
-    bucket_read(GUARDIAN_SET_KEY, storage)
-        .load(&hash)
-        .or::<bool>(Ok(false))
-        .unwrap()
-}
-
-pub fn wrapped_asset<S: Storage>(storage: &mut S) -> Bucket<S, HumanAddr> {
-    bucket(WRAPPED_ASSET_KEY, storage)
-}
-
-pub fn wrapped_asset_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, HumanAddr> {
-    bucket_read(WRAPPED_ASSET_KEY, storage)
-}
-
-pub fn wrapped_asset_address<S: Storage>(storage: &mut S) -> Bucket<S, Vec<u8>> {
-    bucket(WRAPPED_ASSET_ADDRESS_KEY, storage)
-}
-
-pub fn wrapped_asset_address_read<S: Storage>(storage: &S) -> ReadonlyBucket<S, Vec<u8>> {
-    bucket_read(WRAPPED_ASSET_ADDRESS_KEY, storage)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn build_guardian_set(length: usize) -> GuardianSetInfo {
-        let mut addresses: Vec<GuardianAddress> = Vec::with_capacity(length);
-        for _ in 0..length {
-            addresses.push(GuardianAddress {
-                bytes: vec![].into(),
-            });
-        }
-
-        GuardianSetInfo {
-            addresses,
-            expiration_time: 0,
-        }
-    }
-
-    #[test]
-    fn quardian_set_quorum() {
-        assert_eq!(build_guardian_set(1).quorum(), 1);
-        assert_eq!(build_guardian_set(2).quorum(), 2);
-        assert_eq!(build_guardian_set(3).quorum(), 3);
-        assert_eq!(build_guardian_set(4).quorum(), 3);
-        assert_eq!(build_guardian_set(5).quorum(), 4);
-        assert_eq!(build_guardian_set(6).quorum(), 5);
-        assert_eq!(build_guardian_set(7).quorum(), 5);
-        assert_eq!(build_guardian_set(8).quorum(), 6);
-        assert_eq!(build_guardian_set(9).quorum(), 7);
-        assert_eq!(build_guardian_set(10).quorum(), 7);
-        assert_eq!(build_guardian_set(11).quorum(), 8);
-        assert_eq!(build_guardian_set(12).quorum(), 9);
-        assert_eq!(build_guardian_set(20).quorum(), 14);
-        assert_eq!(build_guardian_set(25).quorum(), 17);
-        assert_eq!(build_guardian_set(100).quorum(), 67);
-    }
-}

+ 0 - 90
terra/contracts/wormhole/tests/integration.rs

@@ -1,90 +0,0 @@
-static WASM: &[u8] = include_bytes!("../../../target/wasm32-unknown-unknown/release/wormhole.wasm");
-
-use cosmwasm_std::{from_slice, Env, HumanAddr, InitResponse, Coin};
-use cosmwasm_storage::to_length_prefixed;
-use cosmwasm_vm::testing::{init, mock_env, mock_instance, MockApi, MockQuerier, MockStorage};
-use cosmwasm_vm::{Api, Instance, Storage};
-
-use wormhole::msg::InitMsg;
-use wormhole::state::{ConfigInfo, GuardianAddress, GuardianSetInfo, CONFIG_KEY};
-
-use hex;
-
-enum TestAddress {
-    INITIALIZER,
-}
-
-impl TestAddress {
-    fn value(&self) -> HumanAddr {
-        match self {
-            TestAddress::INITIALIZER => HumanAddr::from("initializer"),
-        }
-    }
-}
-
-fn mock_env_height(signer: &HumanAddr, height: u64, time: u64) -> Env {
-    let mut env = mock_env(signer, &[]);
-    env.block.height = height;
-    env.block.time = time;
-    env
-}
-
-fn get_config_info<S: Storage>(storage: &S) -> ConfigInfo {
-    let key = to_length_prefixed(CONFIG_KEY);
-    let data = storage
-        .get(&key)
-        .0
-        .expect("error getting data")
-        .expect("data should exist");
-    from_slice(&data).expect("invalid data")
-}
-
-fn do_init(
-    height: u64,
-    guardians: &Vec<GuardianAddress>,
-) -> Instance<MockStorage, MockApi, MockQuerier> {
-    let mut deps = mock_instance(WASM, &[]);
-    let init_msg = InitMsg {
-        initial_guardian_set: GuardianSetInfo {
-            addresses: guardians.clone(),
-            expiration_time: 100,
-        },
-        guardian_set_expirity: 50,
-        wrapped_asset_code_id: 999,
-    };
-    let env = mock_env_height(&TestAddress::INITIALIZER.value(), height, 0);
-    let owner = deps
-        .api
-        .canonical_address(&TestAddress::INITIALIZER.value())
-        .0
-        .unwrap();
-    let res: InitResponse = init(&mut deps, env, init_msg).unwrap();
-    assert_eq!(0, res.messages.len());
-
-    // query the store directly
-    deps.with_storage(|storage| {
-        assert_eq!(
-            get_config_info(storage),
-            ConfigInfo {
-                guardian_set_index: 0,
-                guardian_set_expirity: 50,
-                wrapped_asset_code_id: 999,
-                owner,
-                fee: Coin::new(10000, "uluna"),
-            }
-        );
-        Ok(())
-    })
-    .unwrap();
-    deps
-}
-
-#[test]
-fn init_works() {
-    let guardians = vec![GuardianAddress::from(GuardianAddress {
-        bytes: hex::decode("beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe")
-            .expect("Decoding failed")
-            .into(),
-    })];
-    let _deps = do_init(111, &guardians);
-}

+ 0 - 3
terra/devnet/Dockerfile

@@ -1,3 +0,0 @@
-FROM terramoney/localterra-core:0.4.5@sha256:ff4f342325c81dc19fad216c2e3c2aa8777d9dc36bc3521a206ec321864da4e3
-
-ADD config /root/.terrad/config

+ 0 - 4
terra/devnet/config/addrbook.json

@@ -1,4 +0,0 @@
-{
-	"key": "42a010b29b4d74efd44c4694",
-	"addrs": []
-}

+ 0 - 36
terra/devnet/config/app.toml

@@ -1,36 +0,0 @@
-# This is a TOML config file.
-# For more information, see https://github.com/toml-lang/toml
-
-##### main base config options #####
-
-# The minimum gas prices a validator is willing to accept for processing a
-# transaction. A transaction's fees must meet the minimum of any denomination
-# specified in this config (e.g. 0.25token1;0.0001token2).
-minimum-gas-prices = "0.00506uluna,0.0015uusd,0.00102usdr,1.7805ukrw,4.31626umnt"
-
-# default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
-# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
-# everything: all saved states will be deleted, storing only the current state; pruning at 10 block intervals
-# custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
-pruning = "default"
-
-# These are applied if and only if the pruning strategy is custom.
-pruning-keep-recent = "0"
-pruning-keep-every = "0"
-pruning-interval = "0"
-
-# HaltHeight contains a non-zero block height at which a node will gracefully
-# halt and shutdown that can be used to assist upgrades and testing.
-#
-# Note: Commitment of state will be attempted on the corresponding block.
-halt-height = 0
-
-# HaltTime contains a non-zero minimum block time (in Unix seconds) at which
-# a node will gracefully halt and shutdown that can be used to assist upgrades
-# and testing.
-#
-# Note: Commitment of state will be attempted on the corresponding block.
-halt-time = 0
-
-# InterBlockCache enables inter-block caching.
-inter-block-cache = true

+ 0 - 335
terra/devnet/config/config.toml

@@ -1,335 +0,0 @@
-# This is a TOML config file.
-# For more information, see https://github.com/toml-lang/toml
-
-# NOTE: Any path below can be absolute (e.g. "/var/myawesomeapp/data") or
-# relative to the home directory (e.g. "data"). The home directory is
-# "$HOME/.tendermint" by default, but could be changed via $TMHOME env variable
-# or --home cmd flag.
-
-##### main base config options #####
-
-# TCP or UNIX socket address of the ABCI application,
-# or the name of an ABCI application compiled in with the Tendermint binary
-proxy_app = "tcp://127.0.0.1:26656"
-
-# A custom human readable name for this node
-moniker = "localterra"
-
-# If this node is many blocks behind the tip of the chain, FastSync
-# allows them to catchup quickly by downloading blocks in parallel
-# and verifying their commits
-fast_sync = true
-
-# Database backend: goleveldb | cleveldb | boltdb | rocksdb
-# * goleveldb (github.com/syndtr/goleveldb - most popular implementation)
-#   - pure go
-#   - stable
-# * cleveldb (uses levigo wrapper)
-#   - fast
-#   - requires gcc
-#   - use cleveldb build tag (go build -tags cleveldb)
-# * boltdb (uses etcd's fork of bolt - github.com/etcd-io/bbolt)
-#   - EXPERIMENTAL
-#   - may be faster is some use-cases (random reads - indexer)
-#   - use boltdb build tag (go build -tags boltdb)
-# * rocksdb (uses github.com/tecbot/gorocksdb)
-#   - EXPERIMENTAL
-#   - requires gcc
-#   - use rocksdb build tag (go build -tags rocksdb)
-db_backend = "goleveldb"
-
-# Database directory
-db_dir = "data"
-
-# Output level for logging, including package level options
-log_level = "main:info,state:info,*:error"
-
-# Output format: 'plain' (colored text) or 'json'
-log_format = "plain"
-
-##### additional base config options #####
-
-# Path to the JSON file containing the initial validator set and other meta data
-genesis_file = "config/genesis.json"
-
-# Path to the JSON file containing the private key to use as a validator in the consensus protocol
-priv_validator_key_file = "config/priv_validator_key.json"
-
-# Path to the JSON file containing the last sign state of a validator
-priv_validator_state_file = "data/priv_validator_state.json"
-
-# TCP or UNIX socket address for Tendermint to listen on for
-# connections from an external PrivValidator process
-priv_validator_laddr = ""
-
-# Path to the JSON file containing the private key to use for node authentication in the p2p protocol
-node_key_file = "config/node_key.json"
-
-# Mechanism to connect to the ABCI application: socket | grpc
-abci = "socket"
-
-# TCP or UNIX socket address for the profiling server to listen on
-prof_laddr = "localhost:6060"
-
-# If true, query the ABCI app on connecting to a new peer
-# so the app can decide if we should keep the connection or not
-filter_peers = false
-
-##### advanced configuration options #####
-
-##### rpc server configuration options #####
-[rpc]
-
-# TCP or UNIX socket address for the RPC server to listen on
-laddr = "tcp://0.0.0.0:26657"
-
-# A list of origins a cross-domain request can be executed from
-# Default value '[]' disables cors support
-# Use '["*"]' to allow any origin
-cors_allowed_origins = []
-
-# A list of methods the client is allowed to use with cross-domain requests
-cors_allowed_methods = ["HEAD", "GET", "POST", ]
-
-# A list of non simple headers the client is allowed to use with cross-domain requests
-cors_allowed_headers = ["Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time", ]
-
-# TCP or UNIX socket address for the gRPC server to listen on
-# NOTE: This server only supports /broadcast_tx_commit
-grpc_laddr = ""
-
-# Maximum number of simultaneous connections.
-# Does not include RPC (HTTP&WebSocket) connections. See max_open_connections
-# If you want to accept a larger number than the default, make sure
-# you increase your OS limits.
-# 0 - unlimited.
-# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
-# 1024 - 40 - 10 - 50 = 924 = ~900
-grpc_max_open_connections = 900
-
-# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool
-unsafe = false
-
-# Maximum number of simultaneous connections (including WebSocket).
-# Does not include gRPC connections. See grpc_max_open_connections
-# If you want to accept a larger number than the default, make sure
-# you increase your OS limits.
-# 0 - unlimited.
-# Should be < {ulimit -Sn} - {MaxNumInboundPeers} - {MaxNumOutboundPeers} - {N of wal, db and other open files}
-# 1024 - 40 - 10 - 50 = 924 = ~900
-max_open_connections = 900
-
-# Maximum number of unique clientIDs that can /subscribe
-# If you're using /broadcast_tx_commit, set to the estimated maximum number
-# of broadcast_tx_commit calls per block.
-max_subscription_clients = 100
-
-# Maximum number of unique queries a given client can /subscribe to
-# If you're using GRPC (or Local RPC client) and /broadcast_tx_commit, set to
-# the estimated # maximum number of broadcast_tx_commit calls per block.
-max_subscriptions_per_client = 5
-
-# How long to wait for a tx to be committed during /broadcast_tx_commit.
-# WARNING: Using a value larger than 10s will result in increasing the
-# global HTTP write timeout, which applies to all connections and endpoints.
-# See https://github.com/tendermint/tendermint/issues/3435
-timeout_broadcast_tx_commit = "10s"
-
-# Maximum size of request body, in bytes
-max_body_bytes = 1000000
-
-# Maximum size of request header, in bytes
-max_header_bytes = 1048576
-
-# The path to a file containing certificate that is used to create the HTTPS server.
-# Migth be either absolute path or path related to tendermint's config directory.
-# If the certificate is signed by a certificate authority,
-# the certFile should be the concatenation of the server's certificate, any intermediates,
-# and the CA's certificate.
-# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server.
-# Otherwise, HTTP server is run.
-tls_cert_file = ""
-
-# The path to a file containing matching private key that is used to create the HTTPS server.
-# Migth be either absolute path or path related to tendermint's config directory.
-# NOTE: both tls_cert_file and tls_key_file must be present for Tendermint to create HTTPS server.
-# Otherwise, HTTP server is run.
-tls_key_file = ""
-
-##### peer to peer configuration options #####
-[p2p]
-
-# Address to listen for incoming connections
-laddr = "tcp://0.0.0.0:26660"
-
-# Address to advertise to peers for them to dial
-# If empty, will use the same port as the laddr,
-# and will introspect on the listener or use UPnP
-# to figure out the address.
-external_address = ""
-
-# Comma separated list of seed nodes to connect to
-seeds = ""
-
-# Comma separated list of nodes to keep persistent connections to
-persistent_peers = ""
-
-# UPNP port forwarding
-upnp = false
-
-# Path to address book
-addr_book_file = "config/addrbook.json"
-
-# Set true for strict address routability rules
-# Set false for private or local networks
-addr_book_strict = true
-
-# Maximum number of inbound peers
-max_num_inbound_peers = 40
-
-# Maximum number of outbound peers to connect to, excluding persistent peers
-max_num_outbound_peers = 10
-
-# List of node IDs, to which a connection will be (re)established ignoring any existing limits
-unconditional_peer_ids = ""
-
-# Maximum pause when redialing a persistent peer (if zero, exponential backoff is used)
-persistent_peers_max_dial_period = "0s"
-
-# Time to wait before flushing messages out on the connection
-flush_throttle_timeout = "100ms"
-
-# Maximum size of a message packet payload, in bytes
-max_packet_msg_payload_size = 4096
-
-# Rate at which packets can be sent, in bytes/second
-send_rate = 5120000
-
-# Rate at which packets can be received, in bytes/second
-recv_rate = 5120000
-
-# Set true to enable the peer-exchange reactor
-pex = true
-
-# Seed mode, in which node constantly crawls the network and looks for
-# peers. If another node asks it for addresses, it responds and disconnects.
-#
-# Does not work if the peer-exchange reactor is disabled.
-seed_mode = false
-
-# Comma separated list of peer IDs to keep private (will not be gossiped to other peers)
-private_peer_ids = ""
-
-# Toggle to disable guard against peers connecting from the same ip.
-allow_duplicate_ip = false
-
-# Peer connection configuration.
-handshake_timeout = "20s"
-dial_timeout = "3s"
-
-##### mempool configuration options #####
-[mempool]
-
-recheck = true
-broadcast = true
-wal_dir = ""
-
-# Maximum number of transactions in the mempool
-size = 5000
-
-# Limit the total size of all txs in the mempool.
-# This only accounts for raw transactions (e.g. given 1MB transactions and
-# max_txs_bytes=5MB, mempool will only accept 5 transactions).
-max_txs_bytes = 1073741824
-
-# Size of the cache (used to filter transactions we saw earlier) in transactions
-cache_size = 10000
-
-# Maximum size of a single transaction.
-# NOTE: the max size of a tx transmitted over the network is {max_tx_bytes} + {amino overhead}.
-max_tx_bytes = 1048576
-
-##### fast sync configuration options #####
-[fastsync]
-
-# Fast Sync version to use:
-#   1) "v0" (default) - the legacy fast sync implementation
-#   2) "v1" - refactor of v0 version for better testability
-#   3) "v2" - refactor of v1 version for better usability
-version = "v0"
-
-##### consensus configuration options #####
-[consensus]
-
-wal_file = "data/cs.wal/wal"
-
-timeout_propose = "3s"
-timeout_propose_delta = "500ms"
-timeout_prevote = "1s"
-timeout_prevote_delta = "500ms"
-timeout_precommit = "1s"
-timeout_precommit_delta = "500ms"
-timeout_commit = "5s"
-
-# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0)
-skip_timeout_commit = false
-
-# EmptyBlocks mode and possible interval between empty blocks
-create_empty_blocks = true
-create_empty_blocks_interval = "0s"
-
-# Reactor sleep duration parameters
-peer_gossip_sleep_duration = "100ms"
-peer_query_maj23_sleep_duration = "2s"
-
-##### transactions indexer configuration options #####
-[tx_index]
-
-# What indexer to use for transactions
-#
-# Options:
-#   1) "null"
-#   2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
-indexer = "kv"
-
-# Comma-separated list of compositeKeys to index (by default the only key is "tx.hash")
-# Remember that Event has the following structure: type.key
-# type: [
-#  key: value,
-#  ...
-# ]
-#
-# You can also index transactions by height by adding "tx.height" key here.
-#
-# It's recommended to index only a subset of keys due to possible memory
-# bloat. This is, of course, depends on the indexer's DB and the volume of
-# transactions.
-index_keys = "tx.hash,tx.height,message.action,message.sender,submit_proposal.proposal_id,proposal_vote.proposal_id,proposal_deposit.proposal_id"
-
-# When set to true, tells indexer to index all compositeKeys (predefined keys:
-# "tx.hash", "tx.height" and all keys from DeliverTx responses).
-#
-# Note this may be not desirable (see the comment above). IndexKeys has a
-# precedence over IndexAllKeys (i.e. when given both, IndexKeys will be
-# indexed).
-index_all_keys = false
-
-##### instrumentation configuration options #####
-[instrumentation]
-
-# When true, Prometheus metrics are served under /metrics on
-# PrometheusListenAddr.
-# Check out the documentation for the list of available metrics.
-prometheus = false
-
-# Address to listen for Prometheus collector(s) connections
-prometheus_listen_addr = ":26660"
-
-# Maximum number of simultaneous connections.
-# If you want to accept a larger number than the default, make sure
-# you increase your OS limits.
-# 0 - unlimited.
-max_open_connections = 3
-
-# Instrumentation namespace
-namespace = "tendermint"

+ 0 - 623
terra/devnet/config/genesis.json

@@ -1,623 +0,0 @@
-{
-  "genesis_time": "2020-08-24T08:43:02.336889Z",
-  "chain_id": "localterra",
-  "consensus_params": {
-    "block": {
-      "max_bytes": "22020096",
-      "max_gas": "-1",
-      "time_iota_ms": "1000"
-    },
-    "evidence": {
-      "max_age_num_blocks": "100000",
-      "max_age_duration": "172800000000000"
-    },
-    "validator": {
-      "pub_key_types": [
-        "ed25519"
-      ]
-    }
-  },
-  "app_hash": "",
-  "app_state": {
-    "auth": {
-      "params": {
-        "max_memo_characters": "256",
-        "tx_sig_limit": "7",
-        "tx_size_cost_per_byte": "10",
-        "sig_verify_cost_ed25519": "590",
-        "sig_verify_cost_secp256k1": "1000"
-      },
-      "accounts": [
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra17lmam6zguazs5q5u6z5mmx76uj63gldnse2pdp",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1757tkx08n0cqrw7p86ny9lnxsqeth0wgp0em95",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra199vw7724lzkwz6lf2hsx04lrxfkz09tg8dlp6r",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra18wlvftxzj6zt0xugy2lr9nxzu402690ltaf4ss",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1e8ryd9ezefuucd4mje33zdms9m2s90m57878v9",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra17tv2hvwpg0ukqgd2y5ct2w54fyan7z0zxrm2f9",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1lkccuqgj6sjwjn8gsa9xlklqv4pmrqg9dx2fxc",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1333veey879eeqcff8j3gfcgwt8cfrg9mq20v6f",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        },
-        {
-          "type": "core/Account",
-          "value": {
-            "address": "terra1fmcjjt6yc9wqup2r06urnrd928jhrde6gcld6n",
-            "coins": [
-              {
-                "denom": "ukrw",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "uluna",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "umnt",
-                "amount": "1000000000000000000"
-              },
-              {
-                "denom": "usdr",
-                "amount": "1000000000000000"
-              },
-              {
-                "denom": "uusd",
-                "amount": "10000000000000000"
-              }
-            ],
-            "public_key": null,
-            "account_number": "0",
-            "sequence": "0"
-          }
-        }
-      ]
-    },
-    "params": null,
-    "distribution": {
-      "params": {
-        "community_tax": "0.020000000000000000",
-        "base_proposer_reward": "0.010000000000000000",
-        "bonus_proposer_reward": "0.040000000000000000",
-        "withdraw_addr_enabled": true
-      },
-      "fee_pool": {
-        "community_pool": []
-      },
-      "delegator_withdraw_infos": [],
-      "previous_proposer": "",
-      "outstanding_rewards": [],
-      "validator_accumulated_commissions": [],
-      "validator_historical_rewards": [],
-      "validator_current_rewards": [],
-      "delegator_starting_infos": [],
-      "validator_slash_events": []
-    },
-    "msgauth": {
-      "authorization_entries": []
-    },
-    "wasm": {
-      "params": {
-        "max_contract_size": "512000",
-        "max_contract_gas": "100000000",
-        "max_contract_msg_size": "4096"
-      },
-      "last_code_id": "0",
-      "last_instance_id": "0",
-      "codes": [],
-      "contracts": []
-    },
-    "mint": {
-      "minter": {
-        "inflation": "0.130000000000000000",
-        "annual_provisions": "0.000000000000000000"
-      },
-      "params": {
-        "mint_denom": "uluna",
-        "inflation_rate_change": "0.130000000000000000",
-        "inflation_max": "0.200000000000000000",
-        "inflation_min": "0.070000000000000000",
-        "goal_bonded": "0.670000000000000000",
-        "blocks_per_year": "6311520"
-      }
-    },
-    "upgrade": {},
-    "bank": {
-      "send_enabled": true
-    },
-    "gov": {
-      "starting_proposal_id": "1",
-      "deposits": null,
-      "votes": null,
-      "proposals": null,
-      "deposit_params": {
-        "min_deposit": [
-          {
-            "denom": "uluna",
-            "amount": "10000000"
-          }
-        ],
-        "max_deposit_period": "172800000000000"
-      },
-      "voting_params": {
-        "voting_period": "300000000000"
-      },
-      "tally_params": {
-        "quorum": "0.334000000000000000",
-        "threshold": "0.500000000000000000",
-        "veto": "0.334000000000000000"
-      }
-    },
-    "staking": {
-      "params": {
-        "unbonding_time": "1814400000000000",
-        "max_validators": 100,
-        "max_entries": 100,
-        "historical_entries": 0,
-        "bond_denom": "uluna"
-      },
-      "last_total_power": "0",
-      "last_validator_powers": null,
-      "validators": null,
-      "delegations": null,
-      "unbonding_delegations": null,
-      "redelegations": null,
-      "exported": false
-    },
-    "supply": {
-      "supply": []
-    },
-    "treasury": {
-      "params": {
-        "tax_policy": {
-          "rate_min": "0.000500000000000000",
-          "rate_max": "0.010000000000000000",
-          "cap": {
-            "denom": "usdr",
-            "amount": "1000000"
-          },
-          "change_max": "0.000250000000000000"
-        },
-        "reward_policy": {
-          "rate_min": "0.050000000000000000",
-          "rate_max": "0.500000000000000000",
-          "cap": {
-            "denom": "unused",
-            "amount": "0"
-          },
-          "change_max": "0.025000000000000000"
-        },
-        "seigniorage_burden_target": "0.670000000000000000",
-        "mining_increment": "1.070000000000000000",
-        "window_short": "4",
-        "window_long": "52",
-        "window_probation": "12"
-      },
-      "tax_rate": "0.001000000000000000",
-      "reward_weight": "0.050000000000000000",
-      "tax_caps": {},
-      "tax_proceed": [],
-      "epoch_initial_issuance": [],
-      "cumulated_height": "0",
-      "TRs": [],
-      "SRs": [],
-      "TSLs": []
-    },
-    "genutil": {
-      "gentxs": [
-        {
-          "type": "core/StdTx",
-          "value": {
-            "msg": [
-              {
-                "type": "staking/MsgCreateValidator",
-                "value": {
-                  "description": {
-                    "moniker": "localterra",
-                    "identity": "",
-                    "website": "https://github.com/terra-project/LocalTerra",
-                    "security_contact": "",
-                    "details": ""
-                  },
-                  "commission": {
-                    "rate": "0.100000000000000000",
-                    "max_rate": "0.200000000000000000",
-                    "max_change_rate": "0.010000000000000000"
-                  },
-                  "min_self_delegation": "1",
-                  "delegator_address": "terra1dcegyrekltswvyy0xy69ydgxn9x8x32zdtapd8",
-                  "validator_address": "terravaloper1dcegyrekltswvyy0xy69ydgxn9x8x32zdy3ua5",
-                  "pubkey": "terravalconspub1zcjduepqn7ju9nnv6fl0l6a24ha3a52s8nm948athu5fy3e05vjpapw3rhksszjl92",
-                  "value": {
-                    "denom": "uluna",
-                    "amount": "1000000000"
-                  }
-                }
-              }
-            ],
-            "fee": {
-              "amount": [],
-              "gas": "200000"
-            },
-            "signatures": [
-              {
-                "pub_key": {
-                  "type": "tendermint/PubKeySecp256k1",
-                  "value": "An4JQUJX6KTbh6CvqmDLPhe6knWdqfKYjDvkCl2QE1oc"
-                },
-                "signature": "mj0RNoIqzPCfTjWwA0R7jcUBA5Yr820vgk5sFiN98Vx4ifKTU6qtVRCpXExR6B9pDHZ/1PXyGXMp8DEGZQ/Vxg=="
-              }
-            ],
-            "memo": "e9059223d8b9a9539394a05f996f63ce7bb3a405@10.10.20.74:26656"
-          }
-        }
-      ]
-    },
-    "crisis": {
-      "constant_fee": {
-        "denom": "uluna",
-        "amount": "1000"
-      }
-    },
-    "slashing": {
-      "params": {
-        "signed_blocks_window": "100",
-        "min_signed_per_window": "0.500000000000000000",
-        "downtime_jail_duration": "600000000000",
-        "slash_fraction_double_sign": "0.050000000000000000",
-        "slash_fraction_downtime": "0.010000000000000000"
-      },
-      "signing_infos": {},
-      "missed_blocks": {}
-    },
-    "oracle": {
-      "params": {
-        "vote_period": "5",
-        "vote_threshold": "0.500000000000000000",
-        "reward_band": "0.020000000000000000",
-        "reward_distribution_window": "5256000",
-        "whitelist": [
-          {
-            "name": "ukrw",
-            "tobin_tax": "0.002500000000000000"
-          },
-          {
-            "name": "usdr",
-            "tobin_tax": "0.002500000000000000"
-          },
-          {
-            "name": "uusd",
-            "tobin_tax": "0.002500000000000000"
-          },
-          {
-            "name": "umnt",
-            "tobin_tax": "0.020000000000000000"
-          }
-        ],
-        "slash_fraction": "0.000100000000000000",
-        "slash_window": "100800",
-        "min_valid_per_window": "0.050000000000000000"
-      },
-      "feeder_delegations": {},
-      "exchange_rates": {},
-      "exchange_rate_prevotes": [],
-      "exchange_rate_votes": [],
-      "miss_counters": {},
-      "aggregate_exchange_rate_prevotes": [],
-      "aggregate_exchange_rate_votes": [],
-      "tobin_taxes": {}
-    },
-    "market": {
-      "terra_pool_delta": "0.000000000000000000",
-      "params": {
-        "base_pool": "625000000000",
-        "pool_recovery_period": "14400",
-        "min_spread": "0.005"
-      }
-    },
-    "evidence": {
-      "params": {
-        "max_evidence_age": "120000000000"
-      },
-      "evidence": []
-    }
-  }
-}

+ 0 - 1
terra/devnet/config/node_key.json

@@ -1 +0,0 @@
-{"priv_key":{"type":"tendermint/PrivKeyEd25519","value":"4XSNmmjHbNiAnP0qrXwJ1otOzZuObJXXvg+ASKlxjVwdP4Tlh4TA6iSpQ/5EF9aFpDGUqcTnJY8u3/FfHiIl2A=="}}

+ 0 - 11
terra/devnet/config/priv_validator_key.json

@@ -1,11 +0,0 @@
-{
-  "address": "411E0995BFCA0250FC442B8B330786F6ACA56D9D",
-  "pub_key": {
-    "type": "tendermint/PubKeyEd25519",
-    "value": "n6XCzmzSfv/rqq37HtFQPPZan6u/KJJHL6MkHoXRHe0="
-  },
-  "priv_key": {
-    "type": "tendermint/PrivKeyEd25519",
-    "value": "GYmv6DOzsd8kVS3JU1TkZr6MOr0CFCBzTCxNYquyDK2fpcLObNJ+/+uqrfse0VA89lqfq78okkcvoyQehdEd7Q=="
-  }
-}

+ 0 - 9
terra/devnet/config/terrad.toml

@@ -1,9 +0,0 @@
-# This is a TOML config file.
-# For more information, see https://github.com/toml-lang/toml
-
-##### main base config options #####
-
-# The minimum gas prices a validator is willing to accept for processing a
-# transaction. A transaction's fees must meet the minimum of any denomination
-# specified in this config (e.g. 0.25token1;0.0001token2).
-minimum-gas-prices = ""

+ 0 - 15
terra/devnet/config/wasm.toml

@@ -1,15 +0,0 @@
-# This is a TOML config file.
-# For more information, see https://github.com/toml-lang/toml
-
-##### main base config options #####
-
-# The maximum gas amount can be spent for contract query.
-# The contract query will invoke contract execution vm,
-# so we need to restrict the max usage to prevent DoS attack
-contract-query-gas-limit = "3000000"
-
-# Storing instances in the LRU will have no effect on the results 
-# (still deterministic), but should lower execution time at 
-# the cost of increased memory usage. We cannot pick universal 
-# parameters for this, so we should allow node operators to set it.
-lru-size = "0"

+ 0 - 5
terra/tools/deploy.sh

@@ -1,5 +0,0 @@
-npm run prepare-token http://terra-lcd:1317
-npm run prepare-wormhole http://terra-lcd:1317
-npm run lock-token terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 terra174kgn5rtw4kf6f938wm7kwh70h2v4vcfd26jlc 1000 http://terra-lcd:1317
-echo "Going to sleep, interrupt if running manually"
-sleep infinity

+ 0 - 26
terra/tools/lock-token.ts

@@ -1,26 +0,0 @@
-import { init_lcd, execute_contract, execute_contract_with_fee, query_contract } from './utils';
-
-async function script() {
-    if (process.argv.length < 5) {
-        console.log('Required 3 params TOKEN_CONTRACT, WORMHOLE_CONTRACT, integer AMOUNT');
-    }
-    let token_contract = process.argv[2];
-    let wormhole_contract = process.argv[3];
-    let amount = process.argv[4];
-
-    let allowanceResult = await execute_contract(token_contract, {increase_allowance: {spender: wormhole_contract, amount}});
-    if (allowanceResult == null) return;
-    console.log('Allowance increased');
-    let lockResult = await execute_contract_with_fee(wormhole_contract, {lock_assets: {
-            asset: token_contract, 
-            amount,
-            recipient: Buffer.from('00000000000000000000000019a4437E2BA06bF1FA42C56Fb269Ca0d30f60716', 'hex').toString('base64'),
-            target_chain: 2, // Ethereum
-            nonce: Date.now() % 1000000
-        }});
-    if (lockResult == null) return;
-    console.log('Tokens locked');
-}
-
-init_lcd(process.argv[5]);
-script();

+ 0 - 494
terra/tools/package-lock.json

@@ -1,494 +0,0 @@
-{
-  "name": "terra-contract-tools",
-  "version": "1.0.0",
-  "lockfileVersion": 1,
-  "requires": true,
-  "dependencies": {
-    "@terra-money/terra.js": {
-      "version": "0.5.12",
-      "resolved": "https://registry.npmjs.org/@terra-money/terra.js/-/terra.js-0.5.12.tgz",
-      "integrity": "sha512-3UZWMT3Tu+MOQrHZ8uEtHL+hurKbe1QVm8x+qWOe4+IOeY3eWdVeBIkHflhGI8NU31v/r5eznbTuy7kEMA4CMQ==",
-      "requires": {
-        "axios": "^0.20.0",
-        "bech32": "^1.1.4",
-        "bip32": "^2.0.6",
-        "bip39": "^3.0.2",
-        "bufferutil": "^4.0.1",
-        "crypto-js": "3.3.0",
-        "decimal.js": "^10.2.1",
-        "post-message-stream": "^3.0.0",
-        "secp256k1": "^4.0.2",
-        "utf-8-validate": "^5.0.2",
-        "ws": "^7.3.1"
-      }
-    },
-    "@types/node": {
-      "version": "14.14.6",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz",
-      "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw=="
-    },
-    "arg": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
-      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
-    },
-    "axios": {
-      "version": "0.20.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
-      "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
-      "requires": {
-        "follow-redirects": "^1.10.0"
-      }
-    },
-    "base-x": {
-      "version": "3.0.8",
-      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
-      "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "bech32": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
-      "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
-    },
-    "bindings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "requires": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
-    "bip32": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz",
-      "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==",
-      "requires": {
-        "@types/node": "10.12.18",
-        "bs58check": "^2.1.1",
-        "create-hash": "^1.2.0",
-        "create-hmac": "^1.1.7",
-        "tiny-secp256k1": "^1.1.3",
-        "typeforce": "^1.11.5",
-        "wif": "^2.0.6"
-      },
-      "dependencies": {
-        "@types/node": {
-          "version": "10.12.18",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
-          "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ=="
-        }
-      }
-    },
-    "bip39": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz",
-      "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==",
-      "requires": {
-        "@types/node": "11.11.6",
-        "create-hash": "^1.1.0",
-        "pbkdf2": "^3.0.9",
-        "randombytes": "^2.0.1"
-      },
-      "dependencies": {
-        "@types/node": {
-          "version": "11.11.6",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz",
-          "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ=="
-        }
-      }
-    },
-    "bn.js": {
-      "version": "4.11.9",
-      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
-      "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw=="
-    },
-    "brorand": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
-    },
-    "bs58": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
-      "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
-      "requires": {
-        "base-x": "^3.0.2"
-      }
-    },
-    "bs58check": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
-      "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
-      "requires": {
-        "bs58": "^4.0.0",
-        "create-hash": "^1.1.0",
-        "safe-buffer": "^5.1.2"
-      }
-    },
-    "buffer-from": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
-      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
-    },
-    "bufferutil": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.2.tgz",
-      "integrity": "sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==",
-      "requires": {
-        "node-gyp-build": "^4.2.0"
-      }
-    },
-    "cipher-base": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
-      "requires": {
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "core-util-is": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
-      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
-    },
-    "create-hash": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
-      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
-      "requires": {
-        "cipher-base": "^1.0.1",
-        "inherits": "^2.0.1",
-        "md5.js": "^1.3.4",
-        "ripemd160": "^2.0.1",
-        "sha.js": "^2.4.0"
-      }
-    },
-    "create-hmac": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
-      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
-      "requires": {
-        "cipher-base": "^1.0.3",
-        "create-hash": "^1.1.0",
-        "inherits": "^2.0.1",
-        "ripemd160": "^2.0.0",
-        "safe-buffer": "^5.0.1",
-        "sha.js": "^2.4.8"
-      }
-    },
-    "crypto-js": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz",
-      "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q=="
-    },
-    "decimal.js": {
-      "version": "10.2.1",
-      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz",
-      "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw=="
-    },
-    "diff": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
-    },
-    "elliptic": {
-      "version": "6.5.3",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
-      "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
-      "requires": {
-        "bn.js": "^4.4.0",
-        "brorand": "^1.0.1",
-        "hash.js": "^1.0.0",
-        "hmac-drbg": "^1.0.0",
-        "inherits": "^2.0.1",
-        "minimalistic-assert": "^1.0.0",
-        "minimalistic-crypto-utils": "^1.0.0"
-      }
-    },
-    "file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
-    },
-    "follow-redirects": {
-      "version": "1.13.0",
-      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
-      "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
-    },
-    "hash-base": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
-      "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
-      "requires": {
-        "inherits": "^2.0.4",
-        "readable-stream": "^3.6.0",
-        "safe-buffer": "^5.2.0"
-      }
-    },
-    "hash.js": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
-      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
-      "requires": {
-        "inherits": "^2.0.3",
-        "minimalistic-assert": "^1.0.1"
-      }
-    },
-    "hmac-drbg": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
-      "requires": {
-        "hash.js": "^1.0.3",
-        "minimalistic-assert": "^1.0.0",
-        "minimalistic-crypto-utils": "^1.0.1"
-      }
-    },
-    "inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
-    },
-    "isarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
-    },
-    "make-error": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
-      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="
-    },
-    "md5.js": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
-      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
-      "requires": {
-        "hash-base": "^3.0.0",
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.1.2"
-      }
-    },
-    "minimalistic-assert": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
-    },
-    "minimalistic-crypto-utils": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
-    },
-    "nan": {
-      "version": "2.14.2",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz",
-      "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ=="
-    },
-    "node-addon-api": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
-      "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
-    },
-    "node-gyp-build": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
-      "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
-    },
-    "pbkdf2": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",
-      "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==",
-      "requires": {
-        "create-hash": "^1.1.2",
-        "create-hmac": "^1.1.4",
-        "ripemd160": "^2.0.1",
-        "safe-buffer": "^5.0.1",
-        "sha.js": "^2.4.8"
-      }
-    },
-    "post-message-stream": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/post-message-stream/-/post-message-stream-3.0.0.tgz",
-      "integrity": "sha1-kNn1S9IJ5rb110eVuHWIIFtUcEg=",
-      "requires": {
-        "readable-stream": "^2.1.4"
-      },
-      "dependencies": {
-        "readable-stream": {
-          "version": "2.3.7",
-          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
-          "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
-          "requires": {
-            "core-util-is": "~1.0.0",
-            "inherits": "~2.0.3",
-            "isarray": "~1.0.0",
-            "process-nextick-args": "~2.0.0",
-            "safe-buffer": "~5.1.1",
-            "string_decoder": "~1.1.1",
-            "util-deprecate": "~1.0.1"
-          }
-        },
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
-        },
-        "string_decoder": {
-          "version": "1.1.1",
-          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-          "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-          "requires": {
-            "safe-buffer": "~5.1.0"
-          }
-        }
-      }
-    },
-    "process-nextick-args": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
-    },
-    "randombytes": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
-      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-      "requires": {
-        "safe-buffer": "^5.1.0"
-      }
-    },
-    "readable-stream": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-      "requires": {
-        "inherits": "^2.0.3",
-        "string_decoder": "^1.1.1",
-        "util-deprecate": "^1.0.1"
-      }
-    },
-    "ripemd160": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
-      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
-      "requires": {
-        "hash-base": "^3.0.0",
-        "inherits": "^2.0.1"
-      }
-    },
-    "safe-buffer": {
-      "version": "5.2.1",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
-    },
-    "secp256k1": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz",
-      "integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==",
-      "requires": {
-        "elliptic": "^6.5.2",
-        "node-addon-api": "^2.0.0",
-        "node-gyp-build": "^4.2.0"
-      }
-    },
-    "sha.js": {
-      "version": "2.4.11",
-      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
-      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
-      "requires": {
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
-    },
-    "source-map-support": {
-      "version": "0.5.19",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
-      "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
-      "requires": {
-        "buffer-from": "^1.0.0",
-        "source-map": "^0.6.0"
-      }
-    },
-    "string_decoder": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "requires": {
-        "safe-buffer": "~5.2.0"
-      }
-    },
-    "tiny-secp256k1": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.5.tgz",
-      "integrity": "sha512-duE2hSLSQIpHGzmK48OgRrGTi+4OTkXLC6aa86uOYQ6LLCYZSarVKIAvEtY7MoXjoL6bOXMSerEGMzrvW4SkDw==",
-      "requires": {
-        "bindings": "^1.3.0",
-        "bn.js": "^4.11.8",
-        "create-hmac": "^1.1.7",
-        "elliptic": "^6.4.0",
-        "nan": "^2.13.2"
-      }
-    },
-    "ts-node": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz",
-      "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==",
-      "requires": {
-        "arg": "^4.1.0",
-        "diff": "^4.0.1",
-        "make-error": "^1.1.1",
-        "source-map-support": "^0.5.17",
-        "yn": "3.1.1"
-      }
-    },
-    "typeforce": {
-      "version": "1.18.0",
-      "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
-      "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
-    },
-    "typescript": {
-      "version": "4.1.2",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
-      "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ=="
-    },
-    "utf-8-validate": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz",
-      "integrity": "sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==",
-      "requires": {
-        "node-gyp-build": "^4.2.0"
-      }
-    },
-    "util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
-    },
-    "wif": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
-      "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=",
-      "requires": {
-        "bs58check": "<3.0.0"
-      }
-    },
-    "ws": {
-      "version": "7.3.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
-      "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
-    },
-    "yn": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
-      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="
-    }
-  }
-}

+ 0 - 22
terra/tools/package.json

@@ -1,22 +0,0 @@
-{
-  "name": "terra-contract-tools",
-  "version": "1.0.0",
-  "description": "Tools to build and deploy Terra contracts",
-  "repository": "https://github.com/certusone/wormhole",
-  "main": "index.js",
-  "scripts": {
-    "build-contracts": "( cd .. && docker run --rm -v \"$(pwd)\":/code --mount type=volume,source=\"$(basename \"$(pwd)\")_cache\",target=/code/target --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry cosmwasm/workspace-optimizer:0.10.4 )",
-    "prepare-token": "ts-node prepare-token.ts",
-    "prepare-wormhole": "ts-node prepare-wormhole.ts",
-    "lock-token": "ts-node lock-token.ts",
-    "submit-vaa": "ts-node submit-vaa.ts"
-  },
-  "author": "",
-  "license": "ISC",
-  "dependencies": {
-    "@terra-money/terra.js": "^0.5.12",
-    "@types/node": "^14.14.6",
-    "ts-node": "^9.0.0",
-    "typescript": "^4.1.2"
-  }
-}

+ 0 - 28
terra/tools/prepare-token.ts

@@ -1,28 +0,0 @@
-import { init_lcd, deploy_contract, instantiate_contract, query_contract } from './utils';
-
-async function script() {
-    const TEST_ADDRESS: string = 'terra1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v';
-    // cw20_base.wasm is a binary artifact built from https://github.com/CosmWasm/cosmwasm-plus repository at v0.2.0
-    // and is a standard base cw20 contract. Is it used for testing only.
-    let code_id = await deploy_contract('../artifacts/cw20_base.wasm');
-    if (code_id == -1) return;
-    console.log(`Program deployed with code id ${code_id}`);
-    let contract_address = await instantiate_contract(code_id, {
-        name: 'Test token',
-        symbol: 'TST',
-        decimals: 8,
-        initial_balances: [{
-            address: TEST_ADDRESS,
-            amount: '100000000000000',
-        }],
-        mint: null,
-    });
-    console.log(`Contract instance created at ${contract_address}`);
-
-    // Verify if token was minted to the test address
-    let result = await query_contract(contract_address, {balance: { address : TEST_ADDRESS}});
-    console.log(`${TEST_ADDRESS} balance is ${result.balance}`);
-}
-
-init_lcd(process.argv[2]);
-script();

+ 0 - 27
terra/tools/prepare-wormhole.ts

@@ -1,27 +0,0 @@
-import { init_lcd, deploy_contract, instantiate_contract, query_contract } from './utils';
-
-async function script() {
-    // Deploy cw20-wrapped
-    let wrapped_code_id = await deploy_contract('../artifacts/cw20_wrapped.wasm');
-    if (wrapped_code_id == -1) return;
-    console.log(`CW20 Wrapped program deployed with code id ${wrapped_code_id}`);
-    // Deploy wormhole
-    let wormhole_code_id = await deploy_contract('../artifacts/wormhole.wasm');
-    if (wormhole_code_id == -1) return;
-    console.log(`Wormhole program deployed with code id ${wormhole_code_id}`);
-    // Instantiate wormhole
-    let contract_address = await instantiate_contract(wormhole_code_id, {
-        initial_guardian_set: {
-            addresses: [
-                { bytes: Buffer.from('beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe', 'hex').toString('base64') }
-            ],
-            expiration_time: Math.floor(Date.now() / 1000) + 1000 * 60 * 60
-        },
-        guardian_set_expirity: 0,
-        wrapped_asset_code_id: wrapped_code_id,
-    });
-    console.log(`Wormhole instance created at ${contract_address}`);
-}
-
-init_lcd(process.argv[2]);
-script();

Неке датотеке нису приказане због велике количине промена