diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index b950e1dfd..e24f166fd 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,8 +1,8 @@ self-hosted-runner: # Labels of self-hosted or large runner labels in array of string labels: - - ubuntu20.04-4cores-16GB - - ubuntu20.04-8cores-32GB - - ubuntu20.04-16cores-64GB - - ubuntu20.04-32cores-128GB - - ubuntu20.04-64cores-256GB \ No newline at end of file + - ubuntu-latest-4cores-16GB + - ubuntu-latest-8cores-32GB + - ubuntu-latest-16cores-64GB + - ubuntu-latest-32cores-128GB + - ubuntu-latest-64cores-256GB diff --git a/.github/workflows/e2e_custom_cl.yml b/.github/workflows/e2e_custom_cl.yml index 39791194d..0404a0338 100644 --- a/.github/workflows/e2e_custom_cl.yml +++ b/.github/workflows/e2e_custom_cl.yml @@ -53,7 +53,7 @@ jobs: permissions: id-token: write contents: read - runs-on: ubuntu20.04-32cores-128GB + runs-on: ubuntu-latest-32cores-128GB needs: [get_projectserum_version] container: image: projectserum/build:${{ needs.get_projectserum_version.outputs.projectserum_version }} @@ -68,7 +68,7 @@ jobs: e2e_custom_build_custom_chainlink_image: name: E2E Custom Build Custom CL Image - runs-on: ubuntu20.04-16cores-64GB + runs-on: ubuntu-latest-16cores-64GB environment: integration permissions: id-token: write @@ -101,7 +101,7 @@ jobs: pull-requests: write id-token: write contents: read - runs-on: ubuntu20.04-16cores-64GB + runs-on: ubuntu-latest-16cores-64GB needs: [e2e_custom_build_artifacts, e2e_custom_build_custom_chainlink_image, check_test_compilation] env: diff --git a/cmd/monitoring/main.go b/cmd/monitoring/main.go index c8b9bee10..d713de5ad 100644 --- a/cmd/monitoring/main.go +++ b/cmd/monitoring/main.go @@ -57,27 +57,51 @@ func main() { return } + // per-feed sources feedBalancesSourceFactory := monitoring.NewFeedBalancesSourceFactory( chainReader, logger.With(log, "component", "source-feed-balances"), ) + txDetailsSourceFactory := monitoring.NewTxDetailsSourceFactory( + chainReader, + logger.With(log, "component", "source-tx-details"), + ) + monitor.SourceFactories = append(monitor.SourceFactories, + feedBalancesSourceFactory, + txDetailsSourceFactory, + ) + + // network sources nodeBalancesSourceFactory := monitoring.NewNodeBalancesSourceFactory( chainReader, logger.With(log, "component", "source-node-balances"), ) - monitor.SourceFactories = append(monitor.SourceFactories, feedBalancesSourceFactory) - monitor.NetworkSourceFactories = append(monitor.NetworkSourceFactories, nodeBalancesSourceFactory) + monitor.NetworkSourceFactories = append(monitor.NetworkSourceFactories, + nodeBalancesSourceFactory, + ) + // per-feed exporters feedBalancesExporterFactory := exporter.NewFeedBalancesFactory( logger.With(log, "component", "solana-prom-exporter"), metrics.NewFeedBalances(logger.With(log, "component", "solana-metrics")), ) + reportObservationsFactory := exporter.NewReportObservationsFactory( + logger.With(log, "component", "solana-prome-exporter"), + metrics.NewReportObservations(logger.With(log, "component", "solana-metrics")), + ) + monitor.ExporterFactories = append(monitor.ExporterFactories, + feedBalancesExporterFactory, + reportObservationsFactory, + ) + + // network exporters nodeBalancesExporterFactory := exporter.NewNodeBalancesFactory( logger.With(log, "component", "solana-prom-exporter"), metrics.NewNodeBalances, ) - monitor.ExporterFactories = append(monitor.ExporterFactories, feedBalancesExporterFactory) - monitor.NetworkExporterFactories = append(monitor.NetworkExporterFactories, nodeBalancesExporterFactory) + monitor.NetworkExporterFactories = append(monitor.NetworkExporterFactories, + nodeBalancesExporterFactory, + ) monitor.Run() log.Infow("monitor stopped") diff --git a/go.mod b/go.mod index 596e855f0..8ba8e5086 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.21 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc - github.com/gagliardetto/binary v0.7.1 + github.com/gagliardetto/binary v0.7.7 github.com/gagliardetto/gofuzz v1.2.2 - github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 + github.com/gagliardetto/solana-go v1.8.4 github.com/gagliardetto/treeout v0.1.4 github.com/gagliardetto/utilz v0.1.1 github.com/google/uuid v1.3.1 @@ -83,12 +83,14 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect + github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/teris-io/shortid v0.0.0-20201117134242-e59966efd125 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/x448/float16 v0.8.4 // 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.45.0 // indirect go.opentelemetry.io/otel v1.19.0 // indirect @@ -104,6 +106,7 @@ require ( golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect + golang.org/x/time v0.3.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/grpc v1.58.3 // indirect diff --git a/go.sum b/go.sum index 5041a314e..93d9601c9 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,7 @@ github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -144,14 +145,13 @@ github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8Wlg github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= -github.com/gagliardetto/binary v0.6.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= -github.com/gagliardetto/binary v0.7.1 h1:6ggDQ26vR+4xEvl/S13NcdLK3MUCi4oSy73pS9aI1cI= -github.com/gagliardetto/binary v0.7.1/go.mod h1:aOfYkc20U0deHaHn/LVZXiqlkDbFAX0FpTlDhsXa0S0= +github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyOsjfY= +github.com/gagliardetto/binary v0.7.7/go.mod h1:mUuay5LL8wFVnIlecHakSZMvcdqfs+CsotR5n77kyjM= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= github.com/gagliardetto/hashsearch v0.0.0-20191005111333-09dd671e19f9/go.mod h1:513DXpQPzeRo7d4dsCP3xO3XI8hgvruMl9njxyQeraQ= -github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27 h1:q2IztKyRQUxJ6abXRsawaBtvDFvM+szj4jDqV4od1gs= -github.com/gagliardetto/solana-go v1.4.1-0.20220428092759-5250b4abbb27/go.mod h1:NFuoDwHPvw858ZMHUJr6bkhN8qHt4x6e+U3EYHxAwNY= +github.com/gagliardetto/solana-go v1.8.4 h1:vmD/JmTlonyXGy39bAo0inMhmbdAwV7rXZtLDMZeodE= +github.com/gagliardetto/solana-go v1.8.4/go.mod h1:i+7aAyNDTHG0jK8GZIBSI4OVvDqkt2Qx+LklYclRNG8= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/gagliardetto/utilz v0.1.1 h1:/etW4hl607emKg6R6Lj9jRJ9d6ue2AQOyjhuAwjzs1U= @@ -222,6 +222,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -370,6 +371,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mostynb/zstdpool-freelist v0.0.0-20201229113212-927304c0c3b1 h1:mPMvm6X6tf4w8y7j9YIt6V9jfWhL6QlbEc7CCmeQlWk= @@ -466,6 +468,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streamingfast/logging v0.0.0-20220405224725-2755dab2ce75 h1:ZqpS7rAhhKD7S7DnrpEdrnW1/gZcv82ytpMviovkli4= +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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -498,6 +502,7 @@ github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -505,12 +510,20 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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= github.com/yuin/goldmark v1.1.32/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= 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= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= @@ -541,6 +554,7 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -555,6 +569,7 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 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= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -565,7 +580,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -601,6 +617,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -638,11 +655,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -712,7 +729,8 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= @@ -731,11 +749,14 @@ 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.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= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -779,12 +800,12 @@ golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWc golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200601175630-2caf76543d99/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -910,6 +931,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 6dff3827e..54c07ea58 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -7,16 +7,16 @@ replace github.com/smartcontractkit/chainlink-solana => ../ require ( github.com/ethereum/go-ethereum v1.13.8 github.com/gagliardetto/binary v0.7.7 - github.com/gagliardetto/solana-go v1.8.3 + github.com/gagliardetto/solana-go v1.8.4 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 github.com/onsi/gomega v1.30.0 github.com/rs/zerolog v1.30.0 github.com/smartcontractkit/chainlink-common v0.1.7-0.20240417194609-e703d51e5e19 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240216142700-c5869534c19e - github.com/smartcontractkit/chainlink-testing-framework v1.28.2 - github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240415213707-06bad6e44ff7 - github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240415213707-06bad6e44ff7 + github.com/smartcontractkit/chainlink-testing-framework v1.28.3 + github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240417200925-ccb8cd85fef8 + github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240417200925-ccb8cd85fef8 github.com/smartcontractkit/libocr v0.0.0-20240326191951-2bbe9382d052 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.28.0 @@ -365,8 +365,8 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 // indirect github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595 // indirect - github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 // indirect - github.com/smartcontractkit/seth v0.1.3 // indirect + github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 // indirect + github.com/smartcontractkit/seth v0.1.5 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wasp v0.4.5 // indirect @@ -415,7 +415,7 @@ require ( go.etcd.io/etcd/api/v3 v3.5.9 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect go.etcd.io/etcd/client/v3 v3.5.9 // indirect - go.mongodb.org/mongo-driver v1.12.0 // indirect + go.mongodb.org/mongo-driver v1.15.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/collector/pdata v1.0.0-rcv0016 // indirect go.opentelemetry.io/collector/semconv v0.87.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index ec97f45a4..20d01883e 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -480,8 +480,8 @@ github.com/gagliardetto/binary v0.7.7 h1:QZpT38+sgoPg+TIQjH94sLbl/vX+nlIRA37pEyO github.com/gagliardetto/binary v0.7.7/go.mod h1:mUuay5LL8wFVnIlecHakSZMvcdqfs+CsotR5n77kyjM= github.com/gagliardetto/gofuzz v1.2.2 h1:XL/8qDMzcgvR4+CyRQW9UGdwPRPMHVJfqQ/uMvSUuQw= github.com/gagliardetto/gofuzz v1.2.2/go.mod h1:bkH/3hYLZrMLbfYWA0pWzXmi5TTRZnu4pMGZBkqMKvY= -github.com/gagliardetto/solana-go v1.8.3 h1:YHcxw2nLNdjyy5iR+ZUcpljSrjZrWRr8OusuCTr70p4= -github.com/gagliardetto/solana-go v1.8.3/go.mod h1:i+7aAyNDTHG0jK8GZIBSI4OVvDqkt2Qx+LklYclRNG8= +github.com/gagliardetto/solana-go v1.8.4 h1:vmD/JmTlonyXGy39bAo0inMhmbdAwV7rXZtLDMZeodE= +github.com/gagliardetto/solana-go v1.8.4/go.mod h1:i+7aAyNDTHG0jK8GZIBSI4OVvDqkt2Qx+LklYclRNG8= github.com/gagliardetto/treeout v0.1.4 h1:ozeYerrLCmCubo1TcIjFiOWTTGteOOHND1twdFpgwaw= github.com/gagliardetto/treeout v0.1.4/go.mod h1:loUefvXTrlRG5rYmJmExNryyBRh8f89VZhmMOyCyqok= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= @@ -1423,22 +1423,22 @@ github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 h github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8/go.mod h1:vy1L7NybTy2F/Yv7BOh+oZBa1MACD6gzd1+DkcSkfp8= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595 h1:y6ks0HsSOhPUueOmTcoxDQ50RCS1XINlRDTemZyHjFw= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240325075535-0f7eb05ee595/go.mod h1:vV6WfnVIbK5Q1JsIru4YcTG0T1uRpLJm6t2BgCnCSsg= -github.com/smartcontractkit/chainlink-testing-framework v1.28.2 h1:H4/RW9J3EmHi4uJUxREJHkxxBbKRRk/eO3YhuR9a9zI= -github.com/smartcontractkit/chainlink-testing-framework v1.28.2/go.mod h1:jN+HgXbriq6fKRlIqLw9F3I81aYImV6kBJkIfz0mdIA= -github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 h1:FFdvEzlYwcuVHkdZ8YnZR/XomeMGbz5E2F2HZI3I3w8= -github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= -github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240415213707-06bad6e44ff7 h1:/EW/44obUyAXuha+K89RHJJ5zLLwho500rNPwB2QyGU= -github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240415213707-06bad6e44ff7/go.mod h1:YnDzdGyqoqUHUYNf/9c2k79WIQDT69qOePd+s8UcboU= -github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240415213707-06bad6e44ff7 h1:taia8w1/Tmg8FbwV+ZEY003gJDs5jbfWuiExd0NIQOE= -github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240415213707-06bad6e44ff7/go.mod h1:pLFAn8V6R43E1UnjlyHLx+AizzqFTfcDCDub2zVprKA= +github.com/smartcontractkit/chainlink-testing-framework v1.28.3 h1:rZ622PUSE9jJvI2g1SNNcMJedXyMzq9XJ8SbV2j9TvA= +github.com/smartcontractkit/chainlink-testing-framework v1.28.3/go.mod h1:jN+HgXbriq6fKRlIqLw9F3I81aYImV6kBJkIfz0mdIA= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 h1:LQmRsrzzaYYN3wEU1l5tWiccznhvbyGnu2N+wHSXZAo= +github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= +github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240417200925-ccb8cd85fef8 h1:ry4UvL8Xvv5WQ88loTPfTGFEpx3jvTW33vo3atIUDLY= +github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240417200925-ccb8cd85fef8/go.mod h1:PYVNCjUoVe5ikCuOPEZLPEkylvX7vb7ETz5DBedImlU= +github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240417200925-ccb8cd85fef8 h1:VmFX9BEGxVK0NbR9OPNxsWGSwluiIVSUxvn2MRLaMD0= +github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240417200925-ccb8cd85fef8/go.mod h1:c74b4KKCLhkKVmRCAWmot8/+Cr2vdEisSDKygdZUV9E= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f/go.mod h1:MvMXoufZAtqExNexqi4cjrNYE9MefKddKylxjS+//n0= github.com/smartcontractkit/libocr v0.0.0-20240326191951-2bbe9382d052 h1:1WFjrrVrWoQ9UpVMh7Mx4jDpzhmo1h8hFUKd9awIhIU= github.com/smartcontractkit/libocr v0.0.0-20240326191951-2bbe9382d052/go.mod h1:SJEZCHgMCAzzBvo9vMV2DQ9onfEcIJCYSViyP4JI6c4= -github.com/smartcontractkit/seth v0.1.3 h1:pQc+SJeONWg73lQOiY5ZmBbvvVqEVBmTM9PiJOr+n4s= -github.com/smartcontractkit/seth v0.1.3/go.mod h1:2TMOZQ8WTAw7rR1YBbXpnad6VmT/+xDd/nXLmB7Eero= +github.com/smartcontractkit/seth v0.1.5 h1:tobdA3uzRHubN/ytE6bSq1dtvmaXjKdaHEGW5Re9I1U= +github.com/smartcontractkit/seth v0.1.5/go.mod h1:2TMOZQ8WTAw7rR1YBbXpnad6VmT/+xDd/nXLmB7Eero= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 h1:yiKnypAqP8l0OX0P3klzZ7SCcBUxy5KqTAKZmQOvSQE= github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1/go.mod h1:q6f4fe39oZPdsh1i57WznEZgxd8siidMaSFq3wdPmVg= github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 h1:Dai1bn+Q5cpeGMQwRdjOdVjG8mmFFROVkSKuUgBErRQ= @@ -1580,10 +1580,8 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= @@ -1632,8 +1630,8 @@ go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R7 go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8= go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8= -go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= -go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= +go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= diff --git a/pkg/monitoring/config/feed_config.go b/pkg/monitoring/config/feed_config.go index a9194ae31..2c4cc31ae 100644 --- a/pkg/monitoring/config/feed_config.go +++ b/pkg/monitoring/config/feed_config.go @@ -72,7 +72,7 @@ func (s SolanaFeedConfig) GetMultiply() *big.Int { return s.Multiply } -// GetID returns the state account's address as that uniquely +// GetContractAddress returns the state account's address as that uniquely // identifies a feed on Solana. In Solana, a program is stateless and we // use the same program for all feeds so we can't use the program // account's address. diff --git a/pkg/monitoring/exporter/feedbalances.go b/pkg/monitoring/exporter/feedbalances.go index c1a4a2bd8..5db261b50 100644 --- a/pkg/monitoring/exporter/feedbalances.go +++ b/pkg/monitoring/exporter/feedbalances.go @@ -29,7 +29,7 @@ type feedBalancesFactory struct { func (p *feedBalancesFactory) NewExporter( params commonMonitoring.ExporterParams, ) (commonMonitoring.Exporter, error) { - return &feeBalances{ + return &feedBalances{ params.ChainConfig, params.FeedConfig, p.log, @@ -39,7 +39,7 @@ func (p *feedBalancesFactory) NewExporter( }, nil } -type feeBalances struct { +type feedBalances struct { chainConfig commonMonitoring.ChainConfig feedConfig commonMonitoring.FeedConfig @@ -50,7 +50,7 @@ type feeBalances struct { addresses map[string]solana.PublicKey } -func (p *feeBalances) Export(ctx context.Context, data interface{}) { +func (p *feedBalances) Export(ctx context.Context, data interface{}) { balances, isBalances := data.(types.Balances) if !isBalances { return @@ -70,17 +70,17 @@ func (p *feeBalances) Export(ctx context.Context, data interface{}) { } p.metrics.SetBalance( balance, - metrics.FeedBalanceInput{ - BalanceAccountName: balanceAccountName, - AccountAddress: address.String(), - FeedID: p.feedConfig.GetContractAddress(), - ChainID: p.chainConfig.GetChainID(), - ContractStatus: p.feedConfig.GetContractStatus(), - ContractType: p.feedConfig.GetContractType(), - FeedName: p.feedConfig.GetName(), - FeedPath: p.feedConfig.GetPath(), - NetworkID: p.chainConfig.GetNetworkID(), - NetworkName: p.chainConfig.GetNetworkName(), + balanceAccountName, + metrics.FeedInput{ + AccountAddress: address.String(), + FeedID: p.feedConfig.GetContractAddress(), + ChainID: p.chainConfig.GetChainID(), + ContractStatus: p.feedConfig.GetContractStatus(), + ContractType: p.feedConfig.GetContractType(), + FeedName: p.feedConfig.GetName(), + FeedPath: p.feedConfig.GetPath(), + NetworkID: p.chainConfig.GetNetworkID(), + NetworkName: p.chainConfig.GetNetworkName(), }, ) } @@ -90,21 +90,20 @@ func (p *feeBalances) Export(ctx context.Context, data interface{}) { p.addresses = balances.Addresses } -func (p *feeBalances) Cleanup(_ context.Context) { +func (p *feedBalances) Cleanup(_ context.Context) { p.addressesMu.Lock() defer p.addressesMu.Unlock() for balanceAccountName, address := range p.addresses { - p.metrics.Cleanup(metrics.FeedBalanceInput{ - BalanceAccountName: balanceAccountName, - AccountAddress: address.String(), - FeedID: p.feedConfig.GetContractAddress(), - ChainID: p.chainConfig.GetChainID(), - ContractStatus: p.feedConfig.GetContractStatus(), - ContractType: p.feedConfig.GetContractType(), - FeedName: p.feedConfig.GetName(), - FeedPath: p.feedConfig.GetPath(), - NetworkID: p.chainConfig.GetNetworkID(), - NetworkName: p.chainConfig.GetNetworkName(), + p.metrics.Cleanup(balanceAccountName, metrics.FeedInput{ + AccountAddress: address.String(), + FeedID: p.feedConfig.GetContractAddress(), + ChainID: p.chainConfig.GetChainID(), + ContractStatus: p.feedConfig.GetContractStatus(), + ContractType: p.feedConfig.GetContractType(), + FeedName: p.feedConfig.GetName(), + FeedPath: p.feedConfig.GetPath(), + NetworkID: p.chainConfig.GetNetworkID(), + NetworkName: p.chainConfig.GetNetworkName(), }) } } diff --git a/pkg/monitoring/exporter/feedbalances_test.go b/pkg/monitoring/exporter/feedbalances_test.go index 62ebc488a..0a333f461 100644 --- a/pkg/monitoring/exporter/feedbalances_test.go +++ b/pkg/monitoring/exporter/feedbalances_test.go @@ -37,34 +37,33 @@ func TestFeedBalances(t *testing.T) { for _, accountName := range types.FeedBalanceAccountNames { mockMetrics.On("SetBalance", balances.Values[accountName], - metrics.FeedBalanceInput{ - BalanceAccountName: accountName, - AccountAddress: balances.Addresses[accountName].String(), - FeedID: feedConfig.GetID(), - ChainID: chainConfig.GetChainID(), - ContractStatus: feedConfig.GetContractStatus(), - ContractType: feedConfig.GetContractType(), - FeedName: feedConfig.GetName(), - FeedPath: feedConfig.GetPath(), - NetworkID: chainConfig.GetNetworkID(), - NetworkName: chainConfig.GetNetworkName(), + accountName, + metrics.FeedInput{ + AccountAddress: balances.Addresses[accountName].String(), + FeedID: feedConfig.GetID(), + ChainID: chainConfig.GetChainID(), + ContractStatus: feedConfig.GetContractStatus(), + ContractType: feedConfig.GetContractType(), + FeedName: feedConfig.GetName(), + FeedPath: feedConfig.GetPath(), + NetworkID: chainConfig.GetNetworkID(), + NetworkName: chainConfig.GetNetworkName(), }, ) } exporter.Export(ctx, balances) for _, accountName := range types.FeedBalanceAccountNames { - mockMetrics.On("Cleanup", metrics.FeedBalanceInput{ - BalanceAccountName: accountName, - AccountAddress: balances.Addresses[accountName].String(), - FeedID: feedConfig.GetID(), - ChainID: chainConfig.GetChainID(), - ContractStatus: feedConfig.GetContractStatus(), - ContractType: feedConfig.GetContractType(), - FeedName: feedConfig.GetName(), - FeedPath: feedConfig.GetPath(), - NetworkID: chainConfig.GetNetworkID(), - NetworkName: chainConfig.GetNetworkName(), + mockMetrics.On("Cleanup", accountName, metrics.FeedInput{ + AccountAddress: balances.Addresses[accountName].String(), + FeedID: feedConfig.GetID(), + ChainID: chainConfig.GetChainID(), + ContractStatus: feedConfig.GetContractStatus(), + ContractType: feedConfig.GetContractType(), + FeedName: feedConfig.GetName(), + FeedPath: feedConfig.GetPath(), + NetworkID: chainConfig.GetNetworkID(), + NetworkName: chainConfig.GetNetworkName(), }) } exporter.Cleanup(ctx) diff --git a/pkg/monitoring/exporter/reportobservations.go b/pkg/monitoring/exporter/reportobservations.go new file mode 100644 index 000000000..80e980c81 --- /dev/null +++ b/pkg/monitoring/exporter/reportobservations.go @@ -0,0 +1,82 @@ +package exporter + +import ( + "context" + + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func NewReportObservationsFactory( + log commonMonitoring.Logger, + metrics metrics.ReportObservations, +) commonMonitoring.ExporterFactory { + return &reportObservationsFactory{ + log, + metrics, + } +} + +type reportObservationsFactory struct { + log commonMonitoring.Logger + metrics metrics.ReportObservations +} + +func (p *reportObservationsFactory) NewExporter( + params commonMonitoring.ExporterParams, +) (commonMonitoring.Exporter, error) { + return &reportObservations{ + metrics.FeedInput{ + AccountAddress: params.FeedConfig.GetContractAddress(), + FeedID: params.FeedConfig.GetContractAddress(), + ChainID: params.ChainConfig.GetChainID(), + ContractStatus: params.FeedConfig.GetContractStatus(), + ContractType: params.FeedConfig.GetContractType(), + FeedName: params.FeedConfig.GetName(), + FeedPath: params.FeedConfig.GetPath(), + NetworkID: params.ChainConfig.GetNetworkID(), + NetworkName: params.ChainConfig.GetNetworkName(), + }, + p.log, + p.metrics, + }, nil +} + +type reportObservations struct { + label metrics.FeedInput // static for each feed + log commonMonitoring.Logger + metrics metrics.ReportObservations +} + +func (p *reportObservations) Export(ctx context.Context, data interface{}) { + details, err := types.MakeTxDetails(data) + if err != nil { + return // skip if input could not be parsed + } + + // skip on no updates + if len(details) == 0 { + return + } + + // sanity check: find non-empty detail + // assumption: details ordered from latest -> earliest + var latest types.TxDetails + for _, d := range details { + if !d.Empty() { + latest = d + break + } + } + if latest.Empty() { + p.log.Errorw("exporter could not find non-empty TxDetails", "feed", p.label.ToPromLabels()) + return + } + + p.metrics.SetCount(latest.ObservationCount, p.label) +} + +func (p *reportObservations) Cleanup(_ context.Context) { + p.metrics.Cleanup(p.label) +} diff --git a/pkg/monitoring/exporter/reportobservations_test.go b/pkg/monitoring/exporter/reportobservations_test.go new file mode 100644 index 000000000..70726720e --- /dev/null +++ b/pkg/monitoring/exporter/reportobservations_test.go @@ -0,0 +1,50 @@ +package exporter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + "github.com/smartcontractkit/chainlink-common/pkg/utils" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics/mocks" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/testutils" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func TestReportObservations(t *testing.T) { + ctx := utils.Context(t) + lgr, logs := logger.TestObserved(t, zapcore.ErrorLevel) + m := mocks.NewReportObservations(t) + m.On("SetCount", mock.Anything, mock.Anything).Once() + m.On("Cleanup", mock.Anything).Once() + + factory := NewReportObservationsFactory(lgr, m) + + chainConfig := testutils.GenerateChainConfig() + feedConfig := testutils.GenerateFeedConfig() + exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ChainConfig: chainConfig, FeedConfig: feedConfig, Nodes: []commonMonitoring.NodeConfig{}}) + require.NoError(t, err) + + // happy path + exporter.Export(ctx, []types.TxDetails{{ObservationCount: 10}}) + exporter.Cleanup(ctx) + + // not txdetails type - no calls to mock + assert.NotPanics(t, func() { exporter.Export(ctx, 1) }) + + // zero txdetails - no calls to mock + exporter.Export(ctx, []types.TxDetails{}) + + // empty txdetails + exporter.Export(ctx, []types.TxDetails{{}}) + assert.Equal(t, 1, logs.FilterMessage("exporter could not find non-empty TxDetails").Len()) + + // multiple TxDetails should only call for the first non-empty one + m.On("SetCount", uint8(1), mock.Anything).Once() + exporter.Export(ctx, []types.TxDetails{{}, {ObservationCount: 1}, {ObservationCount: 10}}) +} diff --git a/pkg/monitoring/metrics/common.go b/pkg/monitoring/metrics/common.go new file mode 100644 index 000000000..0de63e97d --- /dev/null +++ b/pkg/monitoring/metrics/common.go @@ -0,0 +1,49 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" +) + +// simpleGauge is an internal implementation for fetching a gauge from the gauges map +// and share logic for fetching, error handling, and setting. +// simpleGauge should be wrapped for export, not directly exported +type simpleGauge struct { + log commonMonitoring.Logger + metricName string +} + +func newSimpleGauge(log commonMonitoring.Logger, name string) simpleGauge { + if log == nil { + panic("simpleGauge.logger is nil") + } + return simpleGauge{log, name} +} + +func (sg simpleGauge) set(value float64, labels prometheus.Labels) { + if gauges == nil { + sg.log.Fatalw("gauges is nil") + return + } + + gauge, ok := gauges[sg.metricName] + if !ok { + sg.log.Errorw("gauge not found", "name", sg.metricName) + return + } + gauge.With(labels).Set(value) +} + +func (sg simpleGauge) delete(labels prometheus.Labels) { + if gauges == nil { + sg.log.Fatalw("gauges is nil") + return + } + + gauge, ok := gauges[sg.metricName] + if !ok { + sg.log.Errorw("gauge not found", "name", sg.metricName) + return + } + gauge.Delete(labels) +} diff --git a/pkg/monitoring/metrics/common_test.go b/pkg/monitoring/metrics/common_test.go new file mode 100644 index 000000000..c8f65f711 --- /dev/null +++ b/pkg/monitoring/metrics/common_test.go @@ -0,0 +1,27 @@ +package metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" +) + +func TestSimpleGauge(t *testing.T) { + // panic on empty logger + require.Panics(t, func() { newSimpleGauge(nil, "") }) + + lgr, logs := logger.TestObserved(t, zapcore.ErrorLevel) + + // invalid name + g := newSimpleGauge(lgr, t.Name()) + g.set(0, prometheus.Labels{}) + g.delete(prometheus.Labels{}) + require.Equal(t, 2, logs.FilterMessage("gauge not found").Len()) + + // happy path is tested by each individual metric implementation + // to match proper metrics and labels +} diff --git a/pkg/monitoring/metrics/feedbalances.go b/pkg/monitoring/metrics/feedbalances.go index fc8507361..c56c86a75 100644 --- a/pkg/monitoring/metrics/feedbalances.go +++ b/pkg/monitoring/metrics/feedbalances.go @@ -12,8 +12,8 @@ import ( type FeedBalances interface { Exists(balanceAccountName string) (*prometheus.GaugeVec, bool) - SetBalance(balance uint64, input FeedBalanceInput) - Cleanup(input FeedBalanceInput) + SetBalance(balance uint64, balanceAccountName string, feedInput FeedInput) + Cleanup(balanceAccountName string, feedInput FeedInput) } var _ FeedBalances = (*feedBalances)(nil) @@ -22,24 +22,6 @@ type feedBalances struct { log commonMonitoring.Logger } -type FeedBalanceInput struct { - BalanceAccountName, AccountAddress, FeedID, ChainID, ContractStatus, ContractType, FeedName, FeedPath, NetworkID, NetworkName string -} - -func (i FeedBalanceInput) ToPromLabels() prometheus.Labels { - return prometheus.Labels{ - "account_address": i.AccountAddress, - "feed_id": i.FeedID, - "chain_id": i.ChainID, - "contract_status": i.ContractStatus, - "contract_type": i.ContractType, - "feed_name": i.FeedName, - "feed_path": i.FeedPath, - "network_id": i.NetworkID, - "network_name": i.NetworkName, - } -} - func NewFeedBalances(log commonMonitoring.Logger) *feedBalances { return &feedBalances{log} } @@ -49,18 +31,18 @@ func (fb *feedBalances) Exists(balanceAccountName string) (*prometheus.GaugeVec, return g, ok } -func (fb *feedBalances) SetBalance(balance uint64, input FeedBalanceInput) { - gauge, found := fb.Exists(input.BalanceAccountName) +func (fb *feedBalances) SetBalance(balance uint64, balanceAccountName string, feedInput FeedInput) { + gauge, found := fb.Exists(balanceAccountName) if !found { - panic(fmt.Sprintf("gauge not known for name '%s'", input.BalanceAccountName)) + panic(fmt.Sprintf("gauge not known for name '%s'", balanceAccountName)) } - gauge.With(input.ToPromLabels()).Set(float64(balance)) + gauge.With(feedInput.ToPromLabels()).Set(float64(balance)) } -func (fb *feedBalances) Cleanup(input FeedBalanceInput) { - gauge, found := fb.Exists(input.BalanceAccountName) +func (fb *feedBalances) Cleanup(balanceAccountName string, feedInput FeedInput) { + gauge, found := fb.Exists(balanceAccountName) if !found { - panic(fmt.Sprintf("gauge not known for name '%s'", input.BalanceAccountName)) + panic(fmt.Sprintf("gauge not known for name '%s'", balanceAccountName)) } - gauge.Delete(input.ToPromLabels()) + gauge.Delete(feedInput.ToPromLabels()) } diff --git a/pkg/monitoring/metrics/feedbalances_test.go b/pkg/monitoring/metrics/feedbalances_test.go index 27e655710..1fa8f3f9c 100644 --- a/pkg/monitoring/metrics/feedbalances_test.go +++ b/pkg/monitoring/metrics/feedbalances_test.go @@ -22,19 +22,19 @@ func TestFeedBalances(t *testing.T) { assert.False(t, ok) // setting gauges - input := FeedBalanceInput{ - BalanceAccountName: types.FeedBalanceAccountNames[0], - AccountAddress: t.Name(), // use unique to prevent conflicts if run parallel + balanceAccountName := types.FeedBalanceAccountNames[0] + input := FeedInput{ + AccountAddress: t.Name(), // use unique to prevent conflicts if run parallel } v := 100 - assert.NotPanics(t, func() { m.SetBalance(uint64(v), input) }) + assert.NotPanics(t, func() { m.SetBalance(uint64(v), balanceAccountName, input) }) promBal := testutil.ToFloat64(bal.With(input.ToPromLabels())) assert.Equal(t, float64(v), promBal) - assert.Panics(t, func() { m.SetBalance(0, FeedBalanceInput{}) }) + assert.Panics(t, func() { m.SetBalance(0, "", FeedInput{}) }) // cleanup gauges assert.Equal(t, 1, testutil.CollectAndCount(bal)) - assert.NotPanics(t, func() { m.Cleanup(input) }) + assert.NotPanics(t, func() { m.Cleanup(balanceAccountName, input) }) assert.Equal(t, 0, testutil.CollectAndCount(bal)) - assert.Panics(t, func() { m.Cleanup(FeedBalanceInput{}) }) + assert.Panics(t, func() { m.Cleanup("", FeedInput{}) }) } diff --git a/pkg/monitoring/metrics/metrics.go b/pkg/monitoring/metrics/metrics.go index 0447ac24b..2768de97a 100644 --- a/pkg/monitoring/metrics/metrics.go +++ b/pkg/monitoring/metrics/metrics.go @@ -10,7 +10,7 @@ import ( ) var ( - feedBalanceLabelNames = []string{ + feedLabels = []string{ // This is the address of the account associated with one of the account names above. "account_address", "feed_id", @@ -23,7 +23,7 @@ var ( "network_name", } - nodeBalanceLabels = []string{ + nodeLabels = []string{ "account_address", "node_operator", "chain", @@ -45,7 +45,7 @@ func init() { prometheus.GaugeOpts{ Name: makeBalanceMetricName(balanceAccountName), }, - feedBalanceLabelNames, + feedLabels, ) } @@ -54,6 +54,32 @@ func init() { prometheus.GaugeOpts{ Name: makeBalanceMetricName(types.NodeBalanceMetric), }, - nodeBalanceLabels, + nodeLabels, ) + + // init gauge for observation count tracking + gauges[types.ReportObservationMetric] = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: types.ReportObservationMetric, + }, + feedLabels, + ) +} + +type FeedInput struct { + AccountAddress, FeedID, ChainID, ContractStatus, ContractType, FeedName, FeedPath, NetworkID, NetworkName string +} + +func (i FeedInput) ToPromLabels() prometheus.Labels { + return prometheus.Labels{ + "account_address": i.AccountAddress, + "feed_id": i.FeedID, + "chain_id": i.ChainID, + "contract_status": i.ContractStatus, + "contract_type": i.ContractType, + "feed_name": i.FeedName, + "feed_path": i.FeedPath, + "network_id": i.NetworkID, + "network_name": i.NetworkName, + } } diff --git a/pkg/monitoring/metrics/mocks/FeedBalances.go b/pkg/monitoring/metrics/mocks/FeedBalances.go index 849bd9312..ad3e69efc 100644 --- a/pkg/monitoring/metrics/mocks/FeedBalances.go +++ b/pkg/monitoring/metrics/mocks/FeedBalances.go @@ -14,9 +14,9 @@ type FeedBalances struct { mock.Mock } -// Cleanup provides a mock function with given fields: input -func (_m *FeedBalances) Cleanup(input metrics.FeedBalanceInput) { - _m.Called(input) +// Cleanup provides a mock function with given fields: balanceAccountName, feedInput +func (_m *FeedBalances) Cleanup(balanceAccountName string, feedInput metrics.FeedInput) { + _m.Called(balanceAccountName, feedInput) } // Exists provides a mock function with given fields: balanceAccountName @@ -45,9 +45,9 @@ func (_m *FeedBalances) Exists(balanceAccountName string) (*prometheus.GaugeVec, return r0, r1 } -// SetBalance provides a mock function with given fields: balance, input -func (_m *FeedBalances) SetBalance(balance uint64, input metrics.FeedBalanceInput) { - _m.Called(balance, input) +// SetBalance provides a mock function with given fields: balance, balanceAccountName, feedInput +func (_m *FeedBalances) SetBalance(balance uint64, balanceAccountName string, feedInput metrics.FeedInput) { + _m.Called(balance, balanceAccountName, feedInput) } type mockConstructorTestingTNewFeedBalances interface { diff --git a/pkg/monitoring/metrics/mocks/ReportObservations.go b/pkg/monitoring/metrics/mocks/ReportObservations.go new file mode 100644 index 000000000..12976948c --- /dev/null +++ b/pkg/monitoring/metrics/mocks/ReportObservations.go @@ -0,0 +1,38 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + metrics "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics" + mock "github.com/stretchr/testify/mock" +) + +// ReportObservations is an autogenerated mock type for the ReportObservations type +type ReportObservations struct { + mock.Mock +} + +// Cleanup provides a mock function with given fields: feedInput +func (_m *ReportObservations) Cleanup(feedInput metrics.FeedInput) { + _m.Called(feedInput) +} + +// SetCount provides a mock function with given fields: count, feedInput +func (_m *ReportObservations) SetCount(count uint8, feedInput metrics.FeedInput) { + _m.Called(count, feedInput) +} + +type mockConstructorTestingTNewReportObservations interface { + mock.TestingT + Cleanup(func()) +} + +// NewReportObservations creates a new instance of ReportObservations. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewReportObservations(t mockConstructorTestingTNewReportObservations) *ReportObservations { + mock := &ReportObservations{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/monitoring/metrics/nodebalances.go b/pkg/monitoring/metrics/nodebalances.go index f3ffe5968..0bee640ba 100644 --- a/pkg/monitoring/metrics/nodebalances.go +++ b/pkg/monitoring/metrics/nodebalances.go @@ -15,36 +15,27 @@ type NodeBalances interface { } type nodeBalances struct { - log commonMonitoring.Logger + simpleGauge chain string } func NewNodeBalances(log commonMonitoring.Logger, chain string) NodeBalances { - return &nodeBalances{log, chain} + return &nodeBalances{ + newSimpleGauge(log, types.NodeBalanceMetric), + chain, + } } func (nb *nodeBalances) SetBalance(balance uint64, address, operator string) { - gauge, ok := gauges[types.NodeBalanceMetric] - if !ok { - nb.log.Fatalw("gauge not found", "name", types.NodeBalanceMetric) - return - } - - gauge.With(prometheus.Labels{ + nb.set(float64(balance), prometheus.Labels{ "account_address": address, "node_operator": operator, "chain": nb.chain, - }).Set(float64(balance)) + }) } func (nb *nodeBalances) Cleanup(address, operator string) { - gauge, ok := gauges[types.NodeBalanceMetric] - if !ok { - nb.log.Fatalw("gauge not found", "name", types.NodeBalanceMetric) - return - } - - gauge.Delete(prometheus.Labels{ + nb.delete(prometheus.Labels{ "account_address": address, "node_operator": operator, "chain": nb.chain, diff --git a/pkg/monitoring/metrics/reportobservations.go b/pkg/monitoring/metrics/reportobservations.go new file mode 100644 index 000000000..f790a4319 --- /dev/null +++ b/pkg/monitoring/metrics/reportobservations.go @@ -0,0 +1,32 @@ +package metrics + +import ( + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +//go:generate mockery --name ReportObservations --output ./mocks/ + +type ReportObservations interface { + SetCount(count uint8, feedInput FeedInput) + Cleanup(feedInput FeedInput) +} + +var _ ReportObservations = (*reportObservations)(nil) + +type reportObservations struct { + simpleGauge +} + +func NewReportObservations(log commonMonitoring.Logger) *reportObservations { + return &reportObservations{newSimpleGauge(log, types.ReportObservationMetric)} +} + +func (ro *reportObservations) SetCount(count uint8, feedInput FeedInput) { + ro.set(float64(count), feedInput.ToPromLabels()) +} + +func (ro *reportObservations) Cleanup(feedInput FeedInput) { + ro.delete(feedInput.ToPromLabels()) +} diff --git a/pkg/monitoring/metrics/reportobservations_test.go b/pkg/monitoring/metrics/reportobservations_test.go new file mode 100644 index 000000000..bc888a80d --- /dev/null +++ b/pkg/monitoring/metrics/reportobservations_test.go @@ -0,0 +1,35 @@ +package metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func TestReportObservations(t *testing.T) { + lgr := logger.Test(t) + m := NewReportObservations(lgr) + + // fetching gauges + g, ok := gauges[types.ReportObservationMetric] + require.True(t, ok) + + v := 100 + inputs := FeedInput{NetworkName: t.Name()} + + // set gauge + assert.NotPanics(t, func() { m.SetCount(uint8(v), inputs) }) + promBal := testutil.ToFloat64(g.With(inputs.ToPromLabels())) + assert.Equal(t, float64(v), promBal) + + // cleanup gauges + assert.Equal(t, 1, testutil.CollectAndCount(g)) + assert.NotPanics(t, func() { m.Cleanup(inputs) }) + assert.Equal(t, 0, testutil.CollectAndCount(g)) +} diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go new file mode 100644 index 000000000..1d13ca256 --- /dev/null +++ b/pkg/monitoring/source_txdetails.go @@ -0,0 +1,86 @@ +package monitoring + +import ( + "context" + "fmt" + + "github.com/gagliardetto/solana-go/rpc" + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/config" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func NewTxDetailsSourceFactory(client ChainReader, log commonMonitoring.Logger) commonMonitoring.SourceFactory { + return &txDetailsSourceFactory{client, log} +} + +type txDetailsSourceFactory struct { + client ChainReader + log commonMonitoring.Logger +} + +func (f *txDetailsSourceFactory) NewSource(_ commonMonitoring.ChainConfig, feedConfig commonMonitoring.FeedConfig) (commonMonitoring.Source, error) { + solanaFeedConfig, ok := feedConfig.(config.SolanaFeedConfig) + if !ok { + return nil, fmt.Errorf("expected feedConfig to be of type config.SolanaFeedConfig not %T", feedConfig) + } + + return &txDetailsSource{ + source: &txResultsSource{ + client: f.client, + log: f.log, + feedConfig: solanaFeedConfig, + }, + }, nil +} + +func (f *txDetailsSourceFactory) GetType() string { + return types.TxDetailsType +} + +type txDetailsSource struct { + source *txResultsSource // reuse underlying logic for getting signatures +} + +func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { + _, sigs, err := s.source.fetch(ctx) + if err != nil { + return nil, err + } + + details := []types.TxDetails{} + for _, sig := range sigs { + if sig == nil { + continue // skip for nil signatures + } + + // check only successful txs: indicates the fastest submissions of a report + if sig.Err != nil { + continue + } + + // potential improvement: worker pool - how many GetTransaction requests in a row? + tx, err := s.source.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{Commitment: "confirmed"}) + if err != nil { + return nil, err + } + if tx == nil { + // skip nil transaction (not found) + s.source.log.Debugw("GetTransaction returned nil", "signature", sig) + continue + } + + // parse transaction + filter based on known senders + res, err := types.ParseTxResult(tx, s.source.feedConfig.ContractAddress) + if err != nil { + // skip invalid transaction + s.source.log.Debugw("tx not valid for tracking", "error", err, "signature", sig) + continue + } + details = append(details, res) + } + + // only return successful OCR2 transmit transactions (slice/array) + return details, nil +} diff --git a/pkg/monitoring/source_txdetails_test.go b/pkg/monitoring/source_txdetails_test.go new file mode 100644 index 000000000..509a412dc --- /dev/null +++ b/pkg/monitoring/source_txdetails_test.go @@ -0,0 +1,88 @@ +package monitoring + +import ( + "encoding/json" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/config" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/mocks" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/testutils" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func TestTxDetailsSource(t *testing.T) { + cr := mocks.NewChainReader(t) + + lgr, logs := logger.TestObserved(t, zapcore.DebugLevel) + + f := NewTxDetailsSourceFactory(cr, lgr) + + assert.Equal(t, types.TxDetailsType, f.GetType()) + + cfg := config.SolanaFeedConfig{ + ContractAddress: types.SampleTxResultProgram, + } + s, err := f.NewSource(nil, cfg) + require.NoError(t, err) + + // empty response + cr.On("GetSignaturesForAddressWithOpts", mock.Anything, mock.Anything, mock.Anything).Return([]*rpc.TransactionSignature{}, nil).Once() + res, err := s.Fetch(tests.Context(t)) + require.NoError(t, err) + data := testutils.ParseTxDetails(t, res) + assert.Equal(t, 0, len(data)) + + // nil GetTransaction response + cr.On("GetSignaturesForAddressWithOpts", mock.Anything, mock.Anything, mock.Anything).Return([]*rpc.TransactionSignature{ + {}, + }, nil).Once() + cr.On("GetTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil).Once() + res, err = s.Fetch(tests.Context(t)) + require.NoError(t, err) + data = testutils.ParseTxDetails(t, res) + assert.Equal(t, 0, len(data)) // ignores tx + assert.Equal(t, 1, logs.FilterLevelExact(zapcore.DebugLevel).FilterMessage("GetTransaction returned nil").Len()) + + // invalid tx + cr.On("GetSignaturesForAddressWithOpts", mock.Anything, mock.Anything, mock.Anything).Return([]*rpc.TransactionSignature{ + {}, + }, nil).Once() + blockTime := solana.UnixTimeSeconds(0) + cr.On("GetTransaction", mock.Anything, mock.Anything, mock.Anything).Return(&rpc.GetTransactionResult{ + BlockTime: &blockTime, + Transaction: &rpc.TransactionResultEnvelope{}, + Meta: &rpc.TransactionMeta{}, + }, nil).Once() + res, err = s.Fetch(tests.Context(t)) + require.NoError(t, err) + data = testutils.ParseTxDetails(t, res) + assert.Equal(t, 0, len(data)) // ignores tx + assert.Equal(t, 1, logs.FilterLevelExact(zapcore.DebugLevel).FilterMessage("tx not valid for tracking").Len()) + + // happy path + var rpcResponse rpc.GetTransactionResult + require.NoError(t, json.Unmarshal([]byte(types.SampleTxResultJSON), &rpcResponse)) + cr.On("GetSignaturesForAddressWithOpts", mock.Anything, mock.Anything, mock.Anything).Return([]*rpc.TransactionSignature{ + {}, + }, nil).Once() + cr.On("GetTransaction", mock.Anything, mock.Anything, mock.Anything).Return(&rpcResponse, nil).Once() + res, err = s.Fetch(tests.Context(t)) + require.NoError(t, err) + data = testutils.ParseTxDetails(t, res) + assert.Equal(t, 1, len(data)) + assert.Nil(t, data[0].Err) + assert.NotEqual(t, solana.PublicKey{}, data[0].Sender) + assert.NotZero(t, data[0].ObservationCount) + assert.NotZero(t, data[0].Fee) + assert.NotZero(t, data[0].Slot) +} diff --git a/pkg/monitoring/source_txresults.go b/pkg/monitoring/source_txresults.go index 331cfbe30..cd8171bed 100644 --- a/pkg/monitoring/source_txresults.go +++ b/pkg/monitoring/source_txresults.go @@ -9,6 +9,7 @@ import ( "github.com/gagliardetto/solana-go/rpc" commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/config" ) @@ -61,7 +62,14 @@ type txResultsSource struct { latestSigMu sync.Mutex } +// Fetch is the externally called method that returns the specific TxResults output func (t *txResultsSource) Fetch(ctx context.Context) (interface{}, error) { + out, _, err := t.fetch(ctx) + return out, err +} + +// fetch is the internal method that returns data from the GetSignaturesForAddress RPC call +func (t *txResultsSource) fetch(ctx context.Context) (commonMonitoring.TxResults, []*rpc.TransactionSignature, error) { txSigsPageSize := 100 txSigs, err := t.client.GetSignaturesForAddressWithOpts( ctx, @@ -73,10 +81,10 @@ func (t *txResultsSource) Fetch(ctx context.Context) (interface{}, error) { }, ) if err != nil { - return nil, fmt.Errorf("failed to fetch transactions for state account: %w", err) + return commonMonitoring.TxResults{}, nil, fmt.Errorf("failed to fetch transactions for state account: %w", err) } if len(txSigs) == 0 { - return commonMonitoring.TxResults{NumSucceeded: 0, NumFailed: 0}, nil + return commonMonitoring.TxResults{NumSucceeded: 0, NumFailed: 0}, nil, nil } var numSucceeded, numFailed uint64 = 0, 0 for _, txSig := range txSigs { @@ -91,5 +99,5 @@ func (t *txResultsSource) Fetch(ctx context.Context) (interface{}, error) { defer t.latestSigMu.Unlock() t.latestSig = txSigs[0].Signature }() - return commonMonitoring.TxResults{NumSucceeded: numSucceeded, NumFailed: numFailed}, nil + return commonMonitoring.TxResults{NumSucceeded: numSucceeded, NumFailed: numFailed}, txSigs, nil } diff --git a/pkg/monitoring/testutils/parse.go b/pkg/monitoring/testutils/parse.go new file mode 100644 index 000000000..446d651a9 --- /dev/null +++ b/pkg/monitoring/testutils/parse.go @@ -0,0 +1,15 @@ +package testutils + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func ParseTxDetails(t *testing.T, in interface{}) []types.TxDetails { + out, err := types.MakeTxDetails(in) + require.NoError(t, err) + return out +} diff --git a/pkg/monitoring/types/examples.go b/pkg/monitoring/types/examples.go new file mode 100644 index 000000000..9331b4d85 --- /dev/null +++ b/pkg/monitoring/types/examples.go @@ -0,0 +1,8 @@ +package types + +import "github.com/gagliardetto/solana-go" + +var ( + SampleTxResultProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ") + SampleTxResultJSON = `{"blockTime":1712887149,"meta":{"computeUnitsConsumed":64949,"err":null,"fee":5000,"innerInstructions":[{"index":1,"instructions":[{"accounts":[2,4],"data":"6y43XFem5gk9n8ESJ4pGFboagJiimTtvvy2VCjAUur3y","programIdIndex":3,"stackHeight":2}]}],"loadedAddresses":{"readonly":[],"writable":[]},"logMessages":["Program ComputeBudget111111111111111111111111111111 invoke [1]","Program ComputeBudget111111111111111111111111111111 success","Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ invoke [1]","Program data: gjbLTR5rT6hW4eUAAAN30/iLBm0GRKxe6y9hGtvvKCPLmscA16aVgw6AKe17ouFpAAAAAAAAAAAAAAAAA2uVGGYEAwECAAAAAAAAAAAAAAAAAAAAAKom6kICAAAAsr0AAAAAAAA=","Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny invoke [2]","Program log: Instruction: Submit","Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny consumed 4427 of 140121 compute units","Program HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny success","Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ consumed 64799 of 199850 compute units","Program cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ success"],"postBalances":[1019067874127,49054080,2616960,1141440,0,0,1141440,1],"postTokenBalances":[],"preBalances":[1019067879127,49054080,2616960,1141440,0,0,1141440,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"slot":291748793,"transaction":{"message":{"accountKeys":["9YR7YttJFfptQJSo5xrnYoAw1fJyVonC1vxUSqzAgyjY","Ghm1a2c2NGPg6pKGG3PP1GLAuJkHm1RKMPqqJwPM7JpJ","HXoZZBWv25N4fm2vfSKnHXTeDJ31qaAcWZe3ZKeM6dQv","HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny","3u6T92C2x18s39a7WNM8NGaQK1YEtstTtqamZGsLvNZN","Sysvar1nstructions1111111111111111111111111","cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ","ComputeBudget111111111111111111111111111111"],"header":{"numReadonlySignedAccounts":0,"numReadonlyUnsignedAccounts":5,"numRequiredSignatures":1},"instructions":[{"accounts":[],"data":"3DTZbgwsozUF","programIdIndex":7,"stackHeight":null},{"accounts":[1,0,2,3,4,5],"data":"4W4pS7SH6dugDLwXWijhmW3dGTP7WENQa9vbUjvati1j95ghou2jUJHxvPUoowhZk2bHk21uKk4uFRQrpVF5e54NejQLtAT4DeZPC8n3QudjXhAHgBvFjYvDZDhCKRBK4nvdysDh7aKSE4nb3RiampwUo4u5WsKFfXYZnzbn8edC6jwuJVju1DczQPiLuzuCUps99C8rxwE9XkonGMrjc3Pj4cArMggk5fitRkfdaUn4mGRXDHzPFSg63YTZEn7tnnJd8pWEu9v9H8wBKcN1ptLiY5QmKSnayRcfYvd8MZ9wWf8bD7iVGSNUnwJToyFBVyBNabibozthXSDNmxr3yz1uR9vE3HFq6C2i1LX32a2aqZWzJjmvgdVNfNZZxqDxR6GvWYMw35","programIdIndex":6,"stackHeight":null}],"recentBlockhash":"BKUsMxK39LcgXKm8j5LuYyhig2kgQtRBkxR89szEzaSU"},"signatures":["2eEb8FeJyhczELJ3XKc6yvNLi3jYoC9vdpaR6WUN5vJ3f15ZV1d7LGZZqrqseQFEedgE4cxwcd3S3jYLmvJWBrNg"]}}` +) diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go new file mode 100644 index 000000000..ce526e8d8 --- /dev/null +++ b/pkg/monitoring/types/txdetails.go @@ -0,0 +1,146 @@ +package types + +import ( + "errors" + "fmt" + + solanaGo "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + + "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" +) + +var ( + TxDetailsType = "txdetails" + + ReportObservationMetric = "report_observations" +) + +type TxDetails struct { + Err interface{} + Fee uint64 + Slot uint64 + + Sender solanaGo.PublicKey + + // report information - only supports single report per tx + ObservationCount uint8 +} + +func (td TxDetails) Empty() bool { + return td.Fee == 0 && + td.Slot == 0 && + td.Sender == solanaGo.PublicKey{} && + td.ObservationCount == 0 +} + +// MakeTxDetails casts an interface to []TxDetails +func MakeTxDetails(in interface{}) ([]TxDetails, error) { + out, ok := (in).([]TxDetails) + if !ok { + return nil, fmt.Errorf("Unable to make type []TxDetails from %T", in) + } + return out, nil +} + +// ParseTxResult parses the GetTransaction RPC response +func ParseTxResult(txResult *rpc.GetTransactionResult, programAddr solanaGo.PublicKey) (TxDetails, error) { + if txResult == nil { + return TxDetails{}, fmt.Errorf("txResult is nil") + } + if txResult.Meta == nil { + return TxDetails{}, fmt.Errorf("txResult.Meta is nil") + } + if txResult.Transaction == nil { + return TxDetails{}, fmt.Errorf("txResult.Transaction is nil") + } + + // get original tx + tx, err := txResult.Transaction.GetTransaction() + if err != nil { + return TxDetails{}, fmt.Errorf("GetTransaction: %w", err) + } + + details, err := ParseTx(tx, programAddr) + if err != nil { + return TxDetails{}, fmt.Errorf("ParseTx: %w", err) + } + + // append more details from tx meta + details.Err = txResult.Meta.Err + details.Fee = txResult.Meta.Fee + details.Slot = txResult.Slot + return details, nil +} + +// ParseTx parses a solana transaction +func ParseTx(tx *solanaGo.Transaction, programAddr solanaGo.PublicKey) (TxDetails, error) { + if tx == nil { + return TxDetails{}, fmt.Errorf("tx is nil") + } + + // determine sender + // if more than 1 tx signature, then it is not a data feed report tx from a CL node -> ignore + if len(tx.Signatures) != 1 || len(tx.Message.AccountKeys) == 0 { + return TxDetails{}, fmt.Errorf("invalid number of signatures") + } + // from docs: https://solana.com/docs/rpc/json-structures#transactions + // A list of base-58 encoded signatures applied to the transaction. + // The list is always of length message.header.numRequiredSignatures and not empty. + // The signature at index i corresponds to the public key at index i in message.accountKeys. + sender := tx.Message.AccountKeys[0] + + // CL node DF transactions should only have a compute budget + ocr2 instruction + if len(tx.Message.Instructions) != 2 { + return TxDetails{}, fmt.Errorf("not a node transaction") + } + + var obsCount uint8 + var totalErr error + var foundTransmit bool + var foundFee bool + for _, instruction := range tx.Message.Instructions { + // protect against invalid index + if int(instruction.ProgramIDIndex) >= len(tx.Message.AccountKeys) { + continue + } + + // find OCR2 transmit instruction at specified program address + if tx.Message.AccountKeys[instruction.ProgramIDIndex] == programAddr { + // parse report from tx data (see solana/transmitter.go) + start := solana.StoreNonceLen + solana.ReportContextLen + end := start + int(solana.ReportLen) + report := types.Report(instruction.Data[start:end]) + count, err := solana.ReportCodec{}.ObserversCountFromReport(report) + if err != nil { + totalErr = errors.Join(totalErr, fmt.Errorf("%w (%+v)", err, instruction)) + continue + } + obsCount = count + foundTransmit = true + continue + } + + // find compute budget program instruction + if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.COMPUTE_BUDGET_PROGRAM) { + // future: parsing fee calculation + foundFee = true + } + } + if totalErr != nil { + return TxDetails{}, totalErr + } + + // if missing either instruction, return error + if !foundTransmit || !foundFee { + return TxDetails{}, fmt.Errorf("unable to parse both Transmit and Fee instructions") + } + + return TxDetails{ + Sender: sender, + ObservationCount: obsCount, + }, nil +} diff --git a/pkg/monitoring/types/txdetails_test.go b/pkg/monitoring/types/txdetails_test.go new file mode 100644 index 000000000..e18d482d2 --- /dev/null +++ b/pkg/monitoring/types/txdetails_test.go @@ -0,0 +1,86 @@ +package types + +import ( + "encoding/json" + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + sampleTxResultSigner = solana.MustPublicKeyFromBase58("9YR7YttJFfptQJSo5xrnYoAw1fJyVonC1vxUSqzAgyjY") + + sampleTxResult = rpc.GetTransactionResult{} +) + +func init() { + if err := json.Unmarshal([]byte(SampleTxResultJSON), &sampleTxResult); err != nil { + panic("unable to unmarshal sampleTxResult") + } +} + +func TestParseTxResult(t *testing.T) { + // nil transaction result + _, err := ParseTxResult(nil, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult is nil") + // nil tx result meta + _, err = ParseTxResult(&rpc.GetTransactionResult{}, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult.Meta") + // nil tx result transaction + _, err = ParseTxResult(&rpc.GetTransactionResult{ + Meta: &rpc.TransactionMeta{}, + }, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult.Transaction") + + // happy path + res, err := ParseTxResult(&sampleTxResult, SampleTxResultProgram) + require.NoError(t, err) + + assert.Equal(t, nil, res.Err) + assert.Equal(t, uint64(5000), res.Fee) +} + +func TestParseTx(t *testing.T) { + _, err := ParseTx(nil, SampleTxResultProgram) + require.ErrorContains(t, err, "tx is nil") + + tx, err := sampleTxResult.Transaction.GetTransaction() + require.NoError(t, err) + require.NotNil(t, tx) + + txMissingSig := *tx // copy + txMissingSig.Signatures = []solana.Signature{} + _, err = ParseTx(&txMissingSig, SampleTxResultProgram) + require.ErrorContains(t, err, "invalid number of signatures") + + txMissingAccounts := *tx // copy + txMissingAccounts.Message.AccountKeys = []solana.PublicKey{} + _, err = ParseTx(&txMissingAccounts, SampleTxResultProgram) + require.ErrorContains(t, err, "invalid number of signatures") + + prevIndex := tx.Message.Instructions[1].ProgramIDIndex + txInvalidProgramIndex := *tx // copy + txInvalidProgramIndex.Message.Instructions[1].ProgramIDIndex = 100 // index 1 is ocr transmit call + out, err := ParseTx(&txInvalidProgramIndex, SampleTxResultProgram) + require.Error(t, err) + tx.Message.Instructions[1].ProgramIDIndex = prevIndex // reset - something shares memory underneath + + // don't match program + out, err = ParseTx(tx, solana.PublicKey{}) + require.Error(t, err) + + // happy path + out, err = ParseTx(tx, SampleTxResultProgram) + require.NoError(t, err) + assert.Equal(t, sampleTxResultSigner, out.Sender) + assert.Equal(t, uint8(4), out.ObservationCount) + + // multiple instructions - currently not the case + txMultipleTransmit := *tx + txMultipleTransmit.Message.Instructions = append(tx.Message.Instructions, tx.Message.Instructions[1]) + out, err = ParseTx(&txMultipleTransmit, SampleTxResultProgram) + require.Error(t, err) +} diff --git a/pkg/solana/report.go b/pkg/solana/report.go index 416786cd5..572ac6023 100644 --- a/pkg/solana/report.go +++ b/pkg/solana/report.go @@ -81,7 +81,7 @@ func (c ReportCodec) BuildReport(oo []median.ParsedAttributedObservation) (types func (c ReportCodec) MedianFromReport(report types.Report) (*big.Int, error) { // report should contain timestamp + observers + median + juels per eth if len(report) != int(ReportLen) { - return nil, fmt.Errorf("report length missmatch: %d (received), %d (expected)", len(report), ReportLen) + return nil, fmt.Errorf("report length mismatch: %d (received), %d (expected)", len(report), ReportLen) } // unpack median observation @@ -94,3 +94,15 @@ func (c ReportCodec) MedianFromReport(report types.Report) (*big.Int, error) { func (c ReportCodec) MaxReportLength(n int) (int, error) { return int(ReportLen), nil } + +func (c ReportCodec) ObserversCountFromReport(report types.Report) (uint8, error) { + // report should contain timestamp + observers + median + juels per eth + if len(report) != int(ReportLen) { + return 0, fmt.Errorf("report length mismatch: %d (received), %d (expected)", len(report), ReportLen) + } + + // unpack observers count + start := int(TimestampLen) + end := start + int(ObsCountLen) + return report[start:end][0], nil +} diff --git a/pkg/solana/report_test.go b/pkg/solana/report_test.go index 6209ddf85..2ae298e18 100644 --- a/pkg/solana/report_test.go +++ b/pkg/solana/report_test.go @@ -159,6 +159,9 @@ func TestMedianFromReport(t *testing.T) { med, err := cdc.MedianFromReport(report) require.NoError(t, err) assert.Equal(t, tc.expectedMedian.String(), med.String()) + count, err := cdc.ObserversCountFromReport(report) + require.NoError(t, err) + assert.Equal(t, len(tc.obs), int(count)) }) } diff --git a/pkg/solana/types.go b/pkg/solana/types.go index cf8189200..99fbb6cad 100644 --- a/pkg/solana/types.go +++ b/pkg/solana/types.go @@ -21,13 +21,20 @@ const ( // ReportLen data (61 bytes) MedianLen uint64 = 16 JuelsLen uint64 = 8 - ReportHeaderLen uint64 = 4 + 1 + 32 // timestamp (uint32) + number of observers (uint8) + observer array [32]uint8 + TimestampLen uint64 = 4 // timestamp (uint32) + ObsCountLen uint64 = 1 // number of observers (uint8) + ObsArrLen uint64 = 32 // observer array [32] + ReportHeaderLen uint64 = TimestampLen + ObsCountLen + ObsArrLen ReportLen uint64 = ReportHeaderLen + MedianLen + JuelsLen // MaxOracles is the maximum number of oracles that can be stored onchain MaxOracles = 19 // MaxOffchainConfigLen is the maximum byte length for the encoded offchainconfig MaxOffchainConfigLen = 4096 + + // Additional lengths for data packed into tx (transmitter.go) + StoreNonceLen = 1 + ReportContextLen = 3 * 32 // https://github.com/smartcontractkit/chainlink-common/blob/acef4a2b681f9e05bffd70d212ceee1ea1e526dd/pkg/utils/report.go#L12 ) // State is the struct representing the contract state