diff --git a/.mockery.yaml b/.mockery.yaml index 1df96bfec..5b5c1efef 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -40,3 +40,9 @@ packages: github.com/smartcontractkit/chainlink-solana/pkg/solana/logpoller: interfaces: RPCClient: + ORM: + config: + inpackage: True + dir: "pkg/solana/logpoller" + filename: mock_orm.go + mockname: mockORM diff --git a/go.mod b/go.mod index 8e6423815..6c69afc81 100644 --- a/go.mod +++ b/go.mod @@ -12,10 +12,12 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 github.com/google/uuid v1.6.0 github.com/hashicorp/go-plugin v1.6.2 + github.com/jackc/pgx/v4 v4.18.3 github.com/jpillora/backoff v1.0.0 + github.com/lib/pq v1.10.9 github.com/pelletier/go-toml/v2 v2.2.0 github.com/prometheus/client_golang v1.17.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 + github.com/smartcontractkit/chainlink-common v0.4.0 github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 @@ -28,6 +30,7 @@ require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect + github.com/apache/arrow-go/v18 v18.0.0 // indirect github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -46,9 +49,11 @@ require ( github.com/go-playground/locales v0.13.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-playground/validator/v10 v10.4.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect @@ -57,27 +62,37 @@ require ( github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/invopop/jsonschema v0.12.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.2.0 // indirect - github.com/lib/pq v1.10.9 // indirect github.com/linkedin/goavro/v2 v2.12.0 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/marcboeker/go-duckdb v1.8.3 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.35 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 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/oklog/run v1.0.0 // indirect github.com/onsi/gomega v1.24.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect @@ -87,6 +102,7 @@ require ( github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect + github.com/scylladb/go-reflectx v1.0.1 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 // indirect github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 // indirect @@ -97,6 +113,7 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/xxh3 v1.0.2 // indirect go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect @@ -120,15 +137,18 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.26.0 // indirect + golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a2d9c0b39..dc7bb3640 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 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.9.4 h1:mnUj0ivWy6UzbB1uLFqKR6F+ZyiDc7j4iGgHTpO+5+I= @@ -47,6 +49,12 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= +github.com/apache/thrift v0.21.0 h1:tdPmh/ptjE1IJnhbhrcl2++TauVjy242rkV/UzJChnE= +github.com/apache/thrift v0.21.0/go.mod h1:W1H8aR/QRtYNvrPeFXBtobyRkd0/YVhTc6i07XIAgDw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= @@ -83,6 +91,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/confluentinc/confluent-kafka-go/v2 v2.3.0 h1:icCHutJouWlQREayFwCc7lxDAhws08td+W3/gdqgZts= github.com/confluentinc/confluent-kafka-go/v2 v2.3.0/go.mod h1:/VTy8iEpe6mD9pkCH5BhijlUl8ulUXymKv1Qig5Rgb8= github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= @@ -93,8 +103,10 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -145,8 +157,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg= github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -165,6 +179,10 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -200,6 +218,8 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXi github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -271,6 +291,53 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= @@ -290,22 +357,32 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/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/linkedin/goavro/v2 v2.9.7/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= @@ -318,13 +395,19 @@ github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamh github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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= @@ -340,6 +423,10 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs= github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -350,6 +437,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mount v0.3.3 h1:fX1SVkXFJ47XWDoeFW4Sq7PdQJnV2QIDZAqjNqgEjUs= github.com/moby/sys/mount v0.3.3/go.mod h1:PBaEorSNTLG5t/+4EgukEQVlAvVEc6ZjTySwKdqp5K0= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= @@ -391,6 +480,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 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= @@ -427,6 +518,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -434,16 +528,23 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/scylladb/go-reflectx v1.0.1 h1:b917wZM7189pZdlND9PbIJ6NQxfDPfBvUaQ7cjj1iZQ= +github.com/scylladb/go-reflectx v1.0.1/go.mod h1:rWnOfDIRWBGN0miMLIcoPt/Dhi2doCMZqwMCJ3KupFc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 h1:ZA92CTX9JtEArrxgZw7PNctVxFS+/DmSXumkwf1WiMY= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-common v0.4.0 h1:GZ9MhHt5QHXSaK/sAZvKDxkEqF4fPiFHWHEPqs/2C2o= +github.com/smartcontractkit/chainlink-common v0.4.0/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs= github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360= @@ -464,6 +565,7 @@ github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 h1:ZqpS7rAhh github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -513,6 +615,11 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= @@ -564,6 +671,7 @@ go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8d go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -581,7 +689,9 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= @@ -590,15 +700,20 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -651,6 +766,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/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= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -661,12 +777,13 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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= @@ -694,13 +811,16 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -730,19 +850,21 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -761,12 +883,14 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -781,6 +905,7 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -793,12 +918,18 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= +golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -856,8 +987,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM= -google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 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= @@ -869,8 +1000,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD 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= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -878,6 +1009,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 78f6d6394..668ec5913 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -14,7 +14,7 @@ require ( github.com/lib/pq v1.10.9 github.com/pelletier/go-toml/v2 v2.2.3 github.com/rs/zerolog v1.33.0 - github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 + github.com/smartcontractkit/chainlink-common v0.4.0 github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241210172617-6fd1891d0fbc github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.19 github.com/smartcontractkit/chainlink-testing-framework/seth v1.50.9 @@ -57,7 +57,7 @@ require ( github.com/VictoriaMetrics/fastcache v1.12.2 // indirect github.com/XSAM/otelsql v0.27.0 // indirect github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect - github.com/andybalholm/brotli v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/atombender/go-jsonschema v0.16.1-0.20240916205339-a74cd4e2851c // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect @@ -182,7 +182,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/goccy/go-yaml v1.12.0 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gofrs/flock v0.8.1 // indirect @@ -258,8 +258,8 @@ require ( github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kelseyhightower/envconfig v1.4.0 // indirect - github.com/klauspost/compress v1.17.9 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/leanovate/gopter v0.2.11 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 19dd0b8a2..64299f01b 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -150,9 +150,11 @@ github.com/allegro/bigcache v1.2.1/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2uc github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow-go/v18 v18.0.0 h1:1dBDaSbH3LtulTyOVYaBCHO3yVRwjV+TZaqn3g6V7ZM= +github.com/apache/arrow-go/v18 v18.0.0/go.mod h1:t6+cWRSmKgdQ6HsxisQjok+jBpKGhRDiqcf3p0p/F+A= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -568,8 +570,8 @@ github.com/go-webauthn/webauthn v0.9.4 h1:YxvHSqgUyc5AK2pZbqkWWR55qKeDPhP8zLDr6l github.com/go-webauthn/webauthn v0.9.4/go.mod h1:LqupCtzSef38FcxzaklmOn7AykGKhAhr9xlRbdbgnTw= github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= @@ -634,6 +636,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU= github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -914,11 +918,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -968,6 +972,8 @@ github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYt github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= +github.com/marcboeker/go-duckdb v1.8.3 h1:ZkYwiIZhbYsT6MmJsZ3UPTHrTZccDdM4ztoqSlEMXiQ= +github.com/marcboeker/go-duckdb v1.8.3/go.mod h1:C9bYRE1dPYb1hhfu/SSomm78B0FXmNgRvv6YBW/Hooc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -1126,6 +1132,8 @@ github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 h1:hDSdbBuw3Lefr6R18ax0tZ2BJeNB3NehB3trOwYBsdU= github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -1257,8 +1265,8 @@ github.com/smartcontractkit/chainlink-automation v0.8.1 h1:sTc9LKpBvcKPc1JDYAmgB github.com/smartcontractkit/chainlink-automation v0.8.1/go.mod h1:Iij36PvWZ6blrdC5A/nrQUBuf3MH3JvsBB9sSyc9W08= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241211150100-7683331f64a0 h1:/1L+v4SxUD2K5RMRbfByyLfePMAgQKeD0onSetPnGmA= github.com/smartcontractkit/chainlink-ccip v0.0.0-20241211150100-7683331f64a0/go.mod h1:F8xQAIW0ymb2BZhqn89sWZLXreJhM5KDVF6Qb4y44N0= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49 h1:ZA92CTX9JtEArrxgZw7PNctVxFS+/DmSXumkwf1WiMY= -github.com/smartcontractkit/chainlink-common v0.3.1-0.20241212163958-6a43e61b9d49/go.mod h1:bQktEJf7sJ0U3SmIcXvbGUox7SmXcnSEZ4kUbT8R5Nk= +github.com/smartcontractkit/chainlink-common v0.4.0 h1:GZ9MhHt5QHXSaK/sAZvKDxkEqF4fPiFHWHEPqs/2C2o= +github.com/smartcontractkit/chainlink-common v0.4.0/go.mod h1:yti7e1+G9hhkYhj+L5sVUULn9Bn3bBL5/AxaNqdJ5YQ= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e h1:PRoeby6ZlTuTkv2f+7tVU4+zboTfRzI+beECynF4JQ0= github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241202195413-82468150ac1e/go.mod h1:mUh5/woemsVaHgTorA080hrYmO3syBCmPdnWc/5dOqk= github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241202141438-a90db35252db h1:N1RH1hSr2ACzOFc9hkCcjE8pRBTdcU3p8nsTJByaLes= @@ -1425,6 +1433,8 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1434,6 +1444,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= diff --git a/pkg/solana/logpoller/filters.go b/pkg/solana/logpoller/filters.go new file mode 100644 index 000000000..4a1496371 --- /dev/null +++ b/pkg/solana/logpoller/filters.go @@ -0,0 +1,316 @@ +package logpoller + +import ( + "context" + "errors" + "fmt" + "iter" + "maps" + "sync" + "sync/atomic" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" +) + +type filters struct { + orm ORM + lggr logger.SugaredLogger + + filtersByID map[int64]*Filter + filtersByName map[string]int64 + filtersByAddress map[PublicKey]map[EventSignature]map[int64]struct{} + filtersToBackfill map[int64]struct{} + filtersToDelete map[int64]Filter + filtersMutex sync.RWMutex + loadedFilters atomic.Bool +} + +func newFilters(lggr logger.SugaredLogger, orm ORM) *filters { + return &filters{ + orm: orm, + lggr: lggr, + } +} + +// PruneFilters - prunes all filters marked to be deleted from the database and all corresponding logs. +func (fl *filters) PruneFilters(ctx context.Context) error { + err := fl.LoadFilters(ctx) + if err != nil { + return fmt.Errorf("failed to load filters: %w", err) + } + + fl.filtersMutex.Lock() + filtersToDelete := fl.filtersToDelete + fl.filtersToDelete = make(map[int64]Filter) + fl.filtersMutex.Unlock() + + if len(filtersToDelete) == 0 { + return nil + } + + err = fl.orm.DeleteFilters(ctx, filtersToDelete) + if err != nil { + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + maps.Copy(fl.filtersToDelete, filtersToDelete) + return fmt.Errorf("failed to delete filters: %w", err) + } + + return nil +} + +// RegisterFilter persists provided filter and ensures that any log emitted by a contract with filter.Address +// that matches filter.EventSig signature will be captured starting from filter.StartingBlock. +// The filter may be unregistered later by filter.Name. +// In case of Filter.Name collision (within the chain scope) returns ErrFilterNameConflict if +// one of the fields defining resulting logs (Address, EventSig, EventIDL, SubkeyPaths) does not match original filter. +// Otherwise, updates remaining fields and schedules backfill. +// Warnings/debug information is keyed by filter name. +func (fl *filters) RegisterFilter(ctx context.Context, filter Filter) error { + if len(filter.Name) == 0 { + return errors.New("name is required") + } + + err := fl.LoadFilters(ctx) + if err != nil { + return fmt.Errorf("failed to load filters: %w", err) + } + + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + + filter.IsBackfilled = false + if existingFilterID, ok := fl.filtersByName[filter.Name]; ok { + existingFilter := fl.filtersByID[existingFilterID] + if !existingFilter.MatchSameLogs(filter) { + return ErrFilterNameConflict + } + if existingFilter.IsBackfilled { + // if existing filter was already backfilled, but starting block was higher we need to backfill filter again + filter.IsBackfilled = existingFilter.StartingBlock <= filter.StartingBlock + } + + fl.removeFilterFromIndexes(*existingFilter) + } + + filterID, err := fl.orm.InsertFilter(ctx, filter) + if err != nil { + return fmt.Errorf("failed to insert filter: %w", err) + } + + filter.ID = filterID + + fl.filtersByName[filter.Name] = filter.ID + fl.filtersByID[filter.ID] = &filter + filtersForAddress, ok := fl.filtersByAddress[filter.Address] + if !ok { + filtersForAddress = make(map[EventSignature]map[int64]struct{}) + fl.filtersByAddress[filter.Address] = filtersForAddress + } + + filtersForEventSig, ok := filtersForAddress[filter.EventSig] + if !ok { + filtersForEventSig = make(map[int64]struct{}) + filtersForAddress[filter.EventSig] = filtersForEventSig + } + + filtersForEventSig[filter.ID] = struct{}{} + if !filter.IsBackfilled { + fl.filtersToBackfill[filter.ID] = struct{}{} + } + return nil +} + +// UnregisterFilter will mark the filter with the given name for pruning and async prune all corresponding logs. +// If the name does not exist, it will log an error but not return an error. +// Warnings/debug information is keyed by filter name. +func (fl *filters) UnregisterFilter(ctx context.Context, name string) error { + err := fl.LoadFilters(ctx) + if err != nil { + return fmt.Errorf("failed to load filters: %w", err) + } + + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + + filterID, ok := fl.filtersByName[name] + if !ok { + fl.lggr.Warnw("Filter not found in filtersByName", "name", name) + return nil + } + + filter, ok := fl.filtersByID[filterID] + if !ok { + fl.lggr.Errorw("Filter not found in filtersByID", "id", filterID, "name", name) + return nil + } + + if err := fl.orm.MarkFilterDeleted(ctx, filter.ID); err != nil { + return fmt.Errorf("failed to mark filter deleted: %w", err) + } + + fl.removeFilterFromIndexes(*filter) + + fl.filtersToDelete[filter.ID] = *filter + return nil +} + +func (fl *filters) removeFilterFromIndexes(filter Filter) { + delete(fl.filtersByName, filter.Name) + delete(fl.filtersToBackfill, filter.ID) + delete(fl.filtersByID, filter.ID) + + filtersForAddress, ok := fl.filtersByAddress[filter.Address] + if !ok { + fl.lggr.Warnw("Filter not found in filtersByAddress", "name", filter.Name, "address", filter.Address) + return + } + + filtersForEventSig, ok := filtersForAddress[filter.EventSig] + if !ok { + fl.lggr.Warnw("Filter not found in filtersByEventSig", "name", filter.Name, "address", filter.Address) + return + } + + delete(filtersForEventSig, filter.ID) + if len(filtersForEventSig) == 0 { + delete(filtersForAddress, filter.EventSig) + } + + if len(filtersForAddress) == 0 { + delete(fl.filtersByAddress, filter.Address) + } +} + +// MatchingFilters - returns iterator to go through all matching filters. +// Requires LoadFilters to be called at least once. +func (fl *filters) MatchingFilters(addr PublicKey, eventSignature EventSignature) iter.Seq[Filter] { + if !fl.loadedFilters.Load() { + fl.lggr.Critical("Invariant violation: expected filters to be loaded before call to MatchingFilters") + return nil + } + return func(yield func(Filter) bool) { + fl.filtersMutex.RLock() + defer fl.filtersMutex.RUnlock() + filters, ok := fl.filtersByAddress[addr] + if !ok { + return + } + + for filterID := range filters[eventSignature] { + filter, ok := fl.filtersByID[filterID] + if !ok { + fl.lggr.Errorw("expected filter to exist in filtersByID", "filterID", filterID) + continue + } + if !yield(*filter) { + return + } + } + } +} + +// GetFiltersToBackfill - returns copy of backfill queue +// Requires LoadFilters to be called at least once. +func (fl *filters) GetFiltersToBackfill() []Filter { + if !fl.loadedFilters.Load() { + fl.lggr.Critical("Invariant violation: expected filters to be loaded before call to MatchingFilters") + return nil + } + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + result := make([]Filter, 0, len(fl.filtersToBackfill)) + for filterID := range fl.filtersToBackfill { + filter, ok := fl.filtersByID[filterID] + if !ok { + fl.lggr.Errorw("expected filter to exist in filtersByID", "filterID", filterID) + continue + } + result = append(result, *filter) + } + + return result +} + +func (fl *filters) MarkFilterBackfilled(ctx context.Context, filterID int64) error { + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + filter, ok := fl.filtersByID[filterID] + if !ok { + return fmt.Errorf("filter %d not found", filterID) + } + err := fl.orm.MarkFilterBackfilled(ctx, filterID) + if err != nil { + return fmt.Errorf("failed to mark filter backfilled: %w", err) + } + + filter.IsBackfilled = true + delete(fl.filtersToBackfill, filter.ID) + return nil +} + +// LoadFilters - loads filters from database. Can be called multiple times without side effects. +func (fl *filters) LoadFilters(ctx context.Context) error { + if fl.loadedFilters.Load() { + return nil + } + + fl.lggr.Debugw("Loading filters from db") + fl.filtersMutex.Lock() + defer fl.filtersMutex.Unlock() + // reset filters' indexes to ensure we do not have partial data from the previous run + fl.filtersByID = make(map[int64]*Filter) + fl.filtersByName = make(map[string]int64) + fl.filtersByAddress = make(map[PublicKey]map[EventSignature]map[int64]struct{}) + fl.filtersToBackfill = make(map[int64]struct{}) + fl.filtersToDelete = make(map[int64]Filter) + + filters, err := fl.orm.SelectFilters(ctx) + if err != nil { + return fmt.Errorf("failed to select filters from db: %w", err) + } + + for i := range filters { + filter := filters[i] + if filter.IsDeleted { + fl.filtersToDelete[filter.ID] = filter + continue + } + + fl.filtersByID[filter.ID] = &filter + + if _, ok := fl.filtersByName[filter.Name]; ok { + errMsg := fmt.Sprintf("invariant violation while loading from db: expected filters to have unique name: %s ", filter.Name) + fl.lggr.Critical(errMsg) + return errors.New(errMsg) + } + + fl.filtersByName[filter.Name] = filter.ID + filtersForAddress, ok := fl.filtersByAddress[filter.Address] + if !ok { + filtersForAddress = make(map[EventSignature]map[int64]struct{}) + fl.filtersByAddress[filter.Address] = filtersForAddress + } + + filtersForEventSig, ok := filtersForAddress[filter.EventSig] + if !ok { + filtersForEventSig = make(map[int64]struct{}) + filtersForAddress[filter.EventSig] = filtersForEventSig + } + + if _, ok := filtersForEventSig[filter.ID]; ok { + errMsg := fmt.Sprintf("invariant violation while loading from db: expected filters to have unique ID: %d ", filter.ID) + fl.lggr.Critical(errMsg) + return errors.New(errMsg) + } + + filtersForEventSig[filter.ID] = struct{}{} + if !filter.IsBackfilled { + fl.filtersToBackfill[filter.ID] = struct{}{} + } + } + + fl.loadedFilters.Store(true) + + return nil +} diff --git a/pkg/solana/logpoller/filters_test.go b/pkg/solana/logpoller/filters_test.go new file mode 100644 index 000000000..9f8058703 --- /dev/null +++ b/pkg/solana/logpoller/filters_test.go @@ -0,0 +1,365 @@ +package logpoller + +import ( + "errors" + "fmt" + "slices" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/google/uuid" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +func TestFilters_LoadFilters(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(logger.Sugared(logger.Test(t)), orm) + ctx := tests.Context(t) + orm.On("SelectFilters", mock.Anything).Return(nil, errors.New("db failed")).Once() + deleted := Filter{ + ID: 3, + Name: "Deleted", + IsDeleted: true, + } + happyPath := Filter{ + ID: 1, + Name: "Happy path", + } + happyPath2 := Filter{ + ID: 2, + Name: "Happy path 2", + } + orm.On("SelectFilters", mock.Anything).Return([]Filter{ + deleted, + happyPath, + happyPath2, + }, nil).Once() + + err := fs.LoadFilters(ctx) + require.EqualError(t, err, "failed to select filters from db: db failed") + err = fs.LoadFilters(ctx) + require.NoError(t, err) + // only one filter to delete + require.Len(t, fs.filtersToDelete, 1) + require.Equal(t, deleted, fs.filtersToDelete[deleted.ID]) + // both happy path are indexed + require.Len(t, fs.filtersByAddress, 1) + require.Len(t, fs.filtersByAddress[happyPath.Address], 1) + require.Len(t, fs.filtersByAddress[happyPath.Address][happyPath.EventSig], 2) + require.Contains(t, fs.filtersByAddress[happyPath.Address][happyPath.EventSig], happyPath.ID) + require.Equal(t, happyPath, *fs.filtersByID[happyPath.ID]) + require.Contains(t, fs.filtersByAddress[happyPath.Address][happyPath.EventSig], happyPath2.ID) + require.Equal(t, happyPath2, *fs.filtersByID[happyPath2.ID]) + require.Len(t, fs.filtersByName, 2) + require.Equal(t, fs.filtersByName[happyPath.Name], happyPath.ID) + require.Equal(t, fs.filtersByName[happyPath2.Name], happyPath2.ID) + // any call following successful should be noop + err = fs.LoadFilters(ctx) + require.NoError(t, err) +} + +func TestFilters_RegisterFilter(t *testing.T) { + lggr := logger.Sugared(logger.Test(t)) + t.Run("Returns an error if name is empty", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + err := fs.RegisterFilter(tests.Context(t), Filter{}) + require.EqualError(t, err, "name is required") + }) + t.Run("Returns an error if fails to load filters from db", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + orm.On("SelectFilters", mock.Anything).Return(nil, errors.New("db failed")).Once() + err := fs.RegisterFilter(tests.Context(t), Filter{Name: "Filter"}) + require.EqualError(t, err, "failed to load filters: failed to select filters from db: db failed") + }) + t.Run("Returns an error if trying to update primary fields", func(t *testing.T) { + testCases := []struct { + Name string + ModifyField func(*Filter) + }{ + { + Name: "Address", + ModifyField: func(f *Filter) { + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + f.Address = PublicKey(privateKey.PublicKey()) + }, + }, + { + Name: "EventSig", + ModifyField: func(f *Filter) { + f.EventSig = EventSignature{3, 2, 1} + }, + }, + { + Name: "EventIDL", + ModifyField: func(f *Filter) { + f.EventIDL = uuid.NewString() + }, + }, + { + Name: "SubkeyPaths", + ModifyField: func(f *Filter) { + f.SubkeyPaths = [][]string{{uuid.NewString()}} + }, + }, + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("Updating %s", tc.Name), func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + dbFilter := Filter{Name: filterName} + orm.On("SelectFilters", mock.Anything).Return([]Filter{dbFilter}, nil).Once() + newFilter := dbFilter + tc.ModifyField(&newFilter) + err := fs.RegisterFilter(tests.Context(t), newFilter) + require.EqualError(t, err, ErrFilterNameConflict.Error()) + }) + } + }) + t.Run("Happy path", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + orm.On("SelectFilters", mock.Anything).Return(nil, nil).Once() + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(int64(0), errors.New("failed to insert")).Once() + filter := Filter{Name: filterName} + err := fs.RegisterFilter(tests.Context(t), filter) + require.Error(t, err) + // can readd after db issue is resolved + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(int64(1), nil).Once() + err = fs.RegisterFilter(tests.Context(t), filter) + require.NoError(t, err) + // can update non-primary fields + filter.EventName = uuid.NewString() + filter.StartingBlock++ + filter.Retention++ + filter.MaxLogsKept++ + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(int64(1), nil).Once() + err = fs.RegisterFilter(tests.Context(t), filter) + require.NoError(t, err) + storedFilters := slices.Collect(fs.MatchingFilters(filter.Address, filter.EventSig)) + require.Len(t, storedFilters, 1) + filter.ID = 1 + require.Equal(t, filter, storedFilters[0]) + }) + t.Run("Can reregister after unregister", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + orm.On("SelectFilters", mock.Anything).Return(nil, nil).Once() + const filterID = int64(10) + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(filterID, nil).Once() + err := fs.RegisterFilter(tests.Context(t), Filter{Name: filterName}) + require.NoError(t, err) + orm.On("MarkFilterDeleted", mock.Anything, filterID).Return(nil).Once() + err = fs.UnregisterFilter(tests.Context(t), filterName) + require.NoError(t, err) + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(filterID+1, nil).Once() + err = fs.RegisterFilter(tests.Context(t), Filter{Name: filterName}) + require.NoError(t, err) + require.Len(t, fs.filtersToDelete, 1) + require.Equal(t, Filter{Name: filterName, ID: filterID}, fs.filtersToDelete[filterID]) + require.Len(t, fs.filtersToBackfill, 1) + require.Contains(t, fs.filtersToBackfill, filterID+1) + }) +} + +func TestFilters_UnregisterFilter(t *testing.T) { + lggr := logger.Sugared(logger.Test(t)) + t.Run("Returns an error if fails to load filters from db", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + orm.On("SelectFilters", mock.Anything).Return(nil, errors.New("db failed")).Once() + err := fs.UnregisterFilter(tests.Context(t), "Filter") + require.EqualError(t, err, "failed to load filters: failed to select filters from db: db failed") + }) + t.Run("Noop if filter is not present", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + orm.On("SelectFilters", mock.Anything).Return(nil, nil).Once() + err := fs.UnregisterFilter(tests.Context(t), filterName) + require.NoError(t, err) + }) + t.Run("Returns error if fails to mark filter as deleted", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + const id int64 = 10 + orm.On("SelectFilters", mock.Anything).Return([]Filter{{ID: id, Name: filterName}}, nil).Once() + orm.On("MarkFilterDeleted", mock.Anything, id).Return(errors.New("db query failed")).Once() + err := fs.UnregisterFilter(tests.Context(t), filterName) + require.EqualError(t, err, "failed to mark filter deleted: db query failed") + }) + t.Run("Happy path", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + const filterName = "Filter" + const id int64 = 10 + orm.On("SelectFilters", mock.Anything).Return([]Filter{{ID: id, Name: filterName}}, nil).Once() + orm.On("MarkFilterDeleted", mock.Anything, id).Return(nil).Once() + err := fs.UnregisterFilter(tests.Context(t), filterName) + require.NoError(t, err) + require.Len(t, fs.filtersToDelete, 1) + require.Len(t, fs.filtersToBackfill, 0) + require.Len(t, fs.filtersByName, 0) + require.Len(t, fs.filtersByAddress, 0) + }) +} + +func TestFilters_PruneFilters(t *testing.T) { + lggr := logger.Sugared(logger.Test(t)) + t.Run("Happy path", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + toDelete := Filter{ + ID: 1, + Name: "To delete", + IsDeleted: true, + } + orm.On("SelectFilters", mock.Anything).Return([]Filter{ + toDelete, + { + ID: 2, + Name: "To keep", + }, + }, nil).Once() + orm.On("DeleteFilters", mock.Anything, map[int64]Filter{toDelete.ID: toDelete}).Return(nil).Once() + err := fs.PruneFilters(tests.Context(t)) + require.NoError(t, err) + require.Len(t, fs.filtersToDelete, 0) + }) + t.Run("If DB removal fails will add filters back into removal slice ", func(t *testing.T) { + orm := newMockORM(t) + fs := newFilters(lggr, orm) + toDelete := Filter{ + ID: 1, + Name: "To delete", + IsDeleted: true, + } + orm.On("SelectFilters", mock.Anything).Return([]Filter{ + toDelete, + { + ID: 2, + Name: "To keep", + }, + }, nil).Once() + newToDelete := Filter{ + ID: 3, + Name: "To delete 2", + } + orm.On("DeleteFilters", mock.Anything, map[int64]Filter{toDelete.ID: toDelete}).Return(errors.New("db failed")).Run(func(_ mock.Arguments) { + orm.On("MarkFilterDeleted", mock.Anything, newToDelete.ID).Return(nil).Once() + orm.On("InsertFilter", mock.Anything, mock.Anything).Return(newToDelete.ID, nil).Once() + require.NoError(t, fs.RegisterFilter(tests.Context(t), newToDelete)) + require.NoError(t, fs.UnregisterFilter(tests.Context(t), newToDelete.Name)) + }).Once() + err := fs.PruneFilters(tests.Context(t)) + require.EqualError(t, err, "failed to delete filters: db failed") + require.Equal(t, fs.filtersToDelete, map[int64]Filter{newToDelete.ID: newToDelete, toDelete.ID: toDelete}) + }) +} + +func TestFilters_MatchingFilters(t *testing.T) { + orm := newMockORM(t) + lggr := logger.Sugared(logger.Test(t)) + expectedFilter1 := Filter{ + ID: 1, + Name: "expectedFilter1", + Address: newRandomPublicKey(t), + EventSig: newRandomEventSignature(t), + } + expectedFilter2 := Filter{ + ID: 2, + Name: "expectedFilter2", + Address: expectedFilter1.Address, + EventSig: expectedFilter1.EventSig, + } + sameAddress := Filter{ + ID: 3, + Name: "sameAddressWrongEventSig", + Address: expectedFilter1.Address, + EventSig: newRandomEventSignature(t), + } + + sameEventSig := Filter{ + ID: 4, + Name: "wrongAddressSameEventSig", + Address: newRandomPublicKey(t), + EventSig: expectedFilter1.EventSig, + } + orm.On("SelectFilters", mock.Anything).Return([]Filter{expectedFilter1, expectedFilter2, sameAddress, sameEventSig}, nil).Once() + filters := newFilters(lggr, orm) + err := filters.LoadFilters(tests.Context(t)) + require.NoError(t, err) + matchingFilters := slices.Collect(filters.MatchingFilters(expectedFilter1.Address, expectedFilter1.EventSig)) + require.Len(t, matchingFilters, 2) + require.Contains(t, matchingFilters, expectedFilter1) + require.Contains(t, matchingFilters, expectedFilter2) + // if at least one key does not match - returns empty iterator + require.Empty(t, slices.Collect(filters.MatchingFilters(newRandomPublicKey(t), expectedFilter1.EventSig))) + require.Empty(t, slices.Collect(filters.MatchingFilters(expectedFilter1.Address, newRandomEventSignature(t)))) + require.Empty(t, slices.Collect(filters.MatchingFilters(newRandomPublicKey(t), newRandomEventSignature(t)))) +} + +func TestFilters_GetFiltersToBackfill(t *testing.T) { + orm := newMockORM(t) + lggr := logger.Sugared(logger.Test(t)) + backfilledFilter := Filter{ + ID: 1, + Name: "backfilled", + StartingBlock: 100, + IsBackfilled: true, + } + notBackfilled := Filter{ + ID: 2, + StartingBlock: 101, + Name: "notBackfilled", + } + orm.EXPECT().SelectFilters(mock.Anything).Return([]Filter{backfilledFilter, notBackfilled}, nil).Once() + filters := newFilters(lggr, orm) + err := filters.LoadFilters(tests.Context(t)) + require.NoError(t, err) + // filters that were not backfilled are properly identified on load + ensureInQueue := func(expectedFilters ...Filter) { + filtersToBackfill := filters.GetFiltersToBackfill() + require.Len(t, filtersToBackfill, len(expectedFilters)) + for _, expectedFilter := range expectedFilters { + require.Contains(t, filtersToBackfill, expectedFilter) + } + } + ensureInQueue(notBackfilled) + // filter remains in queue if failed to mark as backfilled + orm.EXPECT().MarkFilterBackfilled(mock.Anything, notBackfilled.ID).Return(errors.New("db call failed")).Once() + err = filters.MarkFilterBackfilled(tests.Context(t), notBackfilled.ID) + require.Error(t, err) + ensureInQueue(notBackfilled) + // filter is removed from queue, if marked as backfilled + orm.EXPECT().MarkFilterBackfilled(mock.Anything, notBackfilled.ID).Return(nil).Once() + err = filters.MarkFilterBackfilled(tests.Context(t), notBackfilled.ID) + require.NoError(t, err) + require.Empty(t, filters.GetFiltersToBackfill()) + // re adding identical filter won't trigger backfill + orm.EXPECT().InsertFilter(mock.Anything, mock.Anything).Return(backfilledFilter.ID, nil).Once() + require.NoError(t, filters.RegisterFilter(tests.Context(t), backfilledFilter)) + orm.EXPECT().InsertFilter(mock.Anything, mock.Anything).Return(notBackfilled.ID, nil).Once() + require.NoError(t, filters.RegisterFilter(tests.Context(t), notBackfilled)) + require.Empty(t, filters.GetFiltersToBackfill()) + // older StartingBlock trigger backfill + notBackfilled.StartingBlock = notBackfilled.StartingBlock - 1 + orm.EXPECT().InsertFilter(mock.Anything, mock.Anything).Return(notBackfilled.ID, nil).Once() + require.NoError(t, filters.RegisterFilter(tests.Context(t), notBackfilled)) + ensureInQueue(notBackfilled) + // new filter is always added to the queue + newFilter := Filter{Name: "new filter", ID: 3} + orm.EXPECT().InsertFilter(mock.Anything, newFilter).Return(newFilter.ID, nil).Once() + require.NoError(t, filters.RegisterFilter(tests.Context(t), newFilter)) + ensureInQueue(notBackfilled, newFilter) +} diff --git a/pkg/solana/logpoller/log_poller.go b/pkg/solana/logpoller/log_poller.go new file mode 100644 index 000000000..4c386693e --- /dev/null +++ b/pkg/solana/logpoller/log_poller.go @@ -0,0 +1,145 @@ +package logpoller + +import ( + "context" + "errors" + "time" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/services" +) + +var ( + ErrFilterNameConflict = errors.New("filter with such name already exists") +) + +type ORM interface { + InsertFilter(ctx context.Context, filter Filter) (id int64, err error) + SelectFilters(ctx context.Context) ([]Filter, error) + DeleteFilters(ctx context.Context, filters map[int64]Filter) error + MarkFilterDeleted(ctx context.Context, id int64) (err error) + MarkFilterBackfilled(ctx context.Context, id int64) (err error) +} + +type LogPoller struct { + services.Service + eng *services.Engine + + lggr logger.SugaredLogger + orm ORM + + filters *filters +} + +func New(lggr logger.SugaredLogger, orm ORM) *LogPoller { + lggr = logger.Sugared(logger.Named(lggr, "LogPoller")) + lp := &LogPoller{ + orm: orm, + lggr: lggr, + filters: newFilters(lggr, orm), + } + + lp.Service, lp.eng = services.Config{ + Name: "LogPollerService", + Start: lp.start, + }.NewServiceEngine(lggr) + lp.lggr = lp.eng.SugaredLogger + return lp +} + +func (lp *LogPoller) start(context.Context) error { + lp.eng.Go(lp.run) + lp.eng.Go(lp.backgroundWorkerRun) + return nil +} + +// RegisterFilter - refer to filters.RegisterFilter for details. +func (lp *LogPoller) RegisterFilter(ctx context.Context, filter Filter) error { + ctx, cancel := lp.eng.Ctx(ctx) + defer cancel() + return lp.filters.RegisterFilter(ctx, filter) +} + +// UnregisterFilter refer to filters.UnregisterFilter for details +func (lp *LogPoller) UnregisterFilter(ctx context.Context, name string) error { + ctx, cancel := lp.eng.Ctx(ctx) + defer cancel() + return lp.filters.UnregisterFilter(ctx, name) +} + +func (lp *LogPoller) loadFilters(ctx context.Context) error { + retryTicker := services.TickerConfig{Initial: 0, JitterPct: services.DefaultJitter}.NewTicker(time.Second) + defer retryTicker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-retryTicker.C: + } + err := lp.filters.LoadFilters(ctx) + if err != nil { + lp.lggr.Errorw("Failed loading filters in init logpoller loop, retrying later", "err", err) + continue + } + + return nil + } +} + +func (lp *LogPoller) run(ctx context.Context) { + err := lp.loadFilters(ctx) + if err != nil { + lp.lggr.Warnw("Failed loading filters", "err", err) + return + } + + var blocks chan struct { + BlockNumber int64 + Logs any // to be defined + } + + for { + select { + case <-ctx.Done(): + return + case block := <-blocks: + filtersToBackfill := lp.filters.GetFiltersToBackfill() + + // TODO: NONEVM-916 parse, filters and persist logs + // NOTE: removal of filters occurs in the separate goroutine, so there is a chance that upon insert + // of log corresponding filter won't be present in the db. Ensure to refilter and retry on insert error + for i := range filtersToBackfill { + filter := filtersToBackfill[i] + lp.eng.Go(func(ctx context.Context) { + lp.startFilterBackfill(ctx, filter, block.BlockNumber) + }) + } + } + } +} + +func (lp *LogPoller) backgroundWorkerRun(ctx context.Context) { + pruneFilters := services.NewTicker(time.Minute) + defer pruneFilters.Stop() + for { + select { + case <-ctx.Done(): + return + case <-pruneFilters.C: + err := lp.filters.PruneFilters(ctx) + if err != nil { + lp.lggr.Errorw("Failed to prune filters", "err", err) + } + } + } +} + +func (lp *LogPoller) startFilterBackfill(ctx context.Context, filter Filter, toBlock int64) { + // TODO: NONEVM-916 start backfill + lp.lggr.Debugw("Starting filter backfill", "filter", filter) + err := lp.filters.MarkFilterBackfilled(ctx, filter.ID) + if err != nil { + lp.lggr.Errorw("Failed to mark filter backfill", "filter", filter, "err", err) + } +} diff --git a/pkg/solana/logpoller/mock_orm.go b/pkg/solana/logpoller/mock_orm.go new file mode 100644 index 000000000..0595ea718 --- /dev/null +++ b/pkg/solana/logpoller/mock_orm.go @@ -0,0 +1,292 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package logpoller + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// mockORM is an autogenerated mock type for the ORM type +type mockORM struct { + mock.Mock +} + +type mockORM_Expecter struct { + mock *mock.Mock +} + +func (_m *mockORM) EXPECT() *mockORM_Expecter { + return &mockORM_Expecter{mock: &_m.Mock} +} + +// DeleteFilters provides a mock function with given fields: ctx, filters +func (_m *mockORM) DeleteFilters(ctx context.Context, filters map[int64]Filter) error { + ret := _m.Called(ctx, filters) + + if len(ret) == 0 { + panic("no return value specified for DeleteFilters") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, map[int64]Filter) error); ok { + r0 = rf(ctx, filters) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockORM_DeleteFilters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFilters' +type mockORM_DeleteFilters_Call struct { + *mock.Call +} + +// DeleteFilters is a helper method to define mock.On call +// - ctx context.Context +// - filters map[int64]Filter +func (_e *mockORM_Expecter) DeleteFilters(ctx interface{}, filters interface{}) *mockORM_DeleteFilters_Call { + return &mockORM_DeleteFilters_Call{Call: _e.mock.On("DeleteFilters", ctx, filters)} +} + +func (_c *mockORM_DeleteFilters_Call) Run(run func(ctx context.Context, filters map[int64]Filter)) *mockORM_DeleteFilters_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(map[int64]Filter)) + }) + return _c +} + +func (_c *mockORM_DeleteFilters_Call) Return(_a0 error) *mockORM_DeleteFilters_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *mockORM_DeleteFilters_Call) RunAndReturn(run func(context.Context, map[int64]Filter) error) *mockORM_DeleteFilters_Call { + _c.Call.Return(run) + return _c +} + +// InsertFilter provides a mock function with given fields: ctx, filter +func (_m *mockORM) InsertFilter(ctx context.Context, filter Filter) (int64, error) { + ret := _m.Called(ctx, filter) + + if len(ret) == 0 { + panic("no return value specified for InsertFilter") + } + + var r0 int64 + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, Filter) (int64, error)); ok { + return rf(ctx, filter) + } + if rf, ok := ret.Get(0).(func(context.Context, Filter) int64); ok { + r0 = rf(ctx, filter) + } else { + r0 = ret.Get(0).(int64) + } + + if rf, ok := ret.Get(1).(func(context.Context, Filter) error); ok { + r1 = rf(ctx, filter) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockORM_InsertFilter_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InsertFilter' +type mockORM_InsertFilter_Call struct { + *mock.Call +} + +// InsertFilter is a helper method to define mock.On call +// - ctx context.Context +// - filter Filter +func (_e *mockORM_Expecter) InsertFilter(ctx interface{}, filter interface{}) *mockORM_InsertFilter_Call { + return &mockORM_InsertFilter_Call{Call: _e.mock.On("InsertFilter", ctx, filter)} +} + +func (_c *mockORM_InsertFilter_Call) Run(run func(ctx context.Context, filter Filter)) *mockORM_InsertFilter_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(Filter)) + }) + return _c +} + +func (_c *mockORM_InsertFilter_Call) Return(id int64, err error) *mockORM_InsertFilter_Call { + _c.Call.Return(id, err) + return _c +} + +func (_c *mockORM_InsertFilter_Call) RunAndReturn(run func(context.Context, Filter) (int64, error)) *mockORM_InsertFilter_Call { + _c.Call.Return(run) + return _c +} + +// MarkFilterBackfilled provides a mock function with given fields: ctx, id +func (_m *mockORM) MarkFilterBackfilled(ctx context.Context, id int64) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for MarkFilterBackfilled") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockORM_MarkFilterBackfilled_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkFilterBackfilled' +type mockORM_MarkFilterBackfilled_Call struct { + *mock.Call +} + +// MarkFilterBackfilled is a helper method to define mock.On call +// - ctx context.Context +// - id int64 +func (_e *mockORM_Expecter) MarkFilterBackfilled(ctx interface{}, id interface{}) *mockORM_MarkFilterBackfilled_Call { + return &mockORM_MarkFilterBackfilled_Call{Call: _e.mock.On("MarkFilterBackfilled", ctx, id)} +} + +func (_c *mockORM_MarkFilterBackfilled_Call) Run(run func(ctx context.Context, id int64)) *mockORM_MarkFilterBackfilled_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64)) + }) + return _c +} + +func (_c *mockORM_MarkFilterBackfilled_Call) Return(err error) *mockORM_MarkFilterBackfilled_Call { + _c.Call.Return(err) + return _c +} + +func (_c *mockORM_MarkFilterBackfilled_Call) RunAndReturn(run func(context.Context, int64) error) *mockORM_MarkFilterBackfilled_Call { + _c.Call.Return(run) + return _c +} + +// MarkFilterDeleted provides a mock function with given fields: ctx, id +func (_m *mockORM) MarkFilterDeleted(ctx context.Context, id int64) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for MarkFilterDeleted") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// mockORM_MarkFilterDeleted_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkFilterDeleted' +type mockORM_MarkFilterDeleted_Call struct { + *mock.Call +} + +// MarkFilterDeleted is a helper method to define mock.On call +// - ctx context.Context +// - id int64 +func (_e *mockORM_Expecter) MarkFilterDeleted(ctx interface{}, id interface{}) *mockORM_MarkFilterDeleted_Call { + return &mockORM_MarkFilterDeleted_Call{Call: _e.mock.On("MarkFilterDeleted", ctx, id)} +} + +func (_c *mockORM_MarkFilterDeleted_Call) Run(run func(ctx context.Context, id int64)) *mockORM_MarkFilterDeleted_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(int64)) + }) + return _c +} + +func (_c *mockORM_MarkFilterDeleted_Call) Return(err error) *mockORM_MarkFilterDeleted_Call { + _c.Call.Return(err) + return _c +} + +func (_c *mockORM_MarkFilterDeleted_Call) RunAndReturn(run func(context.Context, int64) error) *mockORM_MarkFilterDeleted_Call { + _c.Call.Return(run) + return _c +} + +// SelectFilters provides a mock function with given fields: ctx +func (_m *mockORM) SelectFilters(ctx context.Context) ([]Filter, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for SelectFilters") + } + + var r0 []Filter + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]Filter, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) []Filter); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]Filter) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// mockORM_SelectFilters_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectFilters' +type mockORM_SelectFilters_Call struct { + *mock.Call +} + +// SelectFilters is a helper method to define mock.On call +// - ctx context.Context +func (_e *mockORM_Expecter) SelectFilters(ctx interface{}) *mockORM_SelectFilters_Call { + return &mockORM_SelectFilters_Call{Call: _e.mock.On("SelectFilters", ctx)} +} + +func (_c *mockORM_SelectFilters_Call) Run(run func(ctx context.Context)) *mockORM_SelectFilters_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *mockORM_SelectFilters_Call) Return(_a0 []Filter, _a1 error) *mockORM_SelectFilters_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *mockORM_SelectFilters_Call) RunAndReturn(run func(context.Context) ([]Filter, error)) *mockORM_SelectFilters_Call { + _c.Call.Return(run) + return _c +} + +// newMockORM creates a new instance of mockORM. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newMockORM(t interface { + mock.TestingT + Cleanup(func()) +}) *mockORM { + mock := &mockORM{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/solana/logpoller/models.go b/pkg/solana/logpoller/models.go new file mode 100644 index 000000000..0be5b4874 --- /dev/null +++ b/pkg/solana/logpoller/models.go @@ -0,0 +1,44 @@ +package logpoller + +import ( + "time" + + "github.com/lib/pq" +) + +type Filter struct { + ID int64 // only for internal usage. Values set externally are ignored. + Name string + Address PublicKey + EventName string + EventSig EventSignature + StartingBlock int64 + EventIDL string + SubkeyPaths SubkeyPaths + Retention time.Duration + MaxLogsKept int64 + IsDeleted bool // only for internal usage. Values set externally are ignored. + IsBackfilled bool // only for internal usage. Values set externally are ignored. +} + +func (f Filter) MatchSameLogs(other Filter) bool { + return f.Address == other.Address && f.EventSig == other.EventSig && f.EventIDL == other.EventIDL && f.SubkeyPaths.Equal(other.SubkeyPaths) +} + +type Log struct { + ID int64 + FilterID int64 + ChainID string + LogIndex int64 + BlockHash Hash + BlockNumber int64 + BlockTimestamp time.Time + Address PublicKey + EventSig EventSignature + SubkeyValues pq.ByteaArray + TxHash Signature + Data []byte + CreatedAt time.Time + ExpiresAt *time.Time + SequenceNum int64 +} diff --git a/pkg/solana/logpoller/orm.go b/pkg/solana/logpoller/orm.go new file mode 100644 index 000000000..4ca6fee5b --- /dev/null +++ b/pkg/solana/logpoller/orm.go @@ -0,0 +1,202 @@ +package logpoller + +import ( + "context" + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" +) + +var _ ORM = (*DSORM)(nil) + +type DSORM struct { + chainID string + ds sqlutil.DataSource + lggr logger.Logger +} + +// NewORM creates an DSORM scoped to chainID. +func NewORM(chainID string, ds sqlutil.DataSource, lggr logger.Logger) *DSORM { + return &DSORM{ + chainID: chainID, + ds: ds, + lggr: lggr, + } +} + +func (o *DSORM) Transact(ctx context.Context, fn func(*DSORM) error) (err error) { + return sqlutil.Transact(ctx, o.new, o.ds, nil, fn) +} + +// new returns a NewORM like o, but backed by ds. +func (o *DSORM) new(ds sqlutil.DataSource) *DSORM { return NewORM(o.chainID, ds, o.lggr) } + +// InsertFilter is idempotent. +// +// Each address/event pair must have a unique job id, so it may be removed when the job is deleted. +// Returns ID for updated or newly inserted filter. +func (o *DSORM) InsertFilter(ctx context.Context, filter Filter) (id int64, err error) { + args, err := newQueryArgs(o.chainID). + withField("name", filter.Name). + withRetention(filter.Retention). + withMaxLogsKept(filter.MaxLogsKept). + withName(filter.Name). + withAddress(filter.Address). + withEventName(filter.EventName). + withEventSig(filter.EventSig). + withStartingBlock(filter.StartingBlock). + withEventIDL(filter.EventIDL). + withSubkeyPaths(filter.SubkeyPaths). + withIsBackfilled(filter.IsBackfilled). + toArgs() + if err != nil { + return 0, err + } + + // '::' has to be escaped in the query string + // https://github.com/jmoiron/sqlx/issues/91, https://github.com/jmoiron/sqlx/issues/428 + query := ` + INSERT INTO solana.log_poller_filters + (chain_id, name, address, event_name, event_sig, starting_block, event_idl, subkey_paths, retention, max_logs_kept, is_backfilled) + VALUES (:chain_id, :name, :address, :event_name, :event_sig, :starting_block, :event_idl, :subkey_paths, :retention, :max_logs_kept, :is_backfilled) + ON CONFLICT (chain_id, name) WHERE NOT is_deleted DO UPDATE SET + event_name = EXCLUDED.event_name, + starting_block = EXCLUDED.starting_block, + retention = EXCLUDED.retention, + max_logs_kept = EXCLUDED.max_logs_kept, + is_backfilled = EXCLUDED.is_backfilled + RETURNING id;` + + query, sqlArgs, err := o.ds.BindNamed(query, args) + if err != nil { + return 0, err + } + if err = o.ds.GetContext(ctx, &id, query, sqlArgs...); err != nil { + return 0, err + } + return id, nil +} + +// GetFilterByID returns filter by ID +func (o *DSORM) GetFilterByID(ctx context.Context, id int64) (Filter, error) { + query := filtersQuery("WHERE id = $1") + var result Filter + err := o.ds.GetContext(ctx, &result, query, id) + return result, err +} + +func (o *DSORM) MarkFilterDeleted(ctx context.Context, id int64) (err error) { + query := `UPDATE solana.log_poller_filters SET is_deleted = true WHERE id = $1` + _, err = o.ds.ExecContext(ctx, query, id) + return err +} + +func (o *DSORM) MarkFilterBackfilled(ctx context.Context, id int64) (err error) { + query := `UPDATE solana.log_poller_filters SET is_backfilled = true WHERE id = $1` + _, err = o.ds.ExecContext(ctx, query, id) + return err +} + +func (o *DSORM) DeleteFilter(ctx context.Context, id int64) (err error) { + query := `DELETE FROM solana.log_poller_filters WHERE id = $1` + _, err = o.ds.ExecContext(ctx, query, id) + return err +} + +func (o *DSORM) DeleteFilters(ctx context.Context, filters map[int64]Filter) error { + for _, filter := range filters { + err := o.DeleteFilter(ctx, filter.ID) + if err != nil { + return fmt.Errorf("error deleting filter %s (%d): %w", filter.Name, filter.ID, err) + } + } + + return nil +} + +func (o *DSORM) SelectFilters(ctx context.Context) ([]Filter, error) { + query := filtersQuery("WHERE chain_id = $1") + var filters []Filter + err := o.ds.SelectContext(ctx, &filters, query, o.chainID) + return filters, err +} + +// InsertLogs is idempotent to support replays. +func (o *DSORM) InsertLogs(ctx context.Context, logs []Log) error { + if err := o.validateLogs(logs); err != nil { + return err + } + return o.Transact(ctx, func(orm *DSORM) error { + return orm.insertLogsWithinTx(ctx, logs, orm.ds) + }) +} + +func (o *DSORM) insertLogsWithinTx(ctx context.Context, logs []Log, tx sqlutil.DataSource) error { + batchInsertSize := 4000 + for i := 0; i < len(logs); i += batchInsertSize { + start, end := i, i+batchInsertSize + if end > len(logs) { + end = len(logs) + } + + query := `INSERT INTO solana.logs + (filter_id, chain_id, log_index, block_hash, block_number, block_timestamp, address, event_sig, subkey_values, tx_hash, data, created_at, expires_at, sequence_num) + VALUES + (:filter_id, :chain_id, :log_index, :block_hash, :block_number, :block_timestamp, :address, :event_sig, :subkey_values, :tx_hash, :data, NOW(), :expires_at, :sequence_num) + ON CONFLICT DO NOTHING` + + _, err := tx.NamedExecContext(ctx, query, logs[start:end]) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) && batchInsertSize > 500 { + // In case of DB timeouts, try to insert again with a smaller batch upto a limit + batchInsertSize /= 2 + i -= batchInsertSize // counteract +=batchInsertSize on next loop iteration + continue + } + return err + } + } + return nil +} + +func (o *DSORM) validateLogs(logs []Log) error { + for _, log := range logs { + if o.chainID != log.ChainID { + return fmt.Errorf("invalid chainID in log got %v want %v", log.ChainID, o.chainID) + } + } + return nil +} + +// SelectLogs finds the logs in a given block range. +func (o *DSORM) SelectLogs(ctx context.Context, start, end int64, address PublicKey, eventSig EventSignature) ([]Log, error) { + args, err := newQueryArgsForEvent(o.chainID, address, eventSig). + withStartBlock(start). + withEndBlock(end). + toArgs() + if err != nil { + return nil, err + } + + query := logsQuery(` + WHERE chain_id = :chain_id + AND address = :address + AND event_sig = :event_sig + AND block_number >= :start_block + AND block_number <= :end_block + ORDER BY block_number, log_index`) + + var logs []Log + query, sqlArgs, err := o.ds.BindNamed(query, args) + if err != nil { + return nil, err + } + + err = o.ds.SelectContext(ctx, &logs, query, sqlArgs...) + if err != nil { + return nil, err + } + return logs, nil +} diff --git a/pkg/solana/logpoller/orm_test.go b/pkg/solana/logpoller/orm_test.go new file mode 100644 index 000000000..53512d696 --- /dev/null +++ b/pkg/solana/logpoller/orm_test.go @@ -0,0 +1,243 @@ +//go:build db_tests + +package logpoller + +import ( + "testing" + "time" + + "github.com/gagliardetto/solana-go" + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + _ "github.com/jackc/pgx/v4/stdlib" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/pg" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" +) + +// NOTE: at the moment it's not possible to run all db tests at once. This issue will be addressed separately + +func TestLogPollerFilters(t *testing.T) { + lggr := logger.Test(t) + + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + t.Run("Ensure all fields are readable/writable", func(t *testing.T) { + filters := []Filter{ + { + Name: "happy path", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: EventSignature{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{{"a", "b"}, {"c"}}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "empty sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: EventSignature{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: SubkeyPaths([][]string{}), + Retention: 1000, + MaxLogsKept: 3, + }, + { + Name: "nil sub key paths", + Address: PublicKey(pubKey), + EventName: "event", + EventSig: EventSignature{1, 2, 3}, + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: nil, + Retention: 1000, + MaxLogsKept: 3, + }, + } + + for _, filter := range filters { + t.Run("Read/write filter: "+filter.Name, func(t *testing.T) { + ctx := tests.Context(t) + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + id, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.ID = id + dbFilter, err := orm.GetFilterByID(ctx, id) + require.NoError(t, err) + require.Equal(t, filter, dbFilter) + }) + } + }) + t.Run("Updates non primary fields if name and chainID is not unique", func(t *testing.T) { + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + filter := newRandomFilter(t) + ctx := tests.Context(t) + id, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + filter.EventName = uuid.NewString() + filter.StartingBlock++ + filter.Retention++ + filter.MaxLogsKept++ + id2, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + require.Equal(t, id, id2) + dbFilter, err := orm.GetFilterByID(ctx, id) + require.NoError(t, err) + filter.ID = id + require.Equal(t, filter, dbFilter) + }) + t.Run("Allows reuse name of a filter marked as deleted", func(t *testing.T) { + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + filter := newRandomFilter(t) + ctx := tests.Context(t) + filterID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + // mark deleted + err = orm.MarkFilterDeleted(ctx, filterID) + require.NoError(t, err) + // ensure marked as deleted + dbFilter, err := orm.GetFilterByID(ctx, filterID) + require.NoError(t, err) + require.True(t, dbFilter.IsDeleted, "expected to be deleted") + newFilterID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + require.NotEqual(t, newFilterID, filterID, "expected db to generate new filter as we can not be sure that new one matches the same logs") + }) + t.Run("Allows reuse name for a filter with different chainID", func(t *testing.T) { + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm1 := NewORM(uuid.NewString(), dbx, lggr) + orm2 := NewORM(uuid.NewString(), dbx, lggr) + filter := newRandomFilter(t) + ctx := tests.Context(t) + filterID1, err := orm1.InsertFilter(ctx, filter) + require.NoError(t, err) + filterID2, err := orm2.InsertFilter(ctx, filter) + require.NoError(t, err) + require.NotEqual(t, filterID1, filterID2) + }) + t.Run("Deletes log on parent filter deletion", func(t *testing.T) { + dbx := pg.NewTestDB(t, pg.TestURL(t)) + chainID := uuid.NewString() + orm := NewORM(chainID, dbx, lggr) + filter := newRandomFilter(t) + ctx := tests.Context(t) + filterID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + log := newRandomLog(t, filterID, chainID) + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) + logs, err := orm.SelectLogs(ctx, 0, log.BlockNumber, log.Address, log.EventSig) + require.NoError(t, err) + require.Len(t, logs, 1) + err = orm.MarkFilterDeleted(ctx, filterID) + require.NoError(t, err) + // logs are expected to be present in db even if filter was marked as deleted + logs, err = orm.SelectLogs(ctx, 0, log.BlockNumber, log.Address, log.EventSig) + require.NoError(t, err) + require.Len(t, logs, 1) + err = orm.DeleteFilter(ctx, filterID) + require.NoError(t, err) + logs, err = orm.SelectLogs(ctx, 0, log.BlockNumber, log.Address, log.EventSig) + require.NoError(t, err) + require.Len(t, logs, 0) + }) + t.Run("MarkBackfilled updated corresponding filed", func(t *testing.T) { + dbx := pg.NewTestDB(t, pg.TestURL(t)) + chainID := uuid.NewString() + orm := NewORM(chainID, dbx, lggr) + + filter := newRandomFilter(t) + ctx := tests.Context(t) + filter.IsBackfilled = true + filterID, err := orm.InsertFilter(ctx, filter) + require.NoError(t, err) + ensureIsBackfilled := func(expectedIsBackfilled bool) { + filter, err = orm.GetFilterByID(ctx, filterID) + require.NoError(t, err) + require.Equal(t, expectedIsBackfilled, filter.IsBackfilled) + } + ensureIsBackfilled(true) + // insert overrides + filter.IsBackfilled = false + _, err = orm.InsertFilter(ctx, filter) + require.NoError(t, err) + ensureIsBackfilled(false) + // mark changes value to true + err = orm.MarkFilterBackfilled(ctx, filterID) + require.NoError(t, err) + ensureIsBackfilled(true) + }) +} + +func TestLogPollerLogs(t *testing.T) { + lggr := logger.Test(t) + chainID := uuid.NewString() + dbx := pg.NewTestDB(t, pg.TestURL(t)) + orm := NewORM(chainID, dbx, lggr) + + ctx := tests.Context(t) + // create filter as it's required for a log + filterID, err := orm.InsertFilter(ctx, newRandomFilter(t)) + require.NoError(t, err) + log := newRandomLog(t, filterID, chainID) + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) + // insert of the same Log should not produce two instances + err = orm.InsertLogs(ctx, []Log{log}) + require.NoError(t, err) + dbLogs, err := orm.SelectLogs(ctx, 0, 100, log.Address, log.EventSig) + require.NoError(t, err) + require.Len(t, dbLogs, 1) + log.ID = dbLogs[0].ID + log.CreatedAt = dbLogs[0].CreatedAt + require.Equal(t, log, dbLogs[0]) +} + +func newRandomFilter(t *testing.T) Filter { + return Filter{ + Name: uuid.NewString(), + Address: newRandomPublicKey(t), + EventName: "event", + EventSig: newRandomEventSignature(t), + StartingBlock: 1, + EventIDL: "{}", + SubkeyPaths: [][]string{{"a", "b"}, {"c"}}, + Retention: 1000, + MaxLogsKept: 3, + } +} + +func newRandomLog(t *testing.T, filterID int64, chainID string) Log { + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + data := []byte("solana is fun") + signature, err := privateKey.Sign(data) + require.NoError(t, err) + return Log{ + FilterID: filterID, + ChainID: chainID, + LogIndex: 1, + BlockHash: Hash(pubKey), + BlockNumber: 10, + BlockTimestamp: time.Unix(1731590113, 0), + Address: PublicKey(pubKey), + EventSig: EventSignature{3, 2, 1}, + SubkeyValues: [][]byte{{3, 2, 1}, {1}, {1, 2}, pubKey.Bytes()}, + TxHash: Signature(signature), + Data: data, + } +} diff --git a/pkg/solana/logpoller/parser.go b/pkg/solana/logpoller/parser.go new file mode 100644 index 000000000..be97a4f48 --- /dev/null +++ b/pkg/solana/logpoller/parser.go @@ -0,0 +1,6 @@ +package logpoller + +var ( + logsFields = [...]string{"id", "filter_id", "chain_id", "log_index", "block_hash", "block_number", "block_timestamp", "address", "event_sig", "subkey_values", "tx_hash", "data", "created_at", "expires_at", "sequence_num"} + filterFields = [...]string{"id", "name", "address", "event_name", "event_sig", "starting_block", "event_idl", "subkey_paths", "retention", "max_logs_kept", "is_deleted", "is_backfilled"} +) diff --git a/pkg/solana/logpoller/query.go b/pkg/solana/logpoller/query.go new file mode 100644 index 000000000..65a792449 --- /dev/null +++ b/pkg/solana/logpoller/query.go @@ -0,0 +1,135 @@ +package logpoller + +import ( + "errors" + "fmt" + "strings" + "time" +) + +// queryArgs is a helper for building the arguments to a postgres query created by DSORM +// Besides the convenience methods, it also keeps track of arguments validation and sanitization. +type queryArgs struct { + args map[string]any + idxLookup map[string]uint8 + err []error +} + +func newQueryArgs(chainID string) *queryArgs { + return &queryArgs{ + args: map[string]any{ + "chain_id": chainID, + }, + idxLookup: make(map[string]uint8), + err: []error{}, + } +} + +func (q *queryArgs) withField(fieldName string, value any) *queryArgs { + _, args := q.withIndexableField(fieldName, value, false) + + return args +} + +func (q *queryArgs) withIndexableField(fieldName string, value any, addIndex bool) (string, *queryArgs) { + if addIndex { + idx := q.nextIdx(fieldName) + idxName := fmt.Sprintf("%s_%d", fieldName, idx) + + q.idxLookup[fieldName] = idx + fieldName = idxName + } + + q.args[fieldName] = value + + return fieldName, q +} + +func (q *queryArgs) nextIdx(baseFieldName string) uint8 { + idx, ok := q.idxLookup[baseFieldName] + if !ok { + return 0 + } + + return idx + 1 +} + +// withName sets the Name field in queryArgs. +func (q *queryArgs) withName(name string) *queryArgs { + return q.withField("name", name) +} + +// withAddress sets the Address field in queryArgs. +func (q *queryArgs) withAddress(address PublicKey) *queryArgs { + return q.withField("address", address) +} + +// withEventName sets the EventName field in queryArgs. +func (q *queryArgs) withEventName(eventName string) *queryArgs { + return q.withField("event_name", eventName) +} + +// withEventSig sets the EventSig field in queryArgs. +func (q *queryArgs) withEventSig(eventSig EventSignature) *queryArgs { + return q.withField("event_sig", eventSig) +} + +// withStartingBlock sets the StartingBlock field in queryArgs. +func (q *queryArgs) withStartingBlock(startingBlock int64) *queryArgs { + return q.withField("starting_block", startingBlock) +} + +// withEventIDL sets the EventIDL field in queryArgs. +func (q *queryArgs) withEventIDL(eventIDL string) *queryArgs { + return q.withField("event_idl", eventIDL) +} + +// withSubkeyPaths sets the SubkeyPaths field in queryArgs. +func (q *queryArgs) withSubkeyPaths(subkeyPaths [][]string) *queryArgs { + return q.withField("subkey_paths", subkeyPaths) +} + +// withRetention sets the Retention field in queryArgs. +func (q *queryArgs) withRetention(retention time.Duration) *queryArgs { + return q.withField("retention", retention) +} + +// withMaxLogsKept sets the MaxLogsKept field in queryArgs. +func (q *queryArgs) withMaxLogsKept(maxLogsKept int64) *queryArgs { + return q.withField("max_logs_kept", maxLogsKept) +} + +func newQueryArgsForEvent(chainID string, address PublicKey, eventSig EventSignature) *queryArgs { + return newQueryArgs(chainID). + withAddress(address). + withEventSig(eventSig) +} + +func (q *queryArgs) withStartBlock(startBlock int64) *queryArgs { + return q.withField("start_block", startBlock) +} + +func (q *queryArgs) withEndBlock(endBlock int64) *queryArgs { + return q.withField("end_block", endBlock) +} + +// withIsBackfilled sets the isBackfilled field in queryArgs. +func (q *queryArgs) withIsBackfilled(isBackfilled bool) *queryArgs { + return q.withField("is_backfilled", isBackfilled) +} + +func logsQuery(clause string) string { + return fmt.Sprintf(`SELECT %s FROM solana.logs %s`, strings.Join(logsFields[:], ", "), clause) +} + +func filtersQuery(clause string) string { + return fmt.Sprintf(`SELECT %s FROM solana.log_poller_filters %s`, strings.Join(filterFields[:], ", "), clause) +} + +func (q *queryArgs) toArgs() (map[string]any, error) { + if len(q.err) > 0 { + return nil, errors.Join(q.err...) + } + + return q.args, nil +} diff --git a/pkg/solana/logpoller/types.go b/pkg/solana/logpoller/types.go new file mode 100644 index 000000000..143c28898 --- /dev/null +++ b/pkg/solana/logpoller/types.go @@ -0,0 +1,117 @@ +package logpoller + +import ( + "database/sql/driver" + "encoding/json" + "fmt" + "slices" + + "github.com/gagliardetto/solana-go" +) + +type PublicKey solana.PublicKey + +// Scan implements Scanner for database/sql. +func (k *PublicKey) Scan(src interface{}) error { + return scanFixedLengthArray("PublicKey", solana.PublicKeyLength, src, k[:]) +} + +// Value implements valuer for database/sql. +func (k PublicKey) Value() (driver.Value, error) { + return k[:], nil +} + +func (k PublicKey) ToSolana() solana.PublicKey { + return solana.PublicKey(k) +} + +type Hash solana.Hash + +// Scan implements Scanner for database/sql. +func (h *Hash) Scan(src interface{}) error { + return scanFixedLengthArray("Hash", solana.PublicKeyLength, src, h[:]) +} + +// Value implements valuer for database/sql. +func (h Hash) Value() (driver.Value, error) { + return h[:], nil +} + +func (h Hash) ToSolana() solana.Hash { + return solana.Hash(h) +} + +type Signature solana.Signature + +// Scan implements Scanner for database/sql. +func (s *Signature) Scan(src interface{}) error { + return scanFixedLengthArray("Signature", solana.SignatureLength, src, s[:]) +} + +// Value implements valuer for database/sql. +func (s Signature) Value() (driver.Value, error) { + return s[:], nil +} + +func (s Signature) ToSolana() solana.Signature { + return solana.Signature(s) +} + +func scanFixedLengthArray(name string, maxLength int, src interface{}, dest []byte) error { + srcB, ok := src.([]byte) + if !ok { + return fmt.Errorf("can't scan %T into %s", src, name) + } + if len(srcB) != maxLength { + return fmt.Errorf("can't scan []byte of len %d into %s, want %d", len(srcB), name, maxLength) + } + copy(dest, srcB) + return nil +} + +type SubkeyPaths [][]string + +func (p SubkeyPaths) Value() (driver.Value, error) { + return json.Marshal([][]string(p)) +} + +func (p *SubkeyPaths) Scan(src interface{}) error { + var bSrc []byte + switch src := src.(type) { + case string: + bSrc = []byte(src) + case []byte: + bSrc = src + default: + return fmt.Errorf("can't scan %T into SubkeyPaths", src) + } + + if len(bSrc) == 0 || string(bSrc) == "null" { + return nil + } + + err := json.Unmarshal(bSrc, p) + if err != nil { + return fmt.Errorf("failed to scan %v into SubkeyPaths: %w", string(bSrc), err) + } + + return nil +} + +func (p SubkeyPaths) Equal(o SubkeyPaths) bool { + return slices.EqualFunc(p, o, slices.Equal) +} + +const EventSignatureLength = 8 + +type EventSignature [EventSignatureLength]byte + +// Scan implements Scanner for database/sql. +func (s *EventSignature) Scan(src interface{}) error { + return scanFixedLengthArray("EventSignature", EventSignatureLength, src, s[:]) +} + +// Value implements valuer for database/sql. +func (s EventSignature) Value() (driver.Value, error) { + return s[:], nil +} diff --git a/pkg/solana/logpoller/types_test.go b/pkg/solana/logpoller/types_test.go new file mode 100644 index 000000000..263c22bab --- /dev/null +++ b/pkg/solana/logpoller/types_test.go @@ -0,0 +1,20 @@ +package logpoller + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" +) + +func newRandomPublicKey(t *testing.T) PublicKey { + privateKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + pubKey := privateKey.PublicKey() + return PublicKey(pubKey) +} + +func newRandomEventSignature(t *testing.T) EventSignature { + pubKey := newRandomPublicKey(t) + return EventSignature(pubKey[:8]) +}