diff --git a/cspell-custom-words.txt b/cspell-custom-words.txt index a5817c4ad8..bf8041941b 100644 --- a/cspell-custom-words.txt +++ b/cspell-custom-words.txt @@ -280,3 +280,4 @@ Zellic soroban stroops contractimpl +deallocation diff --git a/node/cmd/guardiand/node.go b/node/cmd/guardiand/node.go index 54161d2a54..3a949f9e89 100644 --- a/node/cmd/guardiand/node.go +++ b/node/cmd/guardiand/node.go @@ -18,6 +18,7 @@ import ( "github.com/certusone/wormhole/node/pkg/guardiansigner" "github.com/certusone/wormhole/node/pkg/watchers" "github.com/certusone/wormhole/node/pkg/watchers/ibc" + "github.com/certusone/wormhole/node/pkg/watchers/stellar" ethcrypto "github.com/ethereum/go-ethereum/crypto" "github.com/certusone/wormhole/node/pkg/watchers/cosmwasm" @@ -244,6 +245,9 @@ var ( holeskyRPC *string holeskyContract *string + stellarRPC *string + stellarContract *string + arbitrumSepoliaRPC *string arbitrumSepoliaContract *string @@ -436,6 +440,13 @@ func init() { holeskyRPC = node.RegisterFlagWithValidationOrFail(NodeCmd, "holeskyRPC", "Holesky RPC URL", "ws://eth-devnet:8545", []string{"ws", "wss"}) holeskyContract = NodeCmd.Flags().String("holeskyContract", "", "Holesky contract address") + stellarRPC = node.RegisterFlagWithValidationOrFail( + NodeCmd, "stellarRPC", "Stellar (Soroban) RPC URL", "http://stellar.default.svc.cluster.local:8000/soroban/rpc", []string{"http", "https"}, + ) + stellarContract = NodeCmd.Flags().String( + "stellarContract", "CBWQUIB4R65Z2DGC263FQ7BBI7TGIGOLFTYMLE6QPWBD5QDOUVJY3AKR", "Wormhole core contract ID on Stellar (e.g., StrKey for Soroban core contract)", + ) + optimismRPC = node.RegisterFlagWithValidationOrFail(NodeCmd, "optimismRPC", "Optimism RPC URL", "ws://eth-devnet:8545", []string{"ws", "wss"}) optimismContract = NodeCmd.Flags().String("optimismContract", "", "Optimism contract address") @@ -998,6 +1009,10 @@ func runNode(cmd *cobra.Command, args []string) { logger.Fatal("Either --gatewayContract, --gatewayWS and --gatewayLCD must all be set or all unset") } + if !argsConsistent([]string{*stellarContract, *stellarRPC}) { + logger.Fatal("Either --stellarContract and --stellarRPC must both be set or both unset") + } + if !*chainGovernorEnabled && *coinGeckoApiKey != "" { logger.Fatal("If coinGeckoApiKey is set, then chainGovernorEnabled must be set") } @@ -1066,6 +1081,7 @@ func runNode(cmd *cobra.Command, args []string) { rpcMap["avalancheRPC"] = *avalancheRPC rpcMap["algorandIndexerRPC"] = *algorandIndexerRPC rpcMap["algorandAlgodRPC"] = *algorandAlgodRPC + rpcMap["stellarRPC"] = *stellarRPC rpcMap["klaytnRPC"] = *klaytnRPC rpcMap["celoRPC"] = *celoRPC rpcMap["nearRPC"] = *nearRPC @@ -1744,6 +1760,17 @@ func runNode(cmd *cobra.Command, args []string) { watcherConfigs = append(watcherConfigs, wc) } + if shouldStart(stellarRPC) { + wc := &stellar.WatcherConfig{ + NetworkID: "61", + ChainID: vaa.ChainIDStellar, + Rpc: *stellarRPC, + Contract: *stellarContract, + StartLedger: 0, + } + watcherConfigs = append(watcherConfigs, wc) + } + if shouldStart(aptosRPC) { wc := &aptos.WatcherConfig{ NetworkID: "aptos", diff --git a/node/go.mod b/node/go.mod index 2e6dee3b10..c77ba34569 100644 --- a/node/go.mod +++ b/node/go.mod @@ -3,17 +3,17 @@ module github.com/certusone/wormhole/node go 1.25.10 require ( - github.com/cenkalti/backoff/v4 v4.2.0 + github.com/cenkalti/backoff/v4 v4.3.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgraph-io/badger/v3 v3.2103.1 github.com/ethereum/go-ethereum v1.10.21 github.com/gagliardetto/solana-go v1.8.4 - github.com/gorilla/mux v1.8.0 + github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/improbable-eng/grpc-web v0.15.0 github.com/ipfs/go-log/v2 v2.5.1 github.com/libp2p/go-libp2p v0.37.0 @@ -23,19 +23,19 @@ require ( github.com/multiformats/go-multiaddr v0.13.0 github.com/near/borsh-go v0.3.0 github.com/prometheus/client_golang v1.20.5 - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.14.0 + github.com/spf13/viper v1.17.0 github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 // indirect github.com/stretchr/testify v1.10.0 github.com/tendermint/tendermint v0.34.24 - github.com/tidwall/gjson v1.15.0 + github.com/tidwall/gjson v1.19.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.46.0 golang.org/x/sys v0.39.0 golang.org/x/time v0.5.0 - google.golang.org/api v0.169.0 - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/api v0.183.0 + google.golang.org/genproto v0.0.0-20240528184218-531527333157 // indirect google.golang.org/grpc v1.75.0 google.golang.org/protobuf v1.36.8 ) @@ -45,7 +45,7 @@ require ( github.com/Peersyst/xrpl-go v0.1.14 github.com/algorand/go-algorand-sdk v1.23.0 github.com/aws/aws-sdk-go v1.55.5 - github.com/aws/aws-sdk-go-v2/config v1.28.1 + github.com/aws/aws-sdk-go-v2/config v1.29.17 github.com/aws/aws-sdk-go-v2/service/kms v1.37.3 github.com/blendle/zapdriver v1.3.1 github.com/block-vision/sui-go-sdk v1.2.1 @@ -61,10 +61,11 @@ require ( github.com/google/uuid v1.6.0 github.com/grafana/dskit v0.0.0-20230201083518-528d8a7d52f2 github.com/grafana/loki v1.6.2-0.20230721141808-0d81144cfee8 - github.com/hashicorp/golang-lru v0.6.0 - github.com/holiman/uint256 v1.2.1 + github.com/hashicorp/golang-lru v1.0.2 + github.com/holiman/uint256 v1.2.3 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.60.0 + github.com/stellar/go v0.0.0-20251210100531-aab2ea4aca88 github.com/wormhole-foundation/wormchain v0.0.0-00010101000000-000000000000 github.com/wormhole-foundation/wormhole/sdk v0.0.0-20220926172624-4b38dc650bb0 go.uber.org/goleak v1.3.0 @@ -75,6 +76,8 @@ require ( require github.com/sercand/kuberesolver/v4 v4.0.0 // indirect require ( + cloud.google.com/go/auth v0.5.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.7.0 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.11 // indirect filippo.io/edwards25519 v1.0.0 // indirect @@ -87,20 +90,20 @@ require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/algorand/go-codec/codec v1.1.8 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect - github.com/armon/go-metrics v0.4.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect - github.com/aws/aws-sdk-go-v2 v1.32.3 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.42 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 // indirect - github.com/aws/smithy-go v1.22.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aws/aws-sdk-go-v2 v1.36.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect + github.com/aws/smithy-go v1.22.4 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect @@ -179,17 +182,17 @@ require ( github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/gorilla/handlers v1.5.1 // indirect + github.com/gorilla/handlers v1.5.2 // indirect github.com/grafana/loki/pkg/push v0.0.0-20230127102416-571f88bc5765 // indirect github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // 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/hashicorp/consul/api v1.18.0 // indirect + github.com/hashicorp/consul/api v1.25.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -202,7 +205,7 @@ require ( github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/iancoleman/orderedmap v0.2.0 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.10.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect @@ -221,7 +224,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/lib/pq v1.10.6 // indirect + github.com/lib/pq v1.10.9 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.2.0 // indirect @@ -235,7 +238,7 @@ require ( github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/magiconair/properties v1.8.6 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -272,8 +275,7 @@ require ( github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pion/datachannel v1.5.9 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect @@ -307,21 +309,24 @@ require ( github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/regen-network/cosmos-proto v0.3.1 // indirect github.com/rjeczalik/notify v0.9.1 // indirect - github.com/rs/cors v1.8.2 // indirect + github.com/rs/cors v1.11.0 // indirect github.com/rs/zerolog v1.27.0 // indirect + github.com/sagikazarmark/locafero v0.3.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.10.0 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 // indirect github.com/strangelove-ventures/packet-forward-middleware/v4 v4.0.4 // indirect github.com/streamingfast/logging v0.0.0-20220813175024-b4fbb0e893df // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/subosito/gotenv v1.4.1 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect @@ -343,9 +348,9 @@ require ( github.com/zondax/hid v0.9.2 // indirect github.com/zondax/ledger-go v0.14.3 // indirect go.etcd.io/bbolt v1.3.6 // indirect - go.etcd.io/etcd/api/v3 v3.5.5 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect - go.etcd.io/etcd/client/v3 v3.5.5 // indirect + go.etcd.io/etcd/api/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect + go.etcd.io/etcd/client/v3 v3.5.9 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/node/go.sum b/node/go.sum index 36f15402a5..b7263f84d2 100644 --- a/node/go.sum +++ b/node/go.sum @@ -200,6 +200,10 @@ cloud.google.com/go/assuredworkloads v1.11.2/go.mod h1:O1dfr+oZJMlE6mw0Bp0P1KZSl cloud.google.com/go/assuredworkloads v1.11.3/go.mod h1:vEjfTKYyRUaIeA0bsGJceFV2JKpVRgyG2op3jfa59Zs= cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk= +cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -1496,8 +1500,8 @@ github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOp github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= -github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -1610,16 +1614,16 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -1646,36 +1650,36 @@ github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk= -github.com/aws/aws-sdk-go-v2 v1.32.3/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo= -github.com/aws/aws-sdk-go-v2/config v1.28.1 h1:oxIvOUXy8x0U3fR//0eq+RdCKimWI900+SV+10xsCBw= -github.com/aws/aws-sdk-go-v2/config v1.28.1/go.mod h1:bRQcttQJiARbd5JZxw6wG0yIK3eLeSCPdg6uqmmlIiI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42 h1:sBP0RPjBU4neGpIYyx8mkU2QqLPl5u9cmdTWVzIpHkM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.42/go.mod h1:FwZBfU530dJ26rv9saAbxa9Ej3eF/AK0OAY86k13n4M= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18 h1:68jFVtt3NulEzojFesM/WVarlFpCaXLKaBxDpzkQ9OQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.18/go.mod h1:Fjnn5jQVIo6VyedMc0/EhPpfNlPl7dHV916O6B+49aE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22 h1:Jw50LwEkVjuVzE1NzkhNKkBf9cRN7MtE1F/b2cOKTUM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.22/go.mod h1:Y/SmAyPcOTmpeVaWSzSKiILfXTVJwrGmYZhcRbhWuEY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22 h1:981MHwBaRZM7+9QSR6XamDzF/o7ouUGxFzr+nVSIhrs= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.22/go.mod h1:1RA1+aBEfn+CAB/Mh0MB6LsdCYCnjZm7tKXtnk499ZQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= +github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3 h1:qcxX0JYlgWH3hpPUnd6U0ikcl6LLA9sLkXE2w1fpMvY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.3/go.mod h1:cLSNEmI45soc+Ef8K/L+8sEA3A3pYFEYf5B5UI+6bH4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= github.com/aws/aws-sdk-go-v2/service/kms v1.37.3 h1:VpyBA6KP6JgzwokQps8ArQPGy9rFej8adwuuQGcduH8= github.com/aws/aws-sdk-go-v2/service/kms v1.37.3/go.mod h1:TT/9V4PcmSPpd8LPUNJ8hBHJmpqcfhx6MrbWTkvyR+4= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3 h1:UTpsIf0loCIWEbrqdLb+0RxnTXfWh2vhw4nQmFi4nPc= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.3/go.mod h1:FZ9j3PFHHAR+w0BSEjK955w5YD2UwB/l/H0yAK3MJvI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3 h1:2YCmIXv3tmiItw0LlYf6v7gEHebLY45kBEnPezbUKyU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.3/go.mod h1:u19stRyNPxGhj6dRm+Cdgu6N75qnbW7+QN0q0dsAk58= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3 h1:wVnQ6tigGsRqSWDEEyH6lSAJ9OyFUsSnbaUWChuSGzs= -github.com/aws/aws-sdk-go-v2/service/sts v1.32.3/go.mod h1:VZa9yTFyj4o10YGsmDO4gbQJUvvhY72fhumT8W4LqsE= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM= -github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -1765,8 +1769,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -2046,8 +2050,9 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= @@ -2734,13 +2739,15 @@ github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoT github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -2797,8 +2804,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= @@ -2815,13 +2822,13 @@ github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoP github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= -github.com/hashicorp/consul/api v1.18.0 h1:R7PPNzTCeN6VuQNDwwhZWJvzCtGSrNpJqfb22h3yH9g= -github.com/hashicorp/consul/api v1.18.0/go.mod h1:owRRGJ9M5xReDC5nfT8FTJrNAPbT4NM6p/k+d03q2v4= +github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/consul/sdk v0.13.0 h1:lce3nFlpv8humJL8rNrrGHYSKc3q+Kxfeg3Ii1m6ZWU= -github.com/hashicorp/consul/sdk v0.13.0/go.mod h1:0hs/l5fOVhJy/VdcoaNqUSi2AUs95eF5WKtv+EYIQqE= +github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= +github.com/hashicorp/consul/sdk v0.14.1/go.mod h1:vFt03juSzocLRFo59NkeQHHmQa6+g7oU0pfzdI1mUhg= github.com/hashicorp/cronexpr v1.1.1 h1:NJZDd87hGXjoZBdvyCF9mX4DCq5Wy7+A/w+A7q0wn6c= github.com/hashicorp/cronexpr v1.1.1/go.mod h1:P4wA0KBl9C5q2hABiMO7cp6jcIg96CDh1Efb3g1PWA4= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -2838,8 +2845,9 @@ github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -2866,7 +2874,6 @@ github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjG 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-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -2879,8 +2886,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4= -github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= +github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -2912,8 +2919,8 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.1.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o= -github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= @@ -2943,8 +2950,8 @@ github.com/improbable-eng/grpc-web v0.14.1/go.mod h1:zEjGHa8DAlkoOXmswrNvhUGEYQA github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= @@ -3127,8 +3134,9 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= @@ -3184,8 +3192,9 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -3572,14 +3581,13 @@ github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bA github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.0/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -3804,8 +3812,9 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= +github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -3831,6 +3840,10 @@ github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiB github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= +github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ= +github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10= @@ -3902,8 +3915,9 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= github.com/sivchari/tenv v1.5.0/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= @@ -3925,6 +3939,8 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/sourcegraph/go-diff v0.5.3/go.mod h1:v9JDtjCE4HHHCZGId75rg8gkKKa98RVjBcBGsVmMmak= github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= @@ -3943,8 +3959,9 @@ github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -3955,10 +3972,9 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6 github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -3975,8 +3991,8 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= +github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI= +github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969 h1:Oo2KZNP70KE0+IUJSidPj/BFS/RXNHmKIJOdckzml2E= @@ -3985,6 +4001,10 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8L github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stellar/go v0.0.0-20251210100531-aab2ea4aca88 h1:T7CDnX+NSQlu9pxLlxZN0qt6SeUoQ6lxwZjY+Y9Ky54= +github.com/stellar/go v0.0.0-20251210100531-aab2ea4aca88/go.mod h1:pcoYvfcsyFzzSut3RBWF9Ts8g4Z7SWbkb8Hitu7k4BU= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2 h1:OzCVd0SV5qE3ZcDeSFCmOWLZfEWZ3Oe8KtmSOYKEVWE= +github.com/stellar/go-xdr v0.0.0-20231122183749-b53fb00bcac2/go.mod h1:yoxyU/M8nl9LKeWIoBrbDPQ7Cy+4jxRcWcOayZ4BMps= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/strangelove-ventures/packet-forward-middleware/v4 v4.0.4 h1:8Tn4Gy/DAq7wzV1CxEGv80ujZ+nUvzgwwdCobO/Gj8Y= @@ -4029,8 +4049,8 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -4081,8 +4101,8 @@ github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw= -github.com/tidwall/gjson v1.15.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU= +github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= @@ -4209,6 +4229,8 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xdrpp/goxdr v0.1.1 h1:E1B2c6E8eYhOVyd7yEpOyopzTPirUeF6mVOfXfGyJyc= +github.com/xdrpp/goxdr v0.1.1/go.mod h1:dXo1scL/l6s7iME1gxHWo2XCppbHEKZS7m/KyYWkNzA= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= @@ -4263,22 +4285,22 @@ go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQc go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0= -go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= +go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= +go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= -go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= +go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v2 v2.305.2/go.mod h1:2D7ZejHVMIfog1221iLSYlQRzrtECw3kz4I4VAQm3qI= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= -go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= +go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= +go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= @@ -5358,8 +5380,9 @@ google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9 google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= -google.golang.org/api v0.169.0 h1:QwWPy71FgMWqJN/l6jVlFHUa29a7dcUy02I8o799nPY= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= +google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -5565,8 +5588,9 @@ google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= +google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -5695,7 +5719,6 @@ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= diff --git a/node/pkg/watchers/stellar/watcher.go b/node/pkg/watchers/stellar/watcher.go new file mode 100644 index 0000000000..9040ef4ae2 --- /dev/null +++ b/node/pkg/watchers/stellar/watcher.go @@ -0,0 +1,658 @@ +package stellar + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/certusone/wormhole/node/pkg/common" + "github.com/certusone/wormhole/node/pkg/p2p" + gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1" + "github.com/certusone/wormhole/node/pkg/query" + "github.com/certusone/wormhole/node/pkg/readiness" + "github.com/certusone/wormhole/node/pkg/supervisor" + "github.com/certusone/wormhole/node/pkg/watchers" + "github.com/certusone/wormhole/node/pkg/watchers/interfaces" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + stellarxdr "github.com/stellar/go/xdr" + "github.com/tidwall/gjson" + "github.com/wormhole-foundation/wormhole/sdk/vaa" + "go.uber.org/zap" +) + +var ( + stellarConnectionErrors = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "wormhole_stellar_connection_errors_total", + Help: "Total number of Stellar connection errors", + }, []string{"network", "reason"}) + + stellarMessagesObserved = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "wormhole_stellar_messages_observed_total", + Help: "Total number of Stellar messages observed (pre-confirmation)", + }, []string{"network"}) + + stellarMessagesConfirmed = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "wormhole_stellar_messages_confirmed_total", + Help: "Total number of Stellar messages confirmed (post-publish)", + }, []string{"network"}) + + currentStellarLedger = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "wormhole_stellar_current_ledger", + Help: "Current Stellar ledger sequence number", + }, []string{"network"}) +) + +type WatcherConfig struct { + NetworkID string + NetworkName string // human-readable name for logging and metrics labels + ChainID vaa.ChainID + Rpc string // Soroban RPC HTTP endpoint + Contract string // Core contract id + PollInterval time.Duration + ReadTimeout time.Duration + StartLedger uint64 + MaxPerPoll int +} + +func (wc *WatcherConfig) Create( + msgC chan<- *common.MessagePublication, + obsvReqC <-chan *gossipv1.ObservationRequest, + ccqReqC <-chan *query.PerChainQueryInternal, + ccqRespC chan<- *query.PerChainQueryResponseInternal, + guardianSetC chan<- *common.GuardianSet, + env common.Environment, +) (supervisor.Runnable, interfaces.Reobserver, error) { + _ = ccqReqC + _ = ccqRespC + _ = guardianSetC + + if wc.PollInterval == 0 { + wc.PollInterval = 700 * time.Millisecond + } + if wc.ReadTimeout == 0 { + wc.ReadTimeout = 10 * time.Second + } + if wc.MaxPerPoll <= 0 { + wc.MaxPerPoll = 128 + } + + networkName := wc.NetworkName + if networkName == "" { + networkName = wc.NetworkID + } + + w := NewWatcher( + wc.Rpc, + wc.Contract, + networkName, + wc.ChainID, + wc.StartLedger, + wc.PollInterval, + wc.ReadTimeout, + wc.MaxPerPoll, + msgC, + obsvReqC, + env, + ) + return w.Run, w, nil +} + +func (wc *WatcherConfig) GetChainID() vaa.ChainID { + return wc.ChainID +} + +func (wc *WatcherConfig) GetNetworkID() watchers.NetworkID { + return watchers.NetworkID(wc.NetworkID) +} + +type watcher struct { + rpc string + contract string + networkName string + chainID vaa.ChainID + nextLedger uint64 + pollInterval time.Duration + httpTimeout time.Duration + maxPerPoll int + msgC chan<- *common.MessagePublication + obsvReqC <-chan *gossipv1.ObservationRequest + env common.Environment + httpClient *http.Client + readinessSync readiness.Component + logger *zap.Logger +} + +func NewWatcher( + rpc string, + contract string, + networkName string, + chainID vaa.ChainID, + startLedger uint64, + pollInterval time.Duration, + readTimeout time.Duration, + maxPerPoll int, + msgC chan<- *common.MessagePublication, + obsvReqC <-chan *gossipv1.ObservationRequest, + env common.Environment, +) *watcher { + return &watcher{ + rpc: rpc, + contract: contract, + networkName: networkName, + chainID: chainID, + nextLedger: startLedger, + pollInterval: pollInterval, + httpTimeout: readTimeout, + maxPerPoll: maxPerPoll, + msgC: msgC, + obsvReqC: obsvReqC, + env: env, + httpClient: &http.Client{Timeout: readTimeout}, + readinessSync: common.MustConvertChainIdToReadinessSyncing(chainID), + } +} + +func (w *watcher) Run(ctx context.Context) error { + logger := supervisor.Logger(ctx).With( + zap.String("component", "stellar_watcher"), + zap.String("rpc", w.rpc), + zap.String("contract", w.contract), + zap.String("chain", w.chainID.String()), + ) + w.logger = logger + + if w.nextLedger == 0 { + seq, err := w.getInitialLedger(ctx, logger) + if err != nil { + return err + } + w.nextLedger = seq + logger.Info("initialized start ledger", zap.Uint64("ledger", w.nextLedger)) + } + + p2p.DefaultRegistry.SetNetworkStats(w.chainID, &gossipv1.Heartbeat_Network{ + ContractAddress: w.contract, + }) + + errC := make(chan error) + common.RunWithScissors(ctx, errC, "stellar_reobservation", func(ctx context.Context) error { + return w.runReobservationHandler(ctx) + }) + + t := time.NewTicker(w.pollInterval) + defer t.Stop() + logger.Info("stellar watcher started") + + for { + select { + case <-ctx.Done(): + logger.Info("stellar watcher stopping") + return nil + case err := <-errC: + return err + case <-t.C: + if _, err := w.pollOnce(ctx, logger); err != nil { + stellarConnectionErrors.WithLabelValues(w.networkName, "poll").Inc() + p2p.DefaultRegistry.AddErrorCount(w.chainID, 1) + logger.Warn("pollOnce error", zap.Error(err)) + } + } + } +} + +// getInitialLedger fetches the starting ledger, retrying with capped exponential +// backoff. A transient RPC outage at startup (e.g. the Soroban RPC not yet +// reachable when the guardian boots) must not kill the watcher, mirroring the +// resilience of the poll loop. Returns an error only when the context is cancelled. +func (w *watcher) getInitialLedger(ctx context.Context, logger *zap.Logger) (uint64, error) { + backoff := w.pollInterval + if backoff <= 0 { + backoff = 700 * time.Millisecond + } + const maxBackoff = 60 * time.Second + + for { + seq, err := w.getLatestLedger(ctx) + if err == nil { + return seq, nil + } + + stellarConnectionErrors.WithLabelValues(w.networkName, "initial_ledger").Inc() + p2p.DefaultRegistry.AddErrorCount(w.chainID, 1) + logger.Warn("failed to get latest ledger, retrying", zap.Error(err), zap.Duration("backoff", backoff)) + + select { + case <-ctx.Done(): + return 0, ctx.Err() + case <-time.After(backoff): + } + + if backoff < maxBackoff { + backoff *= 2 + if backoff > maxBackoff { + backoff = maxBackoff + } + } + } +} + +// runReobservationHandler handles incoming reobservation requests. Returns an error only on fatal failure. +func (w *watcher) runReobservationHandler(ctx context.Context) error { + logger := w.logger + for { + select { + case <-ctx.Done(): + return nil + case req := <-w.obsvReqC: + if vaa.ChainID(req.ChainId) != w.chainID { + logger.Debug("ignoring reobservation request for different chain", + zap.Uint32("requestChainId", req.ChainId), + zap.String("watcherChainId", w.chainID.String()), + ) + continue + } + + txHash := hex.EncodeToString(req.TxHash) + logger.Info("received reobservation request", zap.String("txHash", txHash)) + + count, err := w.handleReobservationRequest(ctx, txHash, w.rpc, w.httpClient) + if err != nil { + logger.Error("failed to handle reobservation request", + zap.String("txHash", txHash), + zap.Error(err), + ) + continue + } + + logger.Info("completed reobservation request", + zap.String("txHash", txHash), + zap.Uint32("messagesFound", count), + ) + } + } +} + +// Reobserve implements the Reobserver interface, allowing reobservation via a custom RPC endpoint. +func (w *watcher) Reobserve(ctx context.Context, chainID vaa.ChainID, txID []byte, customEndpoint string) (uint32, error) { + if chainID != w.chainID { + return 0, fmt.Errorf("unexpected chain id: %v", chainID) + } + txHash := hex.EncodeToString(txID) + w.logger.Info("received request to reobserve using custom endpoint", + zap.Stringer("chainID", chainID), + zap.String("txHash", txHash), + zap.String("endpoint", customEndpoint), + ) + httpClient := &http.Client{Timeout: w.httpTimeout} + return w.handleReobservationRequest(ctx, txHash, customEndpoint, httpClient) +} + +// handleReobservationRequest fetches events for a specific transaction using getTransaction +// to get the accurate ledger timestamp, then queries events for that ledger. +func (w *watcher) handleReobservationRequest(ctx context.Context, txHash, rpcURL string, httpClient *http.Client) (uint32, error) { + logger := w.logger + + // Use getTransaction to get the ledger sequence and timestamp for this transaction. + txRes, err := rpcCall(ctx, "getTransaction", map[string]any{"hash": txHash}, rpcURL, httpClient) + if err != nil { + return 0, fmt.Errorf("getTransaction failed: %w", err) + } + + status := gjson.GetBytes(*txRes, "status").Str + switch status { + case "NOT_FOUND": + return 0, fmt.Errorf("transaction %s not found (may be outside retention window)", txHash) + case "FAILED": + return 0, fmt.Errorf("transaction %s failed on-chain", txHash) + case "SUCCESS": + // continue below + default: + return 0, fmt.Errorf("unexpected transaction status %q for %s", status, txHash) + } + + ledger := gjson.GetBytes(*txRes, "ledger").Uint() + createdAt := gjson.GetBytes(*txRes, "createdAt").Int() + timestamp := time.Unix(createdAt, 0).UTC() + + txIDBytes, err := hex.DecodeString(txHash) + if err != nil { + return 0, fmt.Errorf("failed to decode txHash: %w", err) + } + + // Query events starting from the transaction's ledger. + params := map[string]any{ + "startLedger": ledger, + "filters": []map[string]any{ + {"type": "contract", "contractIds": []string{w.contract}}, + }, + "pagination": map[string]any{"limit": w.maxPerPoll}, + } + res, err := rpcCall(ctx, "getEvents", params, rpcURL, httpClient) + if err != nil { + return 0, fmt.Errorf("getEvents failed: %w", err) + } + + events := gjson.GetBytes(*res, "events") + if !events.Exists() { + return 0, nil + } + + var messagesFound uint32 + for _, e := range events.Array() { + if e.Get("txHash").Str != txHash { + continue + } + + mp := w.parseEventJSON(e, logger) + if mp == nil { + continue + } + + mp.TxID = txIDBytes + mp.Timestamp = timestamp + mp.EmitterChain = w.chainID + mp.IsReobservation = true + + logger.Info("reobserved stellar message", + zap.Uint64("ledger", ledger), + zap.String("tx", txHash), + zap.Uint64("seq", mp.Sequence), + zap.Uint8("consistency", mp.ConsistencyLevel), + ) + + select { + case w.msgC <- mp: + messagesFound++ + case <-ctx.Done(): + return messagesFound, ctx.Err() + } + } + + return messagesFound, nil +} + +type rpcRequest struct { + JSONRPC string `json:"jsonrpc,omitempty"` + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` + ID int `json:"id,omitempty"` +} + +type rpcResponse struct { + JSONRPC string `json:"jsonrpc,omitempty"` + Result *json.RawMessage `json:"result,omitempty"` + Error *struct { + Code int `json:"code"` + Message string `json:"message"` + } `json:"error,omitempty"` + ID int `json:"id,omitempty"` +} + +// rpcCall executes a JSON-RPC request against the given URL using the provided HTTP client. +func rpcCall(ctx context.Context, method string, params any, rpcURL string, httpClient *http.Client) (*json.RawMessage, error) { + req := rpcRequest{ + JSONRPC: "2.0", + Method: method, + Params: params, + ID: 1, + } + + body, _ := json.Marshal(&req) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, rpcURL, bytes.NewReader(body)) + if err != nil { + return nil, err + } + httpReq.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(httpReq) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var rr rpcResponse + if err := json.Unmarshal(b, &rr); err != nil { + return nil, fmt.Errorf("jsonrpc decode: %w", err) + } + if rr.Error != nil { + return nil, fmt.Errorf("jsonrpc error %d: %s", rr.Error.Code, rr.Error.Message) + } + return rr.Result, nil +} + +func (w *watcher) call(ctx context.Context, method string, params any) (*json.RawMessage, error) { + return rpcCall(ctx, method, params, w.rpc, w.httpClient) +} + +func (w *watcher) getLatestLedger(ctx context.Context) (uint64, error) { + res, err := w.call(ctx, "getLatestLedger", nil) + if err != nil { + return 0, err + } + seq := gjson.GetBytes(*res, "sequence").Uint() + return seq, nil +} + +func (w *watcher) pollOnce(ctx context.Context, logger *zap.Logger) (bool, error) { + advanced := false + cursor := "" + + for { + params := map[string]any{ + "filters": []map[string]any{ + { + "type": "contract", + "contractIds": []string{w.contract}, + }, + }, + "pagination": map[string]any{ + "limit": w.maxPerPoll, + }, + } + if cursor == "" { + params["startLedger"] = w.nextLedger + } else { + params["pagination"].(map[string]any)["cursor"] = cursor + } + + res, err := w.call(ctx, "getEvents", params) + if err != nil { + return false, err + } + + // getEvents returns latestLedger in the response; use it to avoid an extra RPC call. + latestLedger := gjson.GetBytes(*res, "latestLedger").Uint() + if latestLedger > 0 { + currentStellarLedger.WithLabelValues(w.networkName).Set(float64(latestLedger)) + p2p.DefaultRegistry.SetNetworkStats(w.chainID, &gossipv1.Heartbeat_Network{ + Height: int64(latestLedger), // #nosec G115 -- ledger numbers are well within int64 range for the foreseeable future + ContractAddress: w.contract, + }) + } + readiness.SetReady(w.readinessSync) + + events := gjson.GetBytes(*res, "events").Array() + + for _, e := range events { + cursor = e.Get("id").Str + + ledger := e.Get("ledger").Uint() + txHash := e.Get("txHash").Str + + mp := w.parseEventJSON(e, logger) + if mp == nil { + continue + } + + txIDBytes, err := hex.DecodeString(txHash) + if err != nil { + logger.Warn("failed to decode txHash", zap.String("txHash", txHash), zap.Error(err)) + continue + } + mp.TxID = txIDBytes + mp.EmitterChain = w.chainID + + // Use ledgerClosedAt from the event for a deterministic timestamp. + // All guardians observing the same event will use the same timestamp, + // ensuring identical VAAs are produced. + closedAt := e.Get("ledgerClosedAt").Str + ts, err := time.Parse(time.RFC3339, closedAt) + if err != nil { + logger.Warn("failed to parse ledgerClosedAt, skipping event", + zap.String("ledgerClosedAt", closedAt), + zap.Error(err), + ) + continue + } + mp.Timestamp = ts + + stellarMessagesObserved.WithLabelValues(w.networkName).Inc() + + logger.Info("stellar message published", + zap.Uint64("ledger", ledger), + zap.String("tx", txHash), + zap.Uint64("seq", mp.Sequence), + zap.Uint8("consistency", mp.ConsistencyLevel), + ) + + select { + case w.msgC <- mp: + stellarMessagesConfirmed.WithLabelValues(w.networkName).Inc() + case <-ctx.Done(): + return advanced, ctx.Err() + } + + if ledger >= w.nextLedger { + w.nextLedger = ledger + 1 + advanced = true + } + } + + if len(events) < w.maxPerPoll { + // Received fewer events than the limit — no more pages. + if !advanced && latestLedger > w.nextLedger { + w.nextLedger = latestLedger + advanced = true + } + break + } + // Received exactly maxPerPoll events — there may be more pages; continue with cursor. + } + + return advanced, nil +} + +// parseEventJSON checks the event topics for a message_published event and parses the event data. +// Returns nil if the event is not a message_published event or cannot be parsed. +func (w *watcher) parseEventJSON(e gjson.Result, logger *zap.Logger) *common.MessagePublication { + topics := e.Get("topic").Array() + if len(topics) < 2 { + return nil + } + + eventNameBytes, err := base64.StdEncoding.DecodeString(topics[1].Str) + if err != nil { + return nil + } + if !bytes.Contains(eventNameBytes, []byte("message_published")) { + return nil + } + + valueBytes, err := base64.StdEncoding.DecodeString(e.Get("value").Str) + if err != nil { + logger.Debug("failed to decode event value", zap.Error(err)) + return nil + } + + return parseMessageFromXDR(valueBytes, logger) +} + +// parseMessageFromXDR parses the XDR-encoded Soroban event value into a MessagePublication. +func parseMessageFromXDR(data []byte, logger *zap.Logger) *common.MessagePublication { + var scVal stellarxdr.ScVal + + _, err := stellarxdr.Unmarshal(bytes.NewReader(data), &scVal) + if err != nil { + logger.Debug("failed to unmarshal XDR", zap.Error(err)) + return nil + } + + eventMap, ok := scVal.GetMap() + if !ok { + logger.Debug("event value is not a map") + return nil + } + + var nonce uint32 + var sequence uint64 + var emitterAddress []byte + var payload []byte + var consistencyLevel uint32 + + for _, entry := range *eventMap { + keySymbol, ok := entry.Key.GetSym() + if !ok { + continue + } + + switch string(keySymbol) { + case "nonce": + if val, ok := entry.Val.GetU32(); ok { + nonce = uint32(val) + } + case "sequence": + if val, ok := entry.Val.GetU64(); ok { + sequence = uint64(val) + } + case "emitter_address": + if val, ok := entry.Val.GetBytes(); ok { + emitterAddress = val + } + case "payload": + if val, ok := entry.Val.GetBytes(); ok { + payload = val + } + case "consistency_level": + if val, ok := entry.Val.GetU32(); ok { + consistencyLevel = uint32(val) + } + } + } + + if len(emitterAddress) == 0 { + logger.Warn("message_published event has empty emitter address, skipping") + return nil + } + + var emitter vaa.Address + if len(emitterAddress) >= 32 { + copy(emitter[:], emitterAddress[:32]) + } else { + copy(emitter[:], emitterAddress) + } + + return &common.MessagePublication{ + Nonce: nonce, + Sequence: sequence, + ConsistencyLevel: uint8(consistencyLevel), + EmitterAddress: emitter, + Payload: payload, + // TxID, Timestamp, and EmitterChain are set by the caller. + } +} diff --git a/node/pkg/watchers/stellar/watcher_test.go b/node/pkg/watchers/stellar/watcher_test.go new file mode 100644 index 0000000000..e4ba72224c --- /dev/null +++ b/node/pkg/watchers/stellar/watcher_test.go @@ -0,0 +1,606 @@ +package stellar + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/certusone/wormhole/node/pkg/common" + gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1" + stellarxdr "github.com/stellar/go/xdr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/wormhole-foundation/wormhole/sdk/vaa" + "go.uber.org/zap" +) + +// --------------------------------------------------------------------------- +// XDR construction helpers +// --------------------------------------------------------------------------- + +func scSym(s string) stellarxdr.ScVal { + sym := stellarxdr.ScSymbol(s) + return stellarxdr.ScVal{Type: stellarxdr.ScValTypeScvSymbol, Sym: &sym} +} + +func scU32(v uint32) stellarxdr.ScVal { + u := stellarxdr.Uint32(v) + return stellarxdr.ScVal{Type: stellarxdr.ScValTypeScvU32, U32: &u} +} + +func scU64(v uint64) stellarxdr.ScVal { + u := stellarxdr.Uint64(v) + return stellarxdr.ScVal{Type: stellarxdr.ScValTypeScvU64, U64: &u} +} + +func scBytesVal(b []byte) stellarxdr.ScVal { + sb := stellarxdr.ScBytes(b) + return stellarxdr.ScVal{Type: stellarxdr.ScValTypeScvBytes, Bytes: &sb} +} + +// makeEventValueB64 encodes a Soroban message_published event value as base64 XDR. +// This mirrors the on-chain format that parseMessageFromXDR consumes. +func makeEventValueB64(t *testing.T, nonce uint32, seq uint64, emitter []byte, payload []byte, cl uint32) string { + t.Helper() + scm := stellarxdr.ScMap{ + {Key: scSym("nonce"), Val: scU32(nonce)}, + {Key: scSym("sequence"), Val: scU64(seq)}, + {Key: scSym("emitter_address"), Val: scBytesVal(emitter)}, + {Key: scSym("payload"), Val: scBytesVal(payload)}, + {Key: scSym("consistency_level"), Val: scU32(cl)}, + } + m := &scm + scVal := stellarxdr.ScVal{Type: stellarxdr.ScValTypeScvMap, Map: &m} + b64, err := stellarxdr.MarshalBase64(scVal) + require.NoError(t, err) + return b64 +} + +// makeTopicB64 encodes a Soroban symbol ScVal as base64 XDR. +func makeTopicB64(t *testing.T, sym string) string { + t.Helper() + b64, err := stellarxdr.MarshalBase64(scSym(sym)) + require.NoError(t, err) + return b64 +} + +// --------------------------------------------------------------------------- +// Mock Soroban RPC server +// --------------------------------------------------------------------------- + +type mockTx struct { + status string // "SUCCESS", "FAILED", "NOT_FOUND" + ledger uint64 + createdAt int64 +} + +type mockEvent struct { + id string + ledger uint64 + ledgerClosedAt string // RFC3339 + contractID string + txHash string + topic0B64 string + topic1B64 string + valueB64 string +} + +type mockSorobanRPC struct { + mu sync.Mutex + latestLedger uint64 + events []mockEvent + transactions map[string]mockTx + methodCounts map[string]int +} + +func newMockRPC(latestLedger uint64) *mockSorobanRPC { + return &mockSorobanRPC{ + latestLedger: latestLedger, + transactions: make(map[string]mockTx), + methodCounts: make(map[string]int), + } +} + +func (m *mockSorobanRPC) ServeHTTP(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + + var req struct { + Method string `json:"method"` + Params json.RawMessage `json:"params"` + } + if err := json.Unmarshal(body, &req); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + m.mu.Lock() + m.methodCounts[req.Method]++ + m.mu.Unlock() + + var result any + switch req.Method { + case "getLatestLedger": + m.mu.Lock() + result = map[string]any{"sequence": m.latestLedger, "protocolVersion": 21} + m.mu.Unlock() + case "getEvents": + result = m.handleGetEvents(req.Params) + case "getTransaction": + result = m.handleGetTransaction(req.Params) + default: + http.Error(w, fmt.Sprintf("unknown method: %s", req.Method), http.StatusBadRequest) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "id": 1, "result": result}) +} + +func (m *mockSorobanRPC) handleGetEvents(params json.RawMessage) map[string]any { + var p struct { + StartLedger uint64 `json:"startLedger"` + Pagination struct { + Limit int `json:"limit"` + Cursor string `json:"cursor"` + } `json:"pagination"` + } + json.Unmarshal(params, &p) //nolint:errcheck + + limit := p.Pagination.Limit + if limit <= 0 { + limit = 128 + } + + m.mu.Lock() + allEvents := m.events + latestLedger := m.latestLedger + m.mu.Unlock() + + var filtered []mockEvent + if p.Pagination.Cursor != "" { + afterCursor := false + for _, e := range allEvents { + if afterCursor { + filtered = append(filtered, e) + } + if e.id == p.Pagination.Cursor { + afterCursor = true + } + } + } else { + for _, e := range allEvents { + if e.ledger >= p.StartLedger { + filtered = append(filtered, e) + } + } + } + + if len(filtered) > limit { + filtered = filtered[:limit] + } + + eventJSONs := make([]map[string]any, 0, len(filtered)) + for _, e := range filtered { + eventJSONs = append(eventJSONs, map[string]any{ + "id": e.id, + "ledger": e.ledger, + "ledgerClosedAt": e.ledgerClosedAt, + "contractId": e.contractID, + "txHash": e.txHash, + "topic": []string{e.topic0B64, e.topic1B64}, + "value": e.valueB64, + }) + } + + return map[string]any{ + "events": eventJSONs, + "latestLedger": latestLedger, + } +} + +func (m *mockSorobanRPC) handleGetTransaction(params json.RawMessage) map[string]any { + var p struct { + Hash string `json:"hash"` + } + json.Unmarshal(params, &p) //nolint:errcheck + + m.mu.Lock() + tx, ok := m.transactions[p.Hash] + latestLedger := m.latestLedger + m.mu.Unlock() + + if !ok || tx.status == "NOT_FOUND" { + return map[string]any{"status": "NOT_FOUND", "latestLedger": latestLedger} + } + + return map[string]any{ + "status": tx.status, + "ledger": tx.ledger, + "createdAt": tx.createdAt, + "latestLedger": latestLedger, + } +} + +func (m *mockSorobanRPC) methodCount(method string) int { + m.mu.Lock() + defer m.mu.Unlock() + return m.methodCounts[method] +} + +// --------------------------------------------------------------------------- +// Test watcher factory +// --------------------------------------------------------------------------- + +const ( + testContract = "CBWQUIB4R65Z2DGC263FQ7BBI7TGIGOLFTYMLE6QPWBD5QDOUVJY3AKR" + testNetworkID = "stellar-test" + testStartLedger = uint64(100) +) + +func newTestWatcher(rpcURL string, maxPerPoll int, msgC chan<- *common.MessagePublication, obsvReqC <-chan *gossipv1.ObservationRequest) *watcher { + w := NewWatcher( + rpcURL, + testContract, + testNetworkID, + vaa.ChainIDStellar, + testStartLedger, + 700*time.Millisecond, + 10*time.Second, + maxPerPoll, + msgC, + obsvReqC, + common.MainNet, + ) + w.logger = zap.NewNop() + return w +} + +func mustDecodeHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +// --------------------------------------------------------------------------- +// pollOnce tests +// --------------------------------------------------------------------------- + +func TestPollOnce_MessagePublished(t *testing.T) { + emitter := make([]byte, 32) + emitter[31] = 0xAB + payload := []byte("hello wormhole") + txHash := "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899" + closedAt := "2024-06-15T12:00:00Z" + expectedTS, _ := time.Parse(time.RFC3339, closedAt) + + mock := newMockRPC(200) + mock.events = []mockEvent{ + { + id: "0000000100-0000000001", + ledger: 100, + ledgerClosedAt: closedAt, + contractID: testContract, + txHash: txHash, + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, 7, 42, emitter, payload, 0), + }, + } + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + _, err := w.pollOnce(context.Background(), zap.NewNop()) + require.NoError(t, err) + + select { + case mp := <-msgC: + assert.Equal(t, uint32(7), mp.Nonce) + assert.Equal(t, uint64(42), mp.Sequence) + assert.Equal(t, uint8(0), mp.ConsistencyLevel) + assert.Equal(t, vaa.ChainIDStellar, mp.EmitterChain) + assert.Equal(t, payload, mp.Payload) + assert.False(t, mp.IsReobservation) + + assert.Equal(t, expectedTS.UTC(), mp.Timestamp.UTC(), + "timestamp must come from ledgerClosedAt, not time.Now()") + + assert.Equal(t, mustDecodeHex(txHash), mp.TxID, + "TxID must be the hex-decoded transaction hash") + + var expectedEmitter vaa.Address + copy(expectedEmitter[:], emitter) + assert.Equal(t, expectedEmitter, mp.EmitterAddress) + + case <-time.After(time.Second): + t.Fatal("timed out waiting for message") + } + + assert.Equal(t, uint64(101), w.nextLedger, "nextLedger must advance past the processed ledger") +} + +func TestPollOnce_Pagination(t *testing.T) { + // With maxPerPoll=2 and 3 events, the watcher must make two getEvents calls + // (using cursor-based pagination) and deliver all 3 messages. + emitter := make([]byte, 32) + emitter[0] = 0x01 + + closedAt := "2024-01-01T00:00:00Z" + makeEv := func(idx int, ledger uint64) mockEvent { + return mockEvent{ + id: fmt.Sprintf("0000000%03d-0000000001", ledger), + ledger: ledger, + ledgerClosedAt: closedAt, + contractID: testContract, + txHash: fmt.Sprintf("%064x", idx), + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, uint32(idx), uint64(idx), emitter, []byte("pay"), 0), + } + } + + mock := newMockRPC(300) + mock.events = []mockEvent{ + makeEv(1, 100), + makeEv(2, 101), + makeEv(3, 102), + } + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 2, msgC, nil) // maxPerPoll = 2 + + _, err := w.pollOnce(context.Background(), zap.NewNop()) + require.NoError(t, err) + + var received []*common.MessagePublication + for i := 0; i < 3; i++ { + select { + case mp := <-msgC: + received = append(received, mp) + case <-time.After(time.Second): + t.Fatalf("timed out waiting for message %d of 3", i+1) + } + } + assert.Len(t, received, 3, "all 3 events must be delivered across paginated responses") + + // Verify no extra messages leaked. + select { + case <-msgC: + t.Fatal("unexpected extra message on channel") + case <-time.After(20 * time.Millisecond): + } + + // Two getEvents calls: first page (2 events), second page (1 event via cursor). + assert.Equal(t, 2, mock.methodCount("getEvents"), "expected 2 getEvents calls for pagination") +} + +func TestPollOnce_SkipsEmptyEmitter(t *testing.T) { + // An event with an empty emitter_address must be silently dropped. + emptyEmitter := []byte{} + + mock := newMockRPC(200) + mock.events = []mockEvent{ + { + id: "0000000100-0000000001", + ledger: 100, + ledgerClosedAt: "2024-01-01T00:00:00Z", + contractID: testContract, + txHash: fmt.Sprintf("%064x", 1), + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, 1, 1, emptyEmitter, []byte("pay"), 0), + }, + } + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + _, err := w.pollOnce(context.Background(), zap.NewNop()) + require.NoError(t, err) + + select { + case mp := <-msgC: + t.Fatalf("expected no message but got seq=%d", mp.Sequence) + case <-time.After(50 * time.Millisecond): + // correct: nothing published + } +} + +func TestPollOnce_NoEventsAdvancesNextLedger(t *testing.T) { + // When getEvents returns no events, nextLedger must advance to latestLedger + // from the getEvents response. No separate getLatestLedger call should be made. + mock := newMockRPC(500) + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + advanced, err := w.pollOnce(context.Background(), zap.NewNop()) + require.NoError(t, err) + assert.True(t, advanced) + assert.Equal(t, uint64(500), w.nextLedger) + + assert.Equal(t, 0, mock.methodCount("getLatestLedger"), + "getLatestLedger must not be called separately; latestLedger comes from the getEvents response") +} + +// --------------------------------------------------------------------------- +// handleReobservationRequest tests +// --------------------------------------------------------------------------- + +func TestHandleReobservation_Success(t *testing.T) { + emitter := make([]byte, 32) + emitter[0] = 0xFF + payload := []byte("reobs payload") + txHash := "ccddccddccddccddccddccddccddccddccddccddccddccddccddccddccddccdd" + createdAt := int64(1718445600) // 2024-06-15T14:00:00Z + expectedTS := time.Unix(createdAt, 0).UTC() + + mock := newMockRPC(300) + mock.transactions[txHash] = mockTx{status: "SUCCESS", ledger: 200, createdAt: createdAt} + mock.events = []mockEvent{ + { + id: "0000000200-0000000001", + ledger: 200, + ledgerClosedAt: "2024-06-15T14:00:00Z", + contractID: testContract, + txHash: txHash, + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, 99, 77, emitter, payload, 1), + }, + } + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + count, err := w.handleReobservationRequest(context.Background(), txHash, srv.URL, w.httpClient) + require.NoError(t, err) + assert.Equal(t, uint32(1), count) + + select { + case mp := <-msgC: + assert.True(t, mp.IsReobservation) + assert.Equal(t, uint64(77), mp.Sequence) + assert.Equal(t, uint32(99), mp.Nonce) + assert.Equal(t, uint8(1), mp.ConsistencyLevel) + assert.Equal(t, payload, mp.Payload) + assert.Equal(t, vaa.ChainIDStellar, mp.EmitterChain) + assert.Equal(t, mustDecodeHex(txHash), mp.TxID) + assert.Equal(t, expectedTS, mp.Timestamp, + "timestamp must come from createdAt returned by getTransaction, not time.Now()") + case <-time.After(time.Second): + t.Fatal("timed out waiting for reobserved message") + } +} + +func TestHandleReobservation_NotFound(t *testing.T) { + txHash := "1234123412341234123412341234123412341234123412341234123412341234" + + mock := newMockRPC(300) + // No entry in mock.transactions → will return NOT_FOUND. + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + count, err := w.handleReobservationRequest(context.Background(), txHash, srv.URL, w.httpClient) + assert.Error(t, err, "NOT_FOUND must return an error") + assert.Contains(t, err.Error(), "not found") + assert.Equal(t, uint32(0), count) +} + +func TestHandleReobservation_NoMatchingEvents(t *testing.T) { + // getTransaction succeeds but the events at that ledger belong to a different txHash. + ourTxHash := "aaaa000000000000000000000000000000000000000000000000000000000000" + otherTxHash := "bbbb000000000000000000000000000000000000000000000000000000000000" + createdAt := int64(1718445600) + + emitter := make([]byte, 32) + emitter[0] = 0x01 + + mock := newMockRPC(300) + mock.transactions[ourTxHash] = mockTx{status: "SUCCESS", ledger: 200, createdAt: createdAt} + mock.events = []mockEvent{ + { + id: "0000000200-0000000001", + ledger: 200, + ledgerClosedAt: "2024-06-15T14:00:00Z", + contractID: testContract, + txHash: otherTxHash, // different tx! + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, 1, 1, emitter, []byte("pay"), 0), + }, + } + + srv := httptest.NewServer(mock) + defer srv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(srv.URL, 128, msgC, nil) + + count, err := w.handleReobservationRequest(context.Background(), ourTxHash, srv.URL, w.httpClient) + require.NoError(t, err, "unmatched txHash is not an error, just zero results") + assert.Equal(t, uint32(0), count) +} + +// --------------------------------------------------------------------------- +// Reobserver interface test +// --------------------------------------------------------------------------- + +func TestReobserve_UsesCustomEndpoint(t *testing.T) { + // Verifies that Reobserve() sends requests to the custom endpoint URL, + // not to the watcher's primary RPC URL. + emitter := make([]byte, 32) + emitter[0] = 0xDE + txHash := "dead000000000000000000000000000000000000000000000000000000000000" + createdAt := int64(1700000000) + + primaryMock := newMockRPC(100) // primary server: no transactions + customMock := newMockRPC(300) // custom server: has the transaction + customMock.transactions[txHash] = mockTx{status: "SUCCESS", ledger: 200, createdAt: createdAt} + customMock.events = []mockEvent{ + { + id: "0000000200-0000000001", + ledger: 200, + ledgerClosedAt: "2023-11-14T22:13:20Z", + contractID: testContract, + txHash: txHash, + topic0B64: makeTopicB64(t, "wormhole"), + topic1B64: makeTopicB64(t, "message_published"), + valueB64: makeEventValueB64(t, 5, 10, emitter, []byte("custom"), 0), + }, + } + + primarySrv := httptest.NewServer(primaryMock) + defer primarySrv.Close() + customSrv := httptest.NewServer(customMock) + defer customSrv.Close() + + msgC := make(chan *common.MessagePublication, 10) + w := newTestWatcher(primarySrv.URL, 128, msgC, nil) + + txIDBytes := mustDecodeHex(txHash) + count, err := w.Reobserve(context.Background(), vaa.ChainIDStellar, txIDBytes, customSrv.URL) + require.NoError(t, err) + assert.Equal(t, uint32(1), count) + + assert.Equal(t, 0, primaryMock.methodCount("getTransaction"), + "primary RPC must not be called; Reobserve must use the custom endpoint") + assert.Equal(t, 1, customMock.methodCount("getTransaction"), + "custom endpoint must receive the getTransaction call") + + select { + case mp := <-msgC: + assert.True(t, mp.IsReobservation) + assert.Equal(t, uint64(10), mp.Sequence) + case <-time.After(time.Second): + t.Fatal("timed out waiting for reobserved message from custom endpoint") + } +}