From 9218afa847dda8900f0945d5c596dc94c767e503 Mon Sep 17 00:00:00 2001 From: Jordan Krage Date: Wed, 4 Oct 2023 05:46:37 -0500 Subject: [PATCH] pkg/solana: copy chains/solana packages from core --- go.mod | 25 +- go.sum | 137 ++++++++- integration-tests/go.mod | 8 +- integration-tests/go.sum | 12 +- pkg/solana/chain.go | 450 +++++++++++++++++++++++++++++ pkg/solana/chain_test.go | 231 +++++++++++++++ pkg/solana/config.go | 270 +++++++++++++++++ pkg/solana/monitor/balance.go | 150 ++++++++++ pkg/solana/monitor/balance_test.go | 94 ++++++ pkg/solana/monitor/prom.go | 19 ++ pkg/solana/monitor/prom_test.go | 21 ++ 11 files changed, 1401 insertions(+), 16 deletions(-) create mode 100644 pkg/solana/chain_test.go create mode 100644 pkg/solana/config.go create mode 100644 pkg/solana/monitor/balance.go create mode 100644 pkg/solana/monitor/balance_test.go create mode 100644 pkg/solana/monitor/prom.go create mode 100644 pkg/solana/monitor/prom_test.go diff --git a/go.mod b/go.mod index c9d5e7bde..1526a12f6 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,11 @@ require ( github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 github.com/gagliardetto/treeout v0.1.4 github.com/google/uuid v1.3.0 + github.com/pelletier/go-toml/v2 v2.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.15.0 - github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a - github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829 + github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7 + github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6 github.com/stretchr/testify v1.8.4 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.24.0 @@ -33,9 +34,17 @@ require ( github.com/confluentinc/confluent-kafka-go v1.9.2 // indirect github.com/dfuse-io/logging v0.0.0-20210109005628-b97a57253f70 // indirect github.com/fatih/color v1.13.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 // indirect + github.com/hashicorp/go-hclog v0.14.1 // indirect + github.com/hashicorp/go-plugin v1.5.2 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/linkedin/goavro/v2 v2.12.0 // indirect @@ -49,6 +58,8 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 // indirect github.com/mr-tron/base58 v1.2.0 // indirect + github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 // indirect + github.com/oklog/run v1.0.0 // indirect github.com/onsi/gomega v1.24.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.3.0 // indirect @@ -56,17 +67,27 @@ require ( github.com/prometheus/procfs v0.9.0 // indirect github.com/riferrei/srclient v0.5.4 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/sdk v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect golang.org/x/crypto v0.9.0 // indirect + golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect + google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 088a73b5d..9c62a3ef9 100644 --- a/go.sum +++ b/go.sum @@ -9,19 +9,32 @@ cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= @@ -61,6 +74,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blendle/zapdriver v1.3.1 h1:C3dydBOWYRiOk+B8X9IVZ5IOe+7cl+tGOexN4QqHfpE= github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -127,6 +141,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -141,11 +160,14 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -156,6 +178,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -171,6 +194,7 @@ github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzr github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -179,11 +203,14 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20211008130755-947d60d73cc0/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -195,6 +222,10 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0 h1:mdLirNAJBxnGgyB6pjZLcs6ue/6eZGBui6gXspfq4ks= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0-rc.0/go.mod h1:kdXbOySqcQeTxiqglW7aahTmWZy3Pgi6SYL36yvKeyA= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 h1:o95KDiV/b1xdkumY5YbLR0/n2+wBxUpgf3HgfKgTyLI= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3/go.mod h1:hTxjzRcX49ogbTGVJ1sM5mz5s+SSgiGIyL3jjPxl32E= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -203,9 +234,13 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= +github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= @@ -220,6 +255,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/heetch/avro v0.3.1/go.mod h1:4xn38Oz/+hiEUTpbVfGVLfvOg0yKLlRP7Q9+gJJILgA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= @@ -233,8 +270,11 @@ github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+ github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -281,6 +321,7 @@ github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxec github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -312,9 +353,13 @@ github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1/go.mod h github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9 h1:62uLwA3l2JMH84liO4ZhnjTH5PjFyCYxbHLgXPaJMtI= +github.com/mwitkow/grpc-proxy v0.0.0-20230212185441-f345521cb9c9/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nrwiersma/avro-benchmarks v0.0.0-20210913175520-21aec48c8f76/go.mod h1:iKyFMidsk/sVYONJRE372sJuX/QTRPacU7imPqqsu7g= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -324,6 +369,8 @@ github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -370,10 +417,10 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a h1:8lHQYdub7q3LgD5ebQOA+usDyUqqiHvKEJPGQ8+FxMA= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a/go.mod h1:Nt4mQh6z6q26jrM4FyQLq/2vBp1Sg3l5md3tBRdRLxg= -github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829 h1:fzefK1SzoRSHzZduOCzIJ2kmkBMPKwIf3FgeBlw7Jjk= -github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= +github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7 h1:6sGDExhcI/FDDnoCerVeCMBBL5sGHywzDkPagcK36F0= +github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7/go.mod h1:agmAM21+teJkw0aj9KYj3q3s1sFkx3PXo5ibWJIrDfU= +github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6 h1:eSo9r53fARv2MnIO5pqYvQOXMBsTlAwhHyQ6BAVp6bY= +github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -422,6 +469,8 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -432,6 +481,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0 h1:ZOLJc06r4CB42laIXg/7udr0pbZyuAihN10A/XuiQRY= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -488,6 +547,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -495,6 +555,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -510,6 +571,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -517,22 +579,34 @@ golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -540,6 +614,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= @@ -563,6 +638,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -573,12 +649,21 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -599,6 +684,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -638,9 +724,20 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -655,14 +752,21 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -681,10 +785,24 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20220503193339-ba3ae3f07e29/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -696,11 +814,19 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= +google.golang.org/grpc/examples v0.0.0-20210424002626-9572fd6faeae/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -709,6 +835,7 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -752,6 +879,8 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 4996b7357..17a14a225 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -17,7 +17,7 @@ require ( github.com/smartcontractkit/chainlink-testing-framework v1.15.1 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20230809180636-8e89b62488d7 github.com/smartcontractkit/chainlink/v2 v2.2.1-0.20230809180636-8e89b62488d7 - github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829 + github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.24.0 golang.org/x/crypto v0.11.0 @@ -148,7 +148,7 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.10 // indirect + github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect @@ -264,7 +264,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -289,7 +289,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/slack-go/slack v0.12.2 // indirect github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704 // indirect - github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a // indirect + github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7 // indirect github.com/smartcontractkit/ocr2keepers v0.7.6 // indirect github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 // indirect github.com/smartcontractkit/sqlx v1.3.5-0.20210805004948-4be295aacbeb // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index b1070ade7..c82471523 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1247,8 +1247,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 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.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= -github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +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/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= @@ -1348,8 +1348,8 @@ github.com/smartcontractkit/caigo v0.0.0-20230621050857-b29a4ca8c704/go.mod h1:2 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20230802143343-59cdfce9fb24 h1:hK/BPzpROry7dR6pXcVWlgNlb6rmwctyw6gNlaeHkfk= github.com/smartcontractkit/chainlink-env v0.36.1-0.20230802063028-a432269a7384 h1:AGEK80chNqkA9VouRtxkHZPG/WDrNGfpR/P7NyhRyYA= github.com/smartcontractkit/chainlink-env v0.36.1-0.20230802063028-a432269a7384/go.mod h1:NbRExHmJGnKSYXmvNuJx5VErSx26GtE1AEN/CRzYOg8= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a h1:8lHQYdub7q3LgD5ebQOA+usDyUqqiHvKEJPGQ8+FxMA= -github.com/smartcontractkit/chainlink-relay v0.1.7-0.20230808141159-4e20b0757f3a/go.mod h1:Nt4mQh6z6q26jrM4FyQLq/2vBp1Sg3l5md3tBRdRLxg= +github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7 h1:6sGDExhcI/FDDnoCerVeCMBBL5sGHywzDkPagcK36F0= +github.com/smartcontractkit/chainlink-relay v0.1.7-0.20231004103629-bee8a37260d7/go.mod h1:agmAM21+teJkw0aj9KYj3q3s1sFkx3PXo5ibWJIrDfU= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20230802150127-d2c95679d61a h1:b3rjvZLpTV45TmCV+ALX+EDDslf91pnDUugP54Lu9FA= github.com/smartcontractkit/chainlink-testing-framework v1.15.1 h1:/i+JuVgStYoiKaLXisgRd96gyMd8NVwwYe/G+Tzy3+8= github.com/smartcontractkit/chainlink-testing-framework v1.15.1/go.mod h1:WxEBntXbe20FgTZllV5fo4fKLd0TM8NfYdWkX5kt6Mg= @@ -1357,8 +1357,8 @@ github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20230809180636-8e github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20230809180636-8e89b62488d7/go.mod h1:eHjvFLINoHJobJybpcSpgXwUih+lfGyxoSa+cNoUS6s= github.com/smartcontractkit/chainlink/v2 v2.2.1-0.20230809180636-8e89b62488d7 h1:XoQli5L+WFVhZjw0Tv9+M/35wzjzjZvngTO8NG/AW6s= github.com/smartcontractkit/chainlink/v2 v2.2.1-0.20230809180636-8e89b62488d7/go.mod h1:9g65oeB8FjRyzQCF6/TQMwb2iykwudIzgi4YQ4CPX4A= -github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829 h1:fzefK1SzoRSHzZduOCzIJ2kmkBMPKwIf3FgeBlw7Jjk= -github.com/smartcontractkit/libocr v0.0.0-20230802221916-2271752fa829/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= +github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6 h1:eSo9r53fARv2MnIO5pqYvQOXMBsTlAwhHyQ6BAVp6bY= +github.com/smartcontractkit/libocr v0.0.0-20230922131214-122accb19ea6/go.mod h1:2lyRkw/qLQgUWlrWWmq5nj0y90rWeO6Y+v+fCakRgb0= github.com/smartcontractkit/ocr2keepers v0.7.6 h1:BIJGekJDHuvsKjqB2jCapNY+P16KypTLrAlsCcgkzZc= github.com/smartcontractkit/ocr2keepers v0.7.6/go.mod h1:y5QUa8sHmrqz/LYIueB5RL8aeO71kh4F9BfaJ8rZHt0= github.com/smartcontractkit/ocr2vrf v0.0.0-20230804151440-2f1eb1e20687 h1:NwC3SOc25noBTe1KUQjt45fyTIuInhoE2UfgcHAdihM= diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index 83ee91d84..6eee3a6a6 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -1,10 +1,34 @@ package solana import ( + "context" + "fmt" + "math/big" + "math/rand" + "strings" + "sync" + "time" + + solanago "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/programs/system" + "github.com/gagliardetto/solana-go/rpc" + "github.com/pkg/errors" + "go.uber.org/multierr" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-relay/pkg/chains" + "github.com/smartcontractkit/chainlink-relay/pkg/logger" + "github.com/smartcontractkit/chainlink-relay/pkg/loop" + "github.com/smartcontractkit/chainlink-relay/pkg/services" "github.com/smartcontractkit/chainlink-relay/pkg/types" + relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-relay/pkg/utils" "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/monitor" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" ) type Chain interface { @@ -16,3 +40,429 @@ type Chain interface { // Reader returns a new Reader from the available list of nodes (if there are multiple, it will randomly select one) Reader() (client.Reader, error) } + +// DefaultRequestTimeout is the default Solana client timeout. +const DefaultRequestTimeout = 30 * time.Second + +// ChainOpts holds options for configuring a Chain. +type ChainOpts struct { + Logger logger.Logger + KeyStore types.Keystore +} + +func (o *ChainOpts) Validate() (err error) { + required := func(s string) error { + return errors.Errorf("%s is required", s) + } + if o.Logger == nil { + err = multierr.Append(err, required("Logger")) + } + if o.KeyStore == nil { + err = multierr.Append(err, required("KeyStore")) + } + return +} + +func (o *ChainOpts) GetLogger() logger.Logger { + return o.Logger +} + +func NewChain(cfg *SolanaConfig, opts ChainOpts) (Chain, error) { + if !cfg.IsEnabled() { + return nil, fmt.Errorf("cannot create new chain with ID %s: chain is disabled", *cfg.ChainID) + } + c, err := newChain(*cfg.ChainID, cfg, opts.KeyStore, opts.Logger) + if err != nil { + return nil, err + } + return c, nil +} + +var _ Chain = (*chain)(nil) + +type chain struct { + utils.StartStopOnce + id string + cfg *SolanaConfig + txm *txm.Txm + balanceMonitor types.Service + lggr logger.Logger + + // tracking node chain id for verification + clientCache map[string]*verifiedCachedClient // map URL -> {client, chainId} [mainnet/testnet/devnet/localnet] + clientLock sync.RWMutex +} + +type verifiedCachedClient struct { + chainID string + expectedChainID string + nodeURL string + + chainIDVerified bool + chainIDVerifiedLock sync.RWMutex + + client.ReaderWriter +} + +func (v *verifiedCachedClient) verifyChainID() (bool, error) { + v.chainIDVerifiedLock.RLock() + if v.chainIDVerified { + v.chainIDVerifiedLock.RUnlock() + return true, nil + } + v.chainIDVerifiedLock.RUnlock() + + var err error + + v.chainIDVerifiedLock.Lock() + defer v.chainIDVerifiedLock.Unlock() + + v.chainID, err = v.ReaderWriter.ChainID() + if err != nil { + v.chainIDVerified = false + return v.chainIDVerified, errors.Wrap(err, "failed to fetch ChainID in verifiedCachedClient") + } + + // check chainID matches expected chainID + expectedChainID := strings.ToLower(v.expectedChainID) + if v.chainID != expectedChainID { + v.chainIDVerified = false + return v.chainIDVerified, errors.Errorf("client returned mismatched chain id (expected: %s, got: %s): %s", expectedChainID, v.chainID, v.nodeURL) + } + + v.chainIDVerified = true + + return v.chainIDVerified, nil +} + +func (v *verifiedCachedClient) SendTx(ctx context.Context, tx *solanago.Transaction) (solanago.Signature, error) { + verified, err := v.verifyChainID() + if !verified { + return [64]byte{}, err + } + + return v.ReaderWriter.SendTx(ctx, tx) +} + +func (v *verifiedCachedClient) SimulateTx(ctx context.Context, tx *solanago.Transaction, opts *rpc.SimulateTransactionOpts) (*rpc.SimulateTransactionResult, error) { + verified, err := v.verifyChainID() + if !verified { + return nil, err + } + + return v.ReaderWriter.SimulateTx(ctx, tx, opts) +} + +func (v *verifiedCachedClient) SignatureStatuses(ctx context.Context, sigs []solanago.Signature) ([]*rpc.SignatureStatusesResult, error) { + verified, err := v.verifyChainID() + if !verified { + return nil, err + } + + return v.ReaderWriter.SignatureStatuses(ctx, sigs) +} + +func (v *verifiedCachedClient) Balance(addr solanago.PublicKey) (uint64, error) { + verified, err := v.verifyChainID() + if !verified { + return 0, err + } + + return v.ReaderWriter.Balance(addr) +} + +func (v *verifiedCachedClient) SlotHeight() (uint64, error) { + verified, err := v.verifyChainID() + if !verified { + return 0, err + } + + return v.ReaderWriter.SlotHeight() +} + +func (v *verifiedCachedClient) LatestBlockhash() (*rpc.GetLatestBlockhashResult, error) { + verified, err := v.verifyChainID() + if !verified { + return nil, err + } + + return v.ReaderWriter.LatestBlockhash() +} + +func (v *verifiedCachedClient) ChainID() (string, error) { + verified, err := v.verifyChainID() + if !verified { + return "", err + } + + return v.chainID, nil +} + +func (v *verifiedCachedClient) GetFeeForMessage(msg string) (uint64, error) { + verified, err := v.verifyChainID() + if !verified { + return 0, err + } + + return v.ReaderWriter.GetFeeForMessage(msg) +} + +func (v *verifiedCachedClient) GetAccountInfoWithOpts(ctx context.Context, addr solanago.PublicKey, opts *rpc.GetAccountInfoOpts) (*rpc.GetAccountInfoResult, error) { + verified, err := v.verifyChainID() + if !verified { + return nil, err + } + + return v.ReaderWriter.GetAccountInfoWithOpts(ctx, addr, opts) +} + +func newChain(id string, cfg *SolanaConfig, ks loop.Keystore, lggr logger.Logger) (*chain, error) { + lggr = logger.With(lggr, "chainID", id, "chain", "solana") + var ch = chain{ + id: id, + cfg: cfg, + lggr: logger.Named(lggr, "Chain"), + clientCache: map[string]*verifiedCachedClient{}, + } + tc := func() (client.ReaderWriter, error) { + return ch.getClient() + } + ch.txm = txm.NewTxm(ch.id, tc, cfg, ks, lggr) + ch.balanceMonitor = monitor.NewBalanceMonitor(ch.id, cfg, lggr, ks, ch.Reader) + return &ch, nil +} + +// ChainService interface +func (c *chain) GetChainStatus(ctx context.Context) (relaytypes.ChainStatus, error) { + toml, err := c.cfg.TOMLString() + if err != nil { + return relaytypes.ChainStatus{}, err + } + return relaytypes.ChainStatus{ + ID: c.id, + Enabled: c.cfg.IsEnabled(), + Config: toml, + }, nil +} + +func (c *chain) ListNodeStatuses(ctx context.Context, pageSize int32, pageToken string) (stats []relaytypes.NodeStatus, nextPageToken string, total int, err error) { + return chains.ListNodeStatuses(int(pageSize), pageToken, c.listNodeStatuses) +} + +func (c *chain) Transact(ctx context.Context, from, to string, amount *big.Int, balanceCheck bool) error { + return c.sendTx(ctx, from, to, amount, balanceCheck) +} + +func (c *chain) listNodeStatuses(start, end int) ([]relaytypes.NodeStatus, int, error) { + stats := make([]relaytypes.NodeStatus, 0) + total := len(c.cfg.Nodes) + if start >= total { + return stats, total, chains.ErrOutOfRange + } + if end > total { + end = total + } + nodes := c.cfg.Nodes[start:end] + for _, node := range nodes { + stat, err := nodeStatus(node, c.ChainID()) + if err != nil { + return stats, total, err + } + stats = append(stats, stat) + } + return stats, total, nil +} + +func (c *chain) Name() string { + return c.lggr.Name() +} + +func (c *chain) ID() string { + return c.id +} + +func (c *chain) Config() config.Config { + return c.cfg +} + +func (c *chain) TxManager() TxManager { + return c.txm +} + +func (c *chain) Reader() (client.Reader, error) { + return c.getClient() +} + +func (c *chain) ChainID() string { + return c.id +} + +// getClient returns a client, randomly selecting one from available and valid nodes +func (c *chain) getClient() (client.ReaderWriter, error) { + var node db.Node + var client client.ReaderWriter + nodes, err := c.cfg.ListNodes() + if err != nil { + return nil, errors.Wrap(err, "failed to get nodes") + } + if len(nodes) == 0 { + return nil, errors.New("no nodes available") + } + // #nosec + index := rand.Perm(len(nodes)) // list of node indexes to try + for _, i := range index { + node = nodes[i] + // create client and check + client, err = c.verifiedClient(node) + // if error, try another node + if err != nil { + c.lggr.Warnw("failed to create node", "name", node.Name, "solana-url", node.SolanaURL, "err", err.Error()) + continue + } + // if all checks passed, mark found and break loop + break + } + // if no valid node found, exit with error + if client == nil { + return nil, errors.New("no node valid nodes available") + } + c.lggr.Debugw("Created client", "name", node.Name, "solana-url", node.SolanaURL) + return client, nil +} + +// verifiedClient returns a client for node or an error if fails to create the client. +// The client will still be returned if the nodes are not valid, or the chain id doesn't match. +// Further client calls will try and verify the client, and fail if the client is still not valid. +func (c *chain) verifiedClient(node db.Node) (client.ReaderWriter, error) { + url := node.SolanaURL + var err error + + // check if cached client exists + c.clientLock.RLock() + cl, exists := c.clientCache[url] + c.clientLock.RUnlock() + + if !exists { + cl = &verifiedCachedClient{ + nodeURL: url, + expectedChainID: c.id, + } + // create client + cl.ReaderWriter, err = client.NewClient(url, c.cfg, DefaultRequestTimeout, logger.Named(c.lggr, "Client."+node.Name)) + if err != nil { + return nil, errors.Wrap(err, "failed to create client") + } + + c.clientLock.Lock() + // recheck when writing to prevent parallel writes (discard duplicate if exists) + if cached, exists := c.clientCache[url]; !exists { + c.clientCache[url] = cl + } else { + cl = cached + } + c.clientLock.Unlock() + } + + return cl, nil +} + +func (c *chain) Start(ctx context.Context) error { + return c.StartOnce("Chain", func() error { + c.lggr.Debug("Starting") + c.lggr.Debug("Starting txm") + c.lggr.Debug("Starting balance monitor") + var ms services.MultiStart + return ms.Start(ctx, c.txm, c.balanceMonitor) + }) +} + +func (c *chain) Close() error { + return c.StopOnce("Chain", func() error { + c.lggr.Debug("Stopping") + c.lggr.Debug("Stopping txm") + c.lggr.Debug("Stopping balance monitor") + return services.CloseAll(c.txm, c.balanceMonitor) + }) +} + +func (c *chain) Ready() error { + return multierr.Combine( + c.StartStopOnce.Ready(), + c.txm.Ready(), + ) +} + +func (c *chain) HealthReport() map[string]error { + report := map[string]error{c.Name(): c.StartStopOnce.Healthy()} + maps.Copy(report, c.txm.HealthReport()) + return report +} + +func (c *chain) sendTx(ctx context.Context, from, to string, amount *big.Int, balanceCheck bool) error { + reader, err := c.Reader() + if err != nil { + return fmt.Errorf("chain unreachable: %w", err) + } + + fromKey, err := solanago.PublicKeyFromBase58(from) + if err != nil { + return fmt.Errorf("failed to parse from key: %w", err) + } + toKey, err := solanago.PublicKeyFromBase58(to) + if err != nil { + return fmt.Errorf("failed to parse to key: %w", err) + } + if !amount.IsUint64() { + return fmt.Errorf("amount %s overflows uint64", amount) + } + amountI := amount.Uint64() + + blockhash, err := reader.LatestBlockhash() + if err != nil { + return fmt.Errorf("failed to get latest block hash: %w", err) + } + tx, err := solanago.NewTransaction( + []solanago.Instruction{ + system.NewTransferInstruction( + amountI, + fromKey, + toKey, + ).Build(), + }, + blockhash.Value.Blockhash, + solanago.TransactionPayer(fromKey), + ) + if err != nil { + return fmt.Errorf("failed to create tx: %w", err) + } + + if balanceCheck { + if err = solanaValidateBalance(reader, fromKey, amountI, tx.Message.ToBase64()); err != nil { + return fmt.Errorf("failed to validate balance: %w", err) + } + } + + txm := c.TxManager() + err = txm.Enqueue("", tx) + if err != nil { + return fmt.Errorf("transaction failed: %w", err) + } + return nil +} + +func solanaValidateBalance(reader client.Reader, from solanago.PublicKey, amount uint64, msg string) error { + balance, err := reader.Balance(from) + if err != nil { + return err + } + + fee, err := reader.GetFeeForMessage(msg) + if err != nil { + return err + } + + if balance < (amount + fee) { + return fmt.Errorf("balance %d is too low for this transaction to be executed: amount %d + fee %d", balance, amount, fee) + } + return nil +} diff --git a/pkg/solana/chain_test.go b/pkg/solana/chain_test.go new file mode 100644 index 000000000..67522b2e2 --- /dev/null +++ b/pkg/solana/chain_test.go @@ -0,0 +1,231 @@ +package solana + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-relay/pkg/utils" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" + + "github.com/smartcontractkit/chainlink-relay/pkg/logger" +) + +const TestSolanaGenesisHashTemplate = `{"jsonrpc":"2.0","result":"%s","id":1}` + +func TestSolanaChain_GetClient(t *testing.T) { + checkOnce := map[string]struct{}{} + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + out := fmt.Sprintf(TestSolanaGenesisHashTemplate, client.MainnetGenesisHash) // mainnet genesis hash + + if !strings.Contains(r.URL.Path, "/mismatch") { + // devnet gensis hash + out = fmt.Sprintf(TestSolanaGenesisHashTemplate, client.DevnetGenesisHash) + + // clients with correct chainID should request chainID only once + if _, exists := checkOnce[r.URL.Path]; exists { + assert.NoError(t, errors.Errorf("rpc has been called once already for successful client '%s'", r.URL.Path)) + } + checkOnce[r.URL.Path] = struct{}{} + } + + _, err := w.Write([]byte(out)) + require.NoError(t, err) + })) + defer mockServer.Close() + + ch := solcfg.Chain{} + ch.SetDefaults() + cfg := &SolanaConfig{ + ChainID: ptr("devnet"), + Chain: ch, + } + testChain := chain{ + id: "devnet", + cfg: cfg, + lggr: logger.Test(t), + clientCache: map[string]*verifiedCachedClient{}, + } + + cfg.Nodes = SolanaNodes([]*solcfg.Node{ + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/1"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/2"), + }, + }) + _, err := testChain.getClient() + assert.NoError(t, err) + + // random nodes (happy path, 1 valid + multiple invalid) + cfg.Nodes = SolanaNodes([]*solcfg.Node{ + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/1"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/1"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/2"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/3"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/4"), + }, + }) + _, err = testChain.getClient() + assert.NoError(t, err) + + // empty nodes response + cfg.Nodes = nil + _, err = testChain.getClient() + assert.Error(t, err) + + // no valid nodes to select from + cfg.Nodes = SolanaNodes([]*solcfg.Node{ + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/1"), + }, + &solcfg.Node{ + Name: ptr("devnet"), + URL: utils.MustParseURL(mockServer.URL + "/mismatch/2"), + }, + }) + _, err = testChain.getClient() + assert.NoError(t, err) +} + +func TestSolanaChain_VerifiedClient(t *testing.T) { + called := false + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + out := `{ "jsonrpc": "2.0", "result": 1234, "id": 1 }` // getSlot response + + body, err := io.ReadAll(r.Body) + require.NoError(t, err) + + // handle getGenesisHash request + if strings.Contains(string(body), "getGenesisHash") { + // should only be called once, chainID will be cached in chain + // allowing `mismatch` to be ignored, since invalid nodes will try to verify the chain ID + // if it is not verified + if !strings.Contains(r.URL.Path, "/mismatch") && called { + assert.NoError(t, errors.New("rpc has been called once already")) + } + // devnet genesis hash + out = fmt.Sprintf(TestSolanaGenesisHashTemplate, client.DevnetGenesisHash) + } + _, err = w.Write([]byte(out)) + require.NoError(t, err) + called = true + })) + defer mockServer.Close() + + ch := solcfg.Chain{} + ch.SetDefaults() + cfg := &SolanaConfig{ + ChainID: ptr("devnet"), + Chain: ch, + } + testChain := chain{ + cfg: cfg, + lggr: logger.Test(t), + clientCache: map[string]*verifiedCachedClient{}, + } + node := db.Node{SolanaURL: mockServer.URL} + + // happy path + testChain.id = "devnet" + _, err := testChain.verifiedClient(node) + assert.NoError(t, err) + + // retrieve cached client and retrieve slot height + c, err := testChain.verifiedClient(node) + assert.NoError(t, err) + slot, err := c.SlotHeight() + assert.NoError(t, err) + assert.Equal(t, uint64(1234), slot) + + node.SolanaURL = mockServer.URL + "/mismatch" + testChain.id = "incorrect" + c, err = testChain.verifiedClient(node) + assert.NoError(t, err) + _, err = c.ChainID() + // expect error from id mismatch (even if using a cached client) when performing RPC calls + assert.Error(t, err) + assert.Equal(t, fmt.Sprintf("client returned mismatched chain id (expected: %s, got: %s): %s", "incorrect", "devnet", node.SolanaURL), err.Error()) +} + +func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + out := fmt.Sprintf(TestSolanaGenesisHashTemplate, client.DevnetGenesisHash) + _, err := w.Write([]byte(out)) + require.NoError(t, err) + })) + defer mockServer.Close() + + ch := solcfg.Chain{} + ch.SetDefaults() + cfg := &SolanaConfig{ + ChainID: ptr("devnet"), + Enabled: ptr(true), + Chain: ch, + } + testChain := chain{ + id: "devnet", + cfg: cfg, + lggr: logger.Test(t), + clientCache: map[string]*verifiedCachedClient{}, + } + node := db.Node{SolanaURL: mockServer.URL} + + var wg sync.WaitGroup + wg.Add(2) + + var client0 client.ReaderWriter + var client1 client.ReaderWriter + var err0 error + var err1 error + + // call verifiedClient in parallel + go func() { + client0, err0 = testChain.verifiedClient(node) + assert.NoError(t, err0) + wg.Done() + }() + go func() { + client1, err1 = testChain.verifiedClient(node) + assert.NoError(t, err1) + wg.Done() + }() + + wg.Wait() + + // check if pointers are all the same + assert.Equal(t, testChain.clientCache[mockServer.URL], client0) + assert.Equal(t, testChain.clientCache[mockServer.URL], client1) +} + +func ptr[T any](t T) *T { + return &t +} diff --git a/pkg/solana/config.go b/pkg/solana/config.go new file mode 100644 index 000000000..7887fbc4b --- /dev/null +++ b/pkg/solana/config.go @@ -0,0 +1,270 @@ +package solana + +import ( + "fmt" + "net/url" + "time" + + "github.com/gagliardetto/solana-go/rpc" + "github.com/pelletier/go-toml/v2" + "go.uber.org/multierr" + "golang.org/x/exp/slices" + + "github.com/smartcontractkit/chainlink-relay/pkg/config" + relaytypes "github.com/smartcontractkit/chainlink-relay/pkg/types" + + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" + soldb "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" +) + +type SolanaConfigs []*SolanaConfig + +func (cs SolanaConfigs) ValidateConfig() (err error) { + return cs.validateKeys() +} + +func (cs SolanaConfigs) validateKeys() (err error) { + // Unique chain IDs + chainIDs := config.UniqueStrings{} + for i, c := range cs { + if chainIDs.IsDupe(c.ChainID) { + err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.ChainID", i), *c.ChainID)) + } + } + + // Unique node names + names := config.UniqueStrings{} + for i, c := range cs { + for j, n := range c.Nodes { + if names.IsDupe(n.Name) { + err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.Nodes.%d.Name", i, j), *n.Name)) + } + } + } + + // Unique URLs + urls := config.UniqueStrings{} + for i, c := range cs { + for j, n := range c.Nodes { + u := (*url.URL)(n.URL) + if urls.IsDupeFmt(u) { + err = multierr.Append(err, config.NewErrDuplicate(fmt.Sprintf("%d.Nodes.%d.URL", i, j), u.String())) + } + } + } + return +} + +func (cs *SolanaConfigs) SetFrom(fs *SolanaConfigs) (err error) { + if err1 := fs.validateKeys(); err1 != nil { + return err1 + } + for _, f := range *fs { + if f.ChainID == nil { + *cs = append(*cs, f) + } else if i := slices.IndexFunc(*cs, func(c *SolanaConfig) bool { + return c.ChainID != nil && *c.ChainID == *f.ChainID + }); i == -1 { + *cs = append(*cs, f) + } else { + (*cs)[i].SetFrom(f) + } + } + return +} + +func nodeStatus(n *solcfg.Node, id string) (relaytypes.NodeStatus, error) { + var s relaytypes.NodeStatus + s.ChainID = id + s.Name = *n.Name + b, err := toml.Marshal(n) + if err != nil { + return relaytypes.NodeStatus{}, err + } + s.Config = string(b) + return s, nil +} + +type SolanaNodes []*solcfg.Node + +func (ns *SolanaNodes) SetFrom(fs *SolanaNodes) { + for _, f := range *fs { + if f.Name == nil { + *ns = append(*ns, f) + } else if i := slices.IndexFunc(*ns, func(n *solcfg.Node) bool { + return n.Name != nil && *n.Name == *f.Name + }); i == -1 { + *ns = append(*ns, f) + } else { + setFromNode((*ns)[i], f) + } + } +} + +func setFromNode(n, f *solcfg.Node) { + if f.Name != nil { + n.Name = f.Name + } + if f.URL != nil { + n.URL = f.URL + } +} + +func legacySolNode(n *solcfg.Node, id string) soldb.Node { + return soldb.Node{ + Name: *n.Name, + SolanaChainID: id, + SolanaURL: (*url.URL)(n.URL).String(), + } +} + +type SolanaConfig struct { + ChainID *string + // Do not access directly, use [IsEnabled] + Enabled *bool + solcfg.Chain + Nodes SolanaNodes +} + +func (c *SolanaConfig) IsEnabled() bool { + return c.Enabled == nil || *c.Enabled +} + +func (c *SolanaConfig) SetFrom(f *SolanaConfig) { + if f.ChainID != nil { + c.ChainID = f.ChainID + } + if f.Enabled != nil { + c.Enabled = f.Enabled + } + setFromChain(&c.Chain, &f.Chain) + c.Nodes.SetFrom(&f.Nodes) +} + +func setFromChain(c, f *solcfg.Chain) { + if f.BalancePollPeriod != nil { + c.BalancePollPeriod = f.BalancePollPeriod + } + if f.ConfirmPollPeriod != nil { + c.ConfirmPollPeriod = f.ConfirmPollPeriod + } + if f.OCR2CachePollPeriod != nil { + c.OCR2CachePollPeriod = f.OCR2CachePollPeriod + } + if f.OCR2CacheTTL != nil { + c.OCR2CacheTTL = f.OCR2CacheTTL + } + if f.TxTimeout != nil { + c.TxTimeout = f.TxTimeout + } + if f.TxRetryTimeout != nil { + c.TxRetryTimeout = f.TxRetryTimeout + } + if f.TxConfirmTimeout != nil { + c.TxConfirmTimeout = f.TxConfirmTimeout + } + if f.SkipPreflight != nil { + c.SkipPreflight = f.SkipPreflight + } + if f.Commitment != nil { + c.Commitment = f.Commitment + } + if f.MaxRetries != nil { + c.MaxRetries = f.MaxRetries + } +} + +func (c *SolanaConfig) ValidateConfig() (err error) { + if c.ChainID == nil { + err = multierr.Append(err, config.ErrMissing{Name: "ChainID", Msg: "required for all chains"}) + } else if *c.ChainID == "" { + err = multierr.Append(err, config.ErrEmpty{Name: "ChainID", Msg: "required for all chains"}) + } + + if len(c.Nodes) == 0 { + err = multierr.Append(err, config.ErrMissing{Name: "Nodes", Msg: "must have at least one node"}) + } + return +} + +func (c *SolanaConfig) TOMLString() (string, error) { + b, err := toml.Marshal(c) + if err != nil { + return "", err + } + return string(b), nil +} + +var _ solcfg.Config = &SolanaConfig{} + +func (c *SolanaConfig) BalancePollPeriod() time.Duration { + return c.Chain.BalancePollPeriod.Duration() +} + +func (c *SolanaConfig) ConfirmPollPeriod() time.Duration { + return c.Chain.ConfirmPollPeriod.Duration() +} + +func (c *SolanaConfig) OCR2CachePollPeriod() time.Duration { + return c.Chain.OCR2CachePollPeriod.Duration() +} + +func (c *SolanaConfig) OCR2CacheTTL() time.Duration { + return c.Chain.OCR2CacheTTL.Duration() +} + +func (c *SolanaConfig) TxTimeout() time.Duration { + return c.Chain.TxTimeout.Duration() +} + +func (c *SolanaConfig) TxRetryTimeout() time.Duration { + return c.Chain.TxRetryTimeout.Duration() +} + +func (c *SolanaConfig) TxConfirmTimeout() time.Duration { + return c.Chain.TxConfirmTimeout.Duration() +} + +func (c *SolanaConfig) SkipPreflight() bool { + return *c.Chain.SkipPreflight +} + +func (c *SolanaConfig) Commitment() rpc.CommitmentType { + return rpc.CommitmentType(*c.Chain.Commitment) +} + +func (c *SolanaConfig) MaxRetries() *uint { + if c.Chain.MaxRetries == nil { + return nil + } + mr := uint(*c.Chain.MaxRetries) + return &mr +} + +func (c *SolanaConfig) FeeEstimatorMode() string { + return *c.Chain.FeeEstimatorMode +} + +func (c *SolanaConfig) ComputeUnitPriceMax() uint64 { + return *c.Chain.ComputeUnitPriceMax +} + +func (c *SolanaConfig) ComputeUnitPriceMin() uint64 { + return *c.Chain.ComputeUnitPriceMin +} + +func (c *SolanaConfig) ComputeUnitPriceDefault() uint64 { + return *c.Chain.ComputeUnitPriceDefault +} + +func (c *SolanaConfig) FeeBumpPeriod() time.Duration { + return c.Chain.FeeBumpPeriod.Duration() +} + +func (c *SolanaConfig) ListNodes() ([]soldb.Node, error) { + var allNodes []soldb.Node + for _, n := range c.Nodes { + allNodes = append(allNodes, legacySolNode(n, *c.ChainID)) + } + return allNodes, nil +} diff --git a/pkg/solana/monitor/balance.go b/pkg/solana/monitor/balance.go new file mode 100644 index 000000000..bd6593240 --- /dev/null +++ b/pkg/solana/monitor/balance.go @@ -0,0 +1,150 @@ +package monitor + +import ( + "context" + "time" + + "github.com/gagliardetto/solana-go" + + "github.com/smartcontractkit/chainlink-relay/pkg/logger" + "github.com/smartcontractkit/chainlink-relay/pkg/types" + "github.com/smartcontractkit/chainlink-relay/pkg/utils" + + solanaClient "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" +) + +// Config defines the monitor configuration. +type Config interface { + BalancePollPeriod() time.Duration +} + +// Keystore provides the keys to be monitored. +type Keystore interface { + Accounts(ctx context.Context) ([]string, error) +} + +// NewBalanceMonitor returns a balance monitoring services.Service which reports the SOL balance of all ks keys to prometheus. +func NewBalanceMonitor(chainID string, cfg Config, lggr logger.Logger, ks Keystore, newReader func() (solanaClient.Reader, error)) types.Service { + return newBalanceMonitor(chainID, cfg, lggr, ks, newReader) +} + +func newBalanceMonitor(chainID string, cfg Config, lggr logger.Logger, ks Keystore, newReader func() (solanaClient.Reader, error)) *balanceMonitor { + b := balanceMonitor{ + chainID: chainID, + cfg: cfg, + lggr: logger.Named(lggr, "BalanceMonitor"), + ks: ks, + newReader: newReader, + stop: make(chan struct{}), + done: make(chan struct{}), + } + b.updateFn = b.updateProm + return &b +} + +type balanceMonitor struct { + utils.StartStopOnce + chainID string + cfg Config + lggr logger.Logger + ks Keystore + newReader func() (solanaClient.Reader, error) + updateFn func(acc solana.PublicKey, lamports uint64) // overridable for testing + + reader solanaClient.Reader + + stop, done chan struct{} +} + +func (b *balanceMonitor) Name() string { + return b.lggr.Name() +} + +func (b *balanceMonitor) Start(context.Context) error { + return b.StartOnce("SolanaBalanceMonitor", func() error { + go b.monitor() + return nil + }) +} + +func (b *balanceMonitor) Close() error { + return b.StopOnce("SolanaBalanceMonitor", func() error { + close(b.stop) + <-b.done + return nil + }) +} + +func (b *balanceMonitor) HealthReport() map[string]error { + return map[string]error{b.Name(): b.Healthy()} +} + +func (b *balanceMonitor) monitor() { + defer close(b.done) + ctx, cancel := utils.ContextFromChan(b.stop) + defer cancel() + + tick := time.After(utils.WithJitter(b.cfg.BalancePollPeriod())) + for { + select { + case <-b.stop: + return + case <-tick: + b.updateBalances(ctx) + tick = time.After(utils.WithJitter(b.cfg.BalancePollPeriod())) + } + } +} + +// getReader returns the cached solanaClient.Reader, or creates a new one if nil. +func (b *balanceMonitor) getReader() (solanaClient.Reader, error) { + if b.reader == nil { + var err error + b.reader, err = b.newReader() + if err != nil { + return nil, err + } + } + return b.reader, nil +} + +func (b *balanceMonitor) updateBalances(ctx context.Context) { + keys, err := b.ks.Accounts(ctx) + if err != nil { + b.lggr.Errorw("Failed to get keys", "err", err) + return + } + if len(keys) == 0 { + return + } + reader, err := b.getReader() + if err != nil { + b.lggr.Errorw("Failed to get client", "err", err) + return + } + var gotSomeBals bool + for _, k := range keys { + // Check for shutdown signal, since Balance blocks and may be slow. + select { + case <-b.stop: + return + default: + } + pubKey, err := solana.PublicKeyFromBase58(k) + if err != nil { + b.lggr.Errorw("Failed parse public key", "account", k, "err", err) + continue + } + lamports, err := reader.Balance(pubKey) + if err != nil { + b.lggr.Errorw("Failed to get balance", "account", k, "err", err) + continue + } + gotSomeBals = true + b.updateFn(pubKey, lamports) + } + if !gotSomeBals { + // Try a new client next time. + b.reader = nil + } +} diff --git a/pkg/solana/monitor/balance_test.go b/pkg/solana/monitor/balance_test.go new file mode 100644 index 000000000..b9cd7fee4 --- /dev/null +++ b/pkg/solana/monitor/balance_test.go @@ -0,0 +1,94 @@ +package monitor + +import ( + "context" + "crypto/rand" + "fmt" + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-relay/pkg/logger" + "github.com/smartcontractkit/chainlink-relay/pkg/utils/tests" + solanaRelay "github.com/smartcontractkit/chainlink-solana/pkg/solana" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/mocks" +) + +func TestBalanceMonitor(t *testing.T) { + const chainID = "Chainlinktest-42" + ks := keystore{} + for i := 0; i < 3; i++ { + var k solana.PublicKey + n, err := rand.Read(k[:]) + require.NoError(t, err) + require.Equal(t, len(k), n) + ks = append(ks, k) + } + + bals := []uint64{0, 1, 1_000_000_000} + expBals := []string{ + "0.000000000", + "0.000000001", + "1.000000000", + } + + client := new(mocks.ReaderWriter) + client.Test(t) + type update struct{ acc, bal string } + var exp []update + for i := range bals { + acc := ks[i] + client.On("Balance", acc).Return(bals[i], nil) + exp = append(exp, update{acc.String(), expBals[i]}) + } + cfg := &config{balancePollPeriod: time.Second} + b := newBalanceMonitor(chainID, cfg, logger.Test(t), ks, nil) + var got []update + done := make(chan struct{}) + b.updateFn = func(acc solana.PublicKey, lamports uint64) { + select { + case <-done: + return + default: + } + v := solanaRelay.LamportsToSol(lamports) // convert from lamports to SOL + got = append(got, update{acc.String(), fmt.Sprintf("%.9f", v)}) + if len(got) == len(exp) { + close(done) + } + } + b.reader = client + + require.NoError(t, b.Start(tests.Context(t))) + t.Cleanup(func() { + assert.NoError(t, b.Close()) + client.AssertExpectations(t) + }) + select { + case <-time.After(tests.WaitTimeout(t)): + t.Fatal("timed out waiting for balance monitor") + case <-done: + } + + assert.EqualValues(t, exp, got) +} + +type config struct { + balancePollPeriod time.Duration +} + +func (c *config) BalancePollPeriod() time.Duration { + return c.balancePollPeriod +} + +type keystore []solana.PublicKey + +func (k keystore) Accounts(ctx context.Context) (ks []string, err error) { + for _, acc := range k { + ks = append(ks, acc.String()) + } + return +} diff --git a/pkg/solana/monitor/prom.go b/pkg/solana/monitor/prom.go new file mode 100644 index 000000000..f853b6610 --- /dev/null +++ b/pkg/solana/monitor/prom.go @@ -0,0 +1,19 @@ +package monitor + +import ( + "github.com/gagliardetto/solana-go" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + solanaRelay "github.com/smartcontractkit/chainlink-solana/pkg/solana" +) + +var promSolanaBalance = promauto.NewGaugeVec( + prometheus.GaugeOpts{Name: "solana_balance", Help: "Solana account balances"}, + []string{"account", "chainID", "chainSet", "denomination"}, +) + +func (b *balanceMonitor) updateProm(acc solana.PublicKey, lamports uint64) { + v := solanaRelay.LamportsToSol(lamports) // convert from lamports to SOL + promSolanaBalance.WithLabelValues(acc.String(), b.chainID, "solana", "SOL").Set(v) +} diff --git a/pkg/solana/monitor/prom_test.go b/pkg/solana/monitor/prom_test.go new file mode 100644 index 000000000..d1646e368 --- /dev/null +++ b/pkg/solana/monitor/prom_test.go @@ -0,0 +1,21 @@ +package monitor + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" +) + +func TestPromSolBalance(t *testing.T) { + key := solana.PublicKey{} + balance := uint64(1_000_000_000) + + monitor := balanceMonitor{chainID: "test-chain"} + monitor.updateProm(key, balance) + + // happy path test + promBalance := testutil.ToFloat64(promSolanaBalance.WithLabelValues(key.String(), monitor.chainID, "solana", "SOL")) + assert.Equal(t, float64(balance)/float64(solana.LAMPORTS_PER_SOL), promBalance) +}