From 3386b76fd70775ce11d6fe3e1353d01084b86eff Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Thu, 11 Apr 2024 19:29:41 -0600 Subject: [PATCH 01/10] wip: parsing tx details framework --- pkg/monitoring/source_txdetails.go | 81 ++++++++++++++++++++++++++++++ pkg/monitoring/source_txresults.go | 13 +++-- pkg/monitoring/types/txdetails.go | 60 ++++++++++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 pkg/monitoring/source_txdetails.go create mode 100644 pkg/monitoring/types/txdetails.go diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go new file mode 100644 index 000000000..2c6ef7d6c --- /dev/null +++ b/pkg/monitoring/source_txdetails.go @@ -0,0 +1,81 @@ +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(cfg commonMonitoring.Params) (commonMonitoring.Source, error) { + solanaFeedConfig, ok := cfg.FeedConfig.(config.SolanaFeedConfig) + if !ok { + return nil, fmt.Errorf("expected feedConfig to be of type config.SolanaFeedConfig not %T", cfg.FeedConfig) + } + + return &txDetailsSource{ + client: f.client, + sigSource: &txResultsSource{ + client: f.client, + log: f.log, + feedConfig: solanaFeedConfig, + }, + }, nil +} + +func (f *txDetailsSourceFactory) GetType() string { + return types.TxDetailsType +} + +type txDetailsSource struct { + client ChainReader + sigSource *txResultsSource // reuse underlying logic for getting signatures +} + +func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { + _, sigs, err := s.sigSource.fetch(ctx) + if err != nil { + return types.TxDetails{}, err + } + if len(sigs) == 0 { + return types.TxDetails{}, nil + } + + for _, sig := range sigs { + if sig == nil { + continue // skip for nil signatures + } + + // TODO: async? + tx, err := s.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{}) + if err != nil { + return types.TxDetails{}, err + } + if tx == nil { + return types.TxDetails{}, fmt.Errorf("GetTransaction returned nil") + } + + // TODO: parse transaction + + // TODO: filter signatures/transactions based on known operator/sender + + // TODO: parse observations from remaining transactions + + // TODO: add to proper list for averaging + } + + return types.TxDetails{}, nil +} diff --git a/pkg/monitoring/source_txresults.go b/pkg/monitoring/source_txresults.go index 331cfbe30..a18cb4f64 100644 --- a/pkg/monitoring/source_txresults.go +++ b/pkg/monitoring/source_txresults.go @@ -61,7 +61,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 +80,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 +98,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/types/txdetails.go b/pkg/monitoring/types/txdetails.go new file mode 100644 index 000000000..87bd87296 --- /dev/null +++ b/pkg/monitoring/types/txdetails.go @@ -0,0 +1,60 @@ +package types + +import ( + "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/gagliardetto/solana-go/rpc" +) + +var ( + TxDetailsType = "txdetails" +) + +// TxDetails expands on TxResults and contains additional detail on a set of tx signatures specific to solana +type TxDetails struct { + count int // total signatures processed + + // TODO: PerOperator categorizes TxResults based on sender/operator + // PerOperator map[string]commonMonitoring.TxResults + + // observation counts within each report + obsLatest int // number of observations in latest included report/tx + obsAvg int // average number of observations across all seen txs/reports from operators + obsSuccessAvg int // average number of observations included in successful reports + obsFailedAvg int // average number of observations included in failed reports + + // TODO: implement - parse fee using shared logic from fee/computebudget.go + // feeAvg + // feeSuccessAvg + // feeFailedAvg +} + +type ParsedTx struct { + Err interface{} + Fee uint64 + + Sender solana.PublicKey + + // report information + ObservationCount int +} + +func ParseTx(txResult *rpc.GetTransactionResult) (ParsedTx, error) { + out := ParsedTx{} + if txResult == nil { + return out, fmt.Errorf("txResult is nil") + } + if txResult.Meta == nil { + return out, fmt.Errorf("txResult.Meta is nil") + } + + out.Err = txResult.Meta.Err + out.Fee = txResult.Meta.Fee + + // determine sender + + // find OCR2 transmit instruction + + return out, nil +} From cc97bfff62fc2b39676a709686dd9cc2b9bde1af Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:45:51 -0600 Subject: [PATCH 02/10] WIP: parsing logic --- pkg/monitoring/source_txdetails.go | 4 +- pkg/monitoring/types/txdetails.go | 65 +++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 2c6ef7d6c..2f76bc172 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -26,6 +26,8 @@ func (f *txDetailsSourceFactory) NewSource(cfg commonMonitoring.Params) (commonM return nil, fmt.Errorf("expected feedConfig to be of type config.SolanaFeedConfig not %T", cfg.FeedConfig) } + // TODO: build map for looking up nodes + return &txDetailsSource{ client: f.client, sigSource: &txResultsSource{ @@ -60,7 +62,7 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { } // TODO: async? - tx, err := s.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{}) + tx, err := s.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{Commitment: "confirmed"}) if err != nil { return types.TxDetails{}, err } diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index 87bd87296..f127b4ad0 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -34,27 +34,74 @@ type ParsedTx struct { Err interface{} Fee uint64 - Sender solana.PublicKey + Sender solana.PublicKey + Operator string // human readable name associated to public key // report information ObservationCount int } -func ParseTx(txResult *rpc.GetTransactionResult) (ParsedTx, error) { - out := ParsedTx{} +func ParseTx(txResult *rpc.GetTransactionResult, nodes map[solana.PublicKey]string) (ParsedTx, error) { if txResult == nil { - return out, fmt.Errorf("txResult is nil") + return ParsedTx{}, fmt.Errorf("txResult is nil") } if txResult.Meta == nil { - return out, fmt.Errorf("txResult.Meta is nil") + return ParsedTx{}, fmt.Errorf("txResult.Meta is nil") + } + if txResult.Transaction == nil { + return ParsedTx{}, fmt.Errorf("txResult.Transaction is nil") } - out.Err = txResult.Meta.Err - out.Fee = txResult.Meta.Fee + // get original tx + tx, err := txResult.Transaction.GetTransaction() + if err != nil { + return ParsedTx{}, fmt.Errorf("GetTransaction: %w", err) + } + if tx == nil { + return ParsedTx{}, fmt.Errorf("GetTransaction returned 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 ParsedTx{}, nil + } + // 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] + + // if sender matches a known node/operator + sending to feed account, parse transmit tx data + // node transmit calls the fallback which is difficult to filter + if _, ok := nodes[sender]; !ok { + return ParsedTx{}, nil + } + + var found bool // found transmit instruction + for _, instruction := range tx.Message.Instructions { + // protect against invalid index + if int(instruction.ProgramIDIndex) >= len(tx.Message.AccountKeys) { + continue + } + + // find OCR2 transmit instruction + // TODO: fix hardcoding OCR2 program ID - SolanaFeedConfig.ContractAddress + if !found && // only can find one transmit instruction + tx.Message.AccountKeys[instruction.ProgramIDIndex].String() == "cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ" { + found = true - // find OCR2 transmit instruction + // TODO: parse report + } + + // TODO: parsing fee calculation + // ComputeBudget111111111111111111111111111111 + } - return out, nil + return ParsedTx{ + Err: txResult.Meta.Err, + Fee: txResult.Meta.Fee, + Sender: sender, + Operator: nodes[sender], + }, nil } From d760789659b6b008de0b1882e3203198410fdcda Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Mon, 15 Apr 2024 07:07:34 -0600 Subject: [PATCH 03/10] wip: parsing observation counts from transactions --- pkg/monitoring/config/feed_config.go | 2 +- pkg/monitoring/source_txdetails.go | 31 +++++--- pkg/monitoring/source_txresults.go | 1 + pkg/monitoring/types/txdetails.go | 106 +++++++++++++++++-------- pkg/monitoring/types/txdetails_test.go | 102 ++++++++++++++++++++++++ pkg/solana/report.go | 14 +++- pkg/solana/report_test.go | 3 + pkg/solana/types.go | 9 ++- 8 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 pkg/monitoring/types/txdetails_test.go 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/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 2f76bc172..06244cdde 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" @@ -29,8 +30,7 @@ func (f *txDetailsSourceFactory) NewSource(cfg commonMonitoring.Params) (commonM // TODO: build map for looking up nodes return &txDetailsSource{ - client: f.client, - sigSource: &txResultsSource{ + source: &txResultsSource{ client: f.client, log: f.log, feedConfig: solanaFeedConfig, @@ -43,12 +43,13 @@ func (f *txDetailsSourceFactory) GetType() string { } type txDetailsSource struct { - client ChainReader - sigSource *txResultsSource // reuse underlying logic for getting signatures + nodes map[solana.PublicKey]string // track pubkey to operator name + + source *txResultsSource // reuse underlying logic for getting signatures } func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { - _, sigs, err := s.sigSource.fetch(ctx) + _, sigs, err := s.source.fetch(ctx) if err != nil { return types.TxDetails{}, err } @@ -56,13 +57,14 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { return types.TxDetails{}, nil } + details := types.TxDetails{} for _, sig := range sigs { if sig == nil { continue // skip for nil signatures } // TODO: async? - tx, err := s.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{Commitment: "confirmed"}) + tx, err := s.source.client.GetTransaction(ctx, sig.Signature, &rpc.GetTransactionOpts{Commitment: "confirmed"}) if err != nil { return types.TxDetails{}, err } @@ -70,14 +72,17 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { return types.TxDetails{}, fmt.Errorf("GetTransaction returned nil") } - // TODO: parse transaction - - // TODO: filter signatures/transactions based on known operator/sender - - // TODO: parse observations from remaining transactions + // parse transaction + filter based on known senders + res, err := types.ParseTxResult(tx, s.nodes, s.source.feedConfig.ContractAddress) + if err != nil { + // skip invalid transaction + s.source.log.Debugw("tx was not valid for tracking", "error", err, "signature", sig) + continue + } - // TODO: add to proper list for averaging + // append to TxDetails + details.Count += 1 } - return types.TxDetails{}, nil + return details, nil } diff --git a/pkg/monitoring/source_txresults.go b/pkg/monitoring/source_txresults.go index a18cb4f64..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" ) diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index f127b4ad0..a5e4507b8 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -1,10 +1,17 @@ package types import ( + "errors" "fmt" - "github.com/gagliardetto/solana-go" + solanaGo "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" + + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + "github.com/smartcontractkit/libocr/offchainreporting2/types" + + "github.com/smartcontractkit/chainlink-solana/pkg/solana" + "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" ) var ( @@ -13,35 +20,41 @@ var ( // TxDetails expands on TxResults and contains additional detail on a set of tx signatures specific to solana type TxDetails struct { - count int // total signatures processed + Count int // total signatures processed - // TODO: PerOperator categorizes TxResults based on sender/operator - // PerOperator map[string]commonMonitoring.TxResults + // PerOperator categorizes TxResults based on sender/operator + PerOperator map[string]commonMonitoring.TxResults // observation counts within each report - obsLatest int // number of observations in latest included report/tx - obsAvg int // average number of observations across all seen txs/reports from operators - obsSuccessAvg int // average number of observations included in successful reports - obsFailedAvg int // average number of observations included in failed reports + latestSet bool + ObsLatest uint8 // number of observations in latest included report/tx + ObsAll []int // observations across all seen txs/reports from operators + ObsSuccess []int // observations included in successful reports + ObsFailed []int // observations included in failed reports // TODO: implement - parse fee using shared logic from fee/computebudget.go - // feeAvg - // feeSuccessAvg - // feeFailedAvg + // FeeAvg + // FeeSuccessAvg + // FeeFailedAvg +} + +func (d TxDetails) SetLatest(obs uint8) { + } type ParsedTx struct { Err interface{} Fee uint64 - Sender solana.PublicKey + Sender solanaGo.PublicKey Operator string // human readable name associated to public key - // report information - ObservationCount int + // report information - can support batched with slice + ObservationCount []uint8 } -func ParseTx(txResult *rpc.GetTransactionResult, nodes map[solana.PublicKey]string) (ParsedTx, error) { +// ParseTxResult parses the GetTransaction RPC response +func ParseTxResult(txResult *rpc.GetTransactionResult, nodes map[solanaGo.PublicKey]string, programAddr solanaGo.PublicKey) (ParsedTx, error) { if txResult == nil { return ParsedTx{}, fmt.Errorf("txResult is nil") } @@ -51,20 +64,36 @@ func ParseTx(txResult *rpc.GetTransactionResult, nodes map[solana.PublicKey]stri if txResult.Transaction == nil { return ParsedTx{}, fmt.Errorf("txResult.Transaction is nil") } - // get original tx tx, err := txResult.Transaction.GetTransaction() if err != nil { return ParsedTx{}, fmt.Errorf("GetTransaction: %w", err) } + + details, err := ParseTx(tx, nodes, programAddr) + if err != nil { + return ParsedTx{}, fmt.Errorf("ParseTx: %w", err) + } + + // append more details from tx meta + details.Err = txResult.Meta.Err + details.Fee = txResult.Meta.Fee + return details, nil +} + +// ParseTx parses a solana transaction +func ParseTx(tx *solanaGo.Transaction, nodes map[solanaGo.PublicKey]string, programAddr solanaGo.PublicKey) (ParsedTx, error) { if tx == nil { - return ParsedTx{}, fmt.Errorf("GetTransaction returned nil") + return ParsedTx{}, fmt.Errorf("tx is nil") + } + if nodes == nil { + return ParsedTx{}, fmt.Errorf("nodes 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 ParsedTx{}, nil + return ParsedTx{}, 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. @@ -73,35 +102,42 @@ func ParseTx(txResult *rpc.GetTransactionResult, nodes map[solana.PublicKey]stri sender := tx.Message.AccountKeys[0] // if sender matches a known node/operator + sending to feed account, parse transmit tx data - // node transmit calls the fallback which is difficult to filter + // node transmit calls the fallback which is difficult to filter for the function call if _, ok := nodes[sender]; !ok { - return ParsedTx{}, nil + return ParsedTx{}, fmt.Errorf("unknown public key: %s", sender) } - var found bool // found transmit instruction + obsCount := []uint8{} + var totalErr error for _, instruction := range tx.Message.Instructions { // protect against invalid index if int(instruction.ProgramIDIndex) >= len(tx.Message.AccountKeys) { continue } - // find OCR2 transmit instruction - // TODO: fix hardcoding OCR2 program ID - SolanaFeedConfig.ContractAddress - if !found && // only can find one transmit instruction - tx.Message.AccountKeys[instruction.ProgramIDIndex].String() == "cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ" { - found = true - - // TODO: parse report + // 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 = append(obsCount, count) } - // TODO: parsing fee calculation - // ComputeBudget111111111111111111111111111111 + // find compute budget program instruction + if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.COMPUTE_BUDGET_PROGRAM) { + // TODO: parsing fee calculation + } } return ParsedTx{ - Err: txResult.Meta.Err, - Fee: txResult.Meta.Fee, - Sender: sender, - Operator: nodes[sender], - }, nil + Sender: sender, + Operator: nodes[sender], + ObservationCount: obsCount, + }, totalErr } diff --git a/pkg/monitoring/types/txdetails_test.go b/pkg/monitoring/types/txdetails_test.go new file mode 100644 index 000000000..2f51c16a2 --- /dev/null +++ b/pkg/monitoring/types/txdetails_test.go @@ -0,0 +1,102 @@ +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") + sampleTxResultProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ") + sampleTxResultNodes = map[solana.PublicKey]string{ + sampleTxResultSigner: "test-signer", + } + + sampleTxResult = rpc.GetTransactionResult{} + 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"]}}` +) + +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, map[solana.PublicKey]string{}, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult is nil") + // nil tx result meta + _, err = ParseTxResult(&rpc.GetTransactionResult{}, map[solana.PublicKey]string{}, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult.Meta") + // nil tx result transaction + _, err = ParseTxResult(&rpc.GetTransactionResult{ + Meta: &rpc.TransactionMeta{}, + }, map[solana.PublicKey]string{}, solana.PublicKey{}) + require.ErrorContains(t, err, "txResult.Transaction") + + // happy path + res, err := ParseTxResult(&sampleTxResult, sampleTxResultNodes, 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, sampleTxResultNodes, sampleTxResultProgram) + require.ErrorContains(t, err, "tx is nil") + + _, err = ParseTx(&solana.Transaction{}, nil, sampleTxResultProgram) + require.ErrorContains(t, err, "nodes 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, sampleTxResultNodes, sampleTxResultProgram) + require.ErrorContains(t, err, "invalid number of signatures") + + txMissingAccounts := *tx // copy + txMissingAccounts.Message.AccountKeys = []solana.PublicKey{} + _, err = ParseTx(&txMissingAccounts, sampleTxResultNodes, sampleTxResultProgram) + require.ErrorContains(t, err, "invalid number of signatures") + + _, err = ParseTx(tx, map[solana.PublicKey]string{}, sampleTxResultProgram) + require.ErrorContains(t, err, "unknown public key") + + 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, sampleTxResultNodes, sampleTxResultProgram) + require.NoError(t, err) + assert.Equal(t, 0, len(out.ObservationCount)) + tx.Message.Instructions[1].ProgramIDIndex = prevIndex // reset - something shares memory underneath + + // don't match program + out, err = ParseTx(tx, sampleTxResultNodes, solana.PublicKey{}) + require.NoError(t, err) + assert.Equal(t, 0, len(out.ObservationCount)) + + // happy path + out, err = ParseTx(tx, sampleTxResultNodes, sampleTxResultProgram) + require.NoError(t, err) + assert.Equal(t, sampleTxResultSigner, out.Sender) + assert.Equal(t, sampleTxResultNodes[sampleTxResultSigner], out.Operator) + assert.Equal(t, 1, len(out.ObservationCount)) + assert.Equal(t, uint8(4), out.ObservationCount[0]) + + // multiple decodable instructions - currently not the case + txMultipleTransmit := *tx + txMultipleTransmit.Message.Instructions = append(tx.Message.Instructions, tx.Message.Instructions[1]) + out, err = ParseTx(&txMultipleTransmit, sampleTxResultNodes, sampleTxResultProgram) + require.NoError(t, err) + assert.Equal(t, 2, len(out.ObservationCount)) +} 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 From b7255a070ab09d22f989ce8c4a5aacbe98f0cc90 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Mon, 15 Apr 2024 08:35:42 -0600 Subject: [PATCH 04/10] sort data into bucket --- pkg/monitoring/source_txdetails.go | 28 +++++++++++++++++++++++++++- pkg/monitoring/types/txdetails.go | 26 ++++++++++---------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 06244cdde..2df9881ef 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -27,9 +27,22 @@ func (f *txDetailsSourceFactory) NewSource(cfg commonMonitoring.Params) (commonM return nil, fmt.Errorf("expected feedConfig to be of type config.SolanaFeedConfig not %T", cfg.FeedConfig) } - // TODO: build map for looking up nodes + // build map for looking up node pubkey -> operator name + nodes := map[solana.PublicKey]string{} + solanaNodeConfigs, err := config.MakeSolanaNodeConfigs(cfg.Nodes) + if err != nil { + return nil, fmt.Errorf("MakeSolanaNodeConfigs: %w", err) + } + for _, c := range solanaNodeConfigs { + key, err := c.PublicKey() + if err != nil { + return nil, fmt.Errorf("Could not parse public key (%s: %s): %w", c.GetName(), c.GetAccount(), err) + } + nodes[key] = c.GetName() + } return &txDetailsSource{ + nodes: nodes, source: &txResultsSource{ client: f.client, log: f.log, @@ -82,6 +95,19 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { // append to TxDetails details.Count += 1 + + // signatures are ordered with the latest first + if res.Err == nil && details.ObsLatest == 0 { + details.ObsLatest = res.ObservationCount // only supports single feed result + } + + if res.Err != nil { + details.ObsFailed = append(details.ObsFailed, res.ObservationCount) + } else { + details.ObsSuccess = append(details.ObsSuccess, res.ObservationCount) + } + details.ObsAll = append(details.ObsAll, res.ObservationCount) + } return details, nil diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index a5e4507b8..8fb9fdb54 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -7,7 +7,6 @@ import ( solanaGo "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" - commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" "github.com/smartcontractkit/libocr/offchainreporting2/types" "github.com/smartcontractkit/chainlink-solana/pkg/solana" @@ -22,15 +21,14 @@ var ( type TxDetails struct { Count int // total signatures processed - // PerOperator categorizes TxResults based on sender/operator - PerOperator map[string]commonMonitoring.TxResults + // TODO: PerOperator categorizes TxResults based on sender/operator + // PerOperator map[string]commonMonitoring.TxResults // observation counts within each report - latestSet bool - ObsLatest uint8 // number of observations in latest included report/tx - ObsAll []int // observations across all seen txs/reports from operators - ObsSuccess []int // observations included in successful reports - ObsFailed []int // observations included in failed reports + ObsLatest uint8 // number of observations in latest included report/tx + ObsAll []uint8 // observations across all seen txs/reports from operators + ObsSuccess []uint8 // observations included in successful reports + ObsFailed []uint8 // observations included in failed reports // TODO: implement - parse fee using shared logic from fee/computebudget.go // FeeAvg @@ -38,10 +36,6 @@ type TxDetails struct { // FeeFailedAvg } -func (d TxDetails) SetLatest(obs uint8) { - -} - type ParsedTx struct { Err interface{} Fee uint64 @@ -49,8 +43,8 @@ type ParsedTx struct { Sender solanaGo.PublicKey Operator string // human readable name associated to public key - // report information - can support batched with slice - ObservationCount []uint8 + // report information - only supports single report per tx + ObservationCount uint8 } // ParseTxResult parses the GetTransaction RPC response @@ -107,7 +101,7 @@ func ParseTx(tx *solanaGo.Transaction, nodes map[solanaGo.PublicKey]string, prog return ParsedTx{}, fmt.Errorf("unknown public key: %s", sender) } - obsCount := []uint8{} + var obsCount uint8 var totalErr error for _, instruction := range tx.Message.Instructions { // protect against invalid index @@ -126,7 +120,7 @@ func ParseTx(tx *solanaGo.Transaction, nodes map[solanaGo.PublicKey]string, prog totalErr = errors.Join(totalErr, fmt.Errorf("%w (%+v)", err, instruction)) continue } - obsCount = append(obsCount, count) + obsCount = count } // find compute budget program instruction From d4b049e9c4e06a2fa2b1717249afc2e3781ca345 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:14:10 -0600 Subject: [PATCH 05/10] txdetails source --- pkg/monitoring/source_txdetails.go | 62 ++++------------- pkg/monitoring/source_txdetails_test.go | 88 +++++++++++++++++++++++++ pkg/monitoring/testutils/parse.go | 15 +++++ pkg/monitoring/types/examples.go | 8 +++ pkg/monitoring/types/txdetails.go | 86 ++++++++++++------------ pkg/monitoring/types/txdetails_test.go | 54 ++++++--------- 6 files changed, 187 insertions(+), 126 deletions(-) create mode 100644 pkg/monitoring/source_txdetails_test.go create mode 100644 pkg/monitoring/testutils/parse.go create mode 100644 pkg/monitoring/types/examples.go diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 2df9881ef..ad8f758cb 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" @@ -21,28 +20,13 @@ type txDetailsSourceFactory struct { log commonMonitoring.Logger } -func (f *txDetailsSourceFactory) NewSource(cfg commonMonitoring.Params) (commonMonitoring.Source, error) { - solanaFeedConfig, ok := cfg.FeedConfig.(config.SolanaFeedConfig) +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", cfg.FeedConfig) - } - - // build map for looking up node pubkey -> operator name - nodes := map[solana.PublicKey]string{} - solanaNodeConfigs, err := config.MakeSolanaNodeConfigs(cfg.Nodes) - if err != nil { - return nil, fmt.Errorf("MakeSolanaNodeConfigs: %w", err) - } - for _, c := range solanaNodeConfigs { - key, err := c.PublicKey() - if err != nil { - return nil, fmt.Errorf("Could not parse public key (%s: %s): %w", c.GetName(), c.GetAccount(), err) - } - nodes[key] = c.GetName() + return nil, fmt.Errorf("expected feedConfig to be of type config.SolanaFeedConfig not %T", feedConfig) } return &txDetailsSource{ - nodes: nodes, source: &txResultsSource{ client: f.client, log: f.log, @@ -56,58 +40,40 @@ func (f *txDetailsSourceFactory) GetType() string { } type txDetailsSource struct { - nodes map[solana.PublicKey]string // track pubkey to operator name - 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 types.TxDetails{}, err - } - if len(sigs) == 0 { - return types.TxDetails{}, nil + return nil, err } - details := types.TxDetails{} - for _, sig := range sigs { + details := make([]types.TxDetails, len(sigs)) + for i, sig := range sigs { if sig == nil { continue // skip for nil signatures } - // TODO: async? + // TODO: 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 types.TxDetails{}, err + return nil, err } if tx == nil { - return types.TxDetails{}, fmt.Errorf("GetTransaction returned 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.nodes, s.source.feedConfig.ContractAddress) + res, err := types.ParseTxResult(tx, s.source.feedConfig.ContractAddress) if err != nil { // skip invalid transaction - s.source.log.Debugw("tx was not valid for tracking", "error", err, "signature", sig) + s.source.log.Debugw("tx not valid for tracking", "error", err, "signature", sig) continue } - - // append to TxDetails - details.Count += 1 - - // signatures are ordered with the latest first - if res.Err == nil && details.ObsLatest == 0 { - details.ObsLatest = res.ObservationCount // only supports single feed result - } - - if res.Err != nil { - details.ObsFailed = append(details.ObsFailed, res.ObservationCount) - } else { - details.ObsSuccess = append(details.ObsSuccess, res.ObservationCount) - } - details.ObsAll = append(details.ObsAll, res.ObservationCount) - + details[i] = res } 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..35f9ce309 --- /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, 1, len(data)) + 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, 1, len(data)) + 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/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 index 8fb9fdb54..720494142 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -17,77 +17,66 @@ var ( TxDetailsType = "txdetails" ) -// TxDetails expands on TxResults and contains additional detail on a set of tx signatures specific to solana type TxDetails struct { - Count int // total signatures processed + Err interface{} + Fee uint64 + Slot uint64 - // TODO: PerOperator categorizes TxResults based on sender/operator - // PerOperator map[string]commonMonitoring.TxResults - - // observation counts within each report - ObsLatest uint8 // number of observations in latest included report/tx - ObsAll []uint8 // observations across all seen txs/reports from operators - ObsSuccess []uint8 // observations included in successful reports - ObsFailed []uint8 // observations included in failed reports - - // TODO: implement - parse fee using shared logic from fee/computebudget.go - // FeeAvg - // FeeSuccessAvg - // FeeFailedAvg -} - -type ParsedTx struct { - Err interface{} - Fee uint64 - - Sender solanaGo.PublicKey - Operator string // human readable name associated to public key + Sender solanaGo.PublicKey // report information - only supports single report per tx ObservationCount uint8 } +// 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, nodes map[solanaGo.PublicKey]string, programAddr solanaGo.PublicKey) (ParsedTx, error) { +func ParseTxResult(txResult *rpc.GetTransactionResult, programAddr solanaGo.PublicKey) (TxDetails, error) { if txResult == nil { - return ParsedTx{}, fmt.Errorf("txResult is nil") + return TxDetails{}, fmt.Errorf("txResult is nil") } if txResult.Meta == nil { - return ParsedTx{}, fmt.Errorf("txResult.Meta is nil") + return TxDetails{}, fmt.Errorf("txResult.Meta is nil") } if txResult.Transaction == nil { - return ParsedTx{}, fmt.Errorf("txResult.Transaction is nil") + return TxDetails{}, fmt.Errorf("txResult.Transaction is nil") } + // get original tx tx, err := txResult.Transaction.GetTransaction() if err != nil { - return ParsedTx{}, fmt.Errorf("GetTransaction: %w", err) + return TxDetails{}, fmt.Errorf("GetTransaction: %w", err) } - details, err := ParseTx(tx, nodes, programAddr) + details, err := ParseTx(tx, programAddr) if err != nil { - return ParsedTx{}, fmt.Errorf("ParseTx: %w", err) + 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, nodes map[solanaGo.PublicKey]string, programAddr solanaGo.PublicKey) (ParsedTx, error) { +func ParseTx(tx *solanaGo.Transaction, programAddr solanaGo.PublicKey) (TxDetails, error) { if tx == nil { - return ParsedTx{}, fmt.Errorf("tx is nil") - } - if nodes == nil { - return ParsedTx{}, fmt.Errorf("nodes is 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 ParsedTx{}, fmt.Errorf("invalid number of signatures") + 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. @@ -95,14 +84,15 @@ func ParseTx(tx *solanaGo.Transaction, nodes map[solanaGo.PublicKey]string, prog // The signature at index i corresponds to the public key at index i in message.accountKeys. sender := tx.Message.AccountKeys[0] - // if sender matches a known node/operator + sending to feed account, parse transmit tx data - // node transmit calls the fallback which is difficult to filter for the function call - if _, ok := nodes[sender]; !ok { - return ParsedTx{}, fmt.Errorf("unknown public key: %s", sender) + // 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) { @@ -121,17 +111,27 @@ func ParseTx(tx *solanaGo.Transaction, nodes map[solanaGo.PublicKey]string, prog continue } obsCount = count + foundTransmit = true + continue } // find compute budget program instruction if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.COMPUTE_BUDGET_PROGRAM) { // TODO: 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 ParsedTx{ + return TxDetails{ Sender: sender, - Operator: nodes[sender], ObservationCount: obsCount, - }, totalErr + }, nil } diff --git a/pkg/monitoring/types/txdetails_test.go b/pkg/monitoring/types/txdetails_test.go index 2f51c16a2..e18d482d2 100644 --- a/pkg/monitoring/types/txdetails_test.go +++ b/pkg/monitoring/types/txdetails_test.go @@ -11,37 +11,32 @@ import ( ) var ( - sampleTxResultSigner = solana.MustPublicKeyFromBase58("9YR7YttJFfptQJSo5xrnYoAw1fJyVonC1vxUSqzAgyjY") - sampleTxResultProgram = solana.MustPublicKeyFromBase58("cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ") - sampleTxResultNodes = map[solana.PublicKey]string{ - sampleTxResultSigner: "test-signer", - } + sampleTxResultSigner = solana.MustPublicKeyFromBase58("9YR7YttJFfptQJSo5xrnYoAw1fJyVonC1vxUSqzAgyjY") - sampleTxResult = rpc.GetTransactionResult{} - 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"]}}` + sampleTxResult = rpc.GetTransactionResult{} ) func init() { - if err := json.Unmarshal([]byte(sampleTxResultJSON), &sampleTxResult); err != nil { + 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, map[solana.PublicKey]string{}, solana.PublicKey{}) + _, err := ParseTxResult(nil, solana.PublicKey{}) require.ErrorContains(t, err, "txResult is nil") // nil tx result meta - _, err = ParseTxResult(&rpc.GetTransactionResult{}, map[solana.PublicKey]string{}, solana.PublicKey{}) + _, err = ParseTxResult(&rpc.GetTransactionResult{}, solana.PublicKey{}) require.ErrorContains(t, err, "txResult.Meta") // nil tx result transaction _, err = ParseTxResult(&rpc.GetTransactionResult{ Meta: &rpc.TransactionMeta{}, - }, map[solana.PublicKey]string{}, solana.PublicKey{}) + }, solana.PublicKey{}) require.ErrorContains(t, err, "txResult.Transaction") // happy path - res, err := ParseTxResult(&sampleTxResult, sampleTxResultNodes, sampleTxResultProgram) + res, err := ParseTxResult(&sampleTxResult, SampleTxResultProgram) require.NoError(t, err) assert.Equal(t, nil, res.Err) @@ -49,54 +44,43 @@ func TestParseTxResult(t *testing.T) { } func TestParseTx(t *testing.T) { - _, err := ParseTx(nil, sampleTxResultNodes, sampleTxResultProgram) + _, err := ParseTx(nil, SampleTxResultProgram) require.ErrorContains(t, err, "tx is nil") - _, err = ParseTx(&solana.Transaction{}, nil, sampleTxResultProgram) - require.ErrorContains(t, err, "nodes 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, sampleTxResultNodes, sampleTxResultProgram) + _, err = ParseTx(&txMissingSig, SampleTxResultProgram) require.ErrorContains(t, err, "invalid number of signatures") txMissingAccounts := *tx // copy txMissingAccounts.Message.AccountKeys = []solana.PublicKey{} - _, err = ParseTx(&txMissingAccounts, sampleTxResultNodes, sampleTxResultProgram) + _, err = ParseTx(&txMissingAccounts, SampleTxResultProgram) require.ErrorContains(t, err, "invalid number of signatures") - _, err = ParseTx(tx, map[solana.PublicKey]string{}, sampleTxResultProgram) - require.ErrorContains(t, err, "unknown public key") - 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, sampleTxResultNodes, sampleTxResultProgram) - require.NoError(t, err) - assert.Equal(t, 0, len(out.ObservationCount)) + 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, sampleTxResultNodes, solana.PublicKey{}) - require.NoError(t, err) - assert.Equal(t, 0, len(out.ObservationCount)) + out, err = ParseTx(tx, solana.PublicKey{}) + require.Error(t, err) // happy path - out, err = ParseTx(tx, sampleTxResultNodes, sampleTxResultProgram) + out, err = ParseTx(tx, SampleTxResultProgram) require.NoError(t, err) assert.Equal(t, sampleTxResultSigner, out.Sender) - assert.Equal(t, sampleTxResultNodes[sampleTxResultSigner], out.Operator) - assert.Equal(t, 1, len(out.ObservationCount)) - assert.Equal(t, uint8(4), out.ObservationCount[0]) + assert.Equal(t, uint8(4), out.ObservationCount) - // multiple decodable instructions - currently not the case + // multiple instructions - currently not the case txMultipleTransmit := *tx txMultipleTransmit.Message.Instructions = append(tx.Message.Instructions, tx.Message.Instructions[1]) - out, err = ParseTx(&txMultipleTransmit, sampleTxResultNodes, sampleTxResultProgram) - require.NoError(t, err) - assert.Equal(t, 2, len(out.ObservationCount)) + out, err = ParseTx(&txMultipleTransmit, SampleTxResultProgram) + require.Error(t, err) } From adf2053ce5f47d8cd6c719c2964f266f03df28b2 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:32:44 -0600 Subject: [PATCH 06/10] bump solana-go --- go.mod | 7 +++++-- go.sum | 42 ++++++++++++++++++++++++++++++---------- integration-tests/go.mod | 4 ++-- integration-tests/go.sum | 10 ++++------ 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index c8ed767e9..5340f02ee 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 fe34ef4b0..563622774 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= @@ -368,6 +369,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= @@ -464,6 +466,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= @@ -496,6 +500,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= @@ -503,12 +508,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= @@ -539,6 +552,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= @@ -553,6 +567,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= @@ -563,7 +578,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= @@ -599,6 +615,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= @@ -636,11 +653,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= @@ -710,7 +727,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= @@ -729,11 +747,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= @@ -777,12 +798,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= @@ -908,6 +929,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 805048f25..0b03a56c5 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -7,7 +7,7 @@ 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 @@ -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 6185003ed..80ce6ce48 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= @@ -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= From adfed0518b96b0ec00b193ea62775b733d19fc28 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Tue, 16 Apr 2024 07:45:11 -0600 Subject: [PATCH 07/10] WIP: metrics --- pkg/monitoring/metrics/metrics.go | 16 ++++++++++++---- pkg/monitoring/metrics/txdetails.go | 2 ++ pkg/monitoring/source_txdetails.go | 12 +++++++++--- pkg/monitoring/source_txdetails_test.go | 4 ++-- pkg/monitoring/types/txdetails.go | 9 +++++++++ 5 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 pkg/monitoring/metrics/txdetails.go diff --git a/pkg/monitoring/metrics/metrics.go b/pkg/monitoring/metrics/metrics.go index 0447ac24b..09989b303 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,14 @@ 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, ) } diff --git a/pkg/monitoring/metrics/txdetails.go b/pkg/monitoring/metrics/txdetails.go new file mode 100644 index 000000000..685d4efb7 --- /dev/null +++ b/pkg/monitoring/metrics/txdetails.go @@ -0,0 +1,2 @@ +package metrics + diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index ad8f758cb..9e187c4f1 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -49,12 +49,17 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { return nil, err } - details := make([]types.TxDetails, len(sigs)) - for i, sig := range sigs { + 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 + } + // TODO: 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 { @@ -73,8 +78,9 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { s.source.log.Debugw("tx not valid for tracking", "error", err, "signature", sig) continue } - details[i] = res + details = append(details, res) } + // only return successful OCR2 transmit transactions return details, nil } diff --git a/pkg/monitoring/source_txdetails_test.go b/pkg/monitoring/source_txdetails_test.go index 35f9ce309..509a412dc 100644 --- a/pkg/monitoring/source_txdetails_test.go +++ b/pkg/monitoring/source_txdetails_test.go @@ -50,7 +50,7 @@ func TestTxDetailsSource(t *testing.T) { res, err = s.Fetch(tests.Context(t)) require.NoError(t, err) data = testutils.ParseTxDetails(t, res) - assert.Equal(t, 1, len(data)) + assert.Equal(t, 0, len(data)) // ignores tx assert.Equal(t, 1, logs.FilterLevelExact(zapcore.DebugLevel).FilterMessage("GetTransaction returned nil").Len()) // invalid tx @@ -66,7 +66,7 @@ func TestTxDetailsSource(t *testing.T) { res, err = s.Fetch(tests.Context(t)) require.NoError(t, err) data = testutils.ParseTxDetails(t, res) - assert.Equal(t, 1, len(data)) + 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 diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index 720494142..3cb43a3ce 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -15,6 +15,8 @@ import ( var ( TxDetailsType = "txdetails" + + ReportObservationMetric = "report_observations" ) type TxDetails struct { @@ -28,6 +30,13 @@ type TxDetails struct { 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) From e20560867313b07b7a3df02b379a9c78f02a9e32 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:29:39 -0600 Subject: [PATCH 08/10] share gauge logic + implement report observation metric --- pkg/monitoring/exporter/feedbalances.go | 51 +++++++++---------- pkg/monitoring/exporter/feedbalances_test.go | 43 ++++++++-------- pkg/monitoring/metrics/common.go | 49 ++++++++++++++++++ pkg/monitoring/metrics/common_test.go | 27 ++++++++++ pkg/monitoring/metrics/feedbalances.go | 38 ++++---------- pkg/monitoring/metrics/feedbalances_test.go | 14 ++--- pkg/monitoring/metrics/metrics.go | 18 +++++++ pkg/monitoring/metrics/mocks/FeedBalances.go | 12 ++--- pkg/monitoring/metrics/nodebalances.go | 25 +++------ pkg/monitoring/metrics/reportobservations.go | 32 ++++++++++++ .../metrics/reportobservations_test.go | 35 +++++++++++++ pkg/monitoring/metrics/txdetails.go | 2 - 12 files changed, 238 insertions(+), 108 deletions(-) create mode 100644 pkg/monitoring/metrics/common.go create mode 100644 pkg/monitoring/metrics/common_test.go create mode 100644 pkg/monitoring/metrics/reportobservations.go create mode 100644 pkg/monitoring/metrics/reportobservations_test.go delete mode 100644 pkg/monitoring/metrics/txdetails.go 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/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 09989b303..2768de97a 100644 --- a/pkg/monitoring/metrics/metrics.go +++ b/pkg/monitoring/metrics/metrics.go @@ -65,3 +65,21 @@ func init() { 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/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..f15eea00e --- /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 uint64, 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 uint64, 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..7b7753184 --- /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(uint64(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/metrics/txdetails.go b/pkg/monitoring/metrics/txdetails.go deleted file mode 100644 index 685d4efb7..000000000 --- a/pkg/monitoring/metrics/txdetails.go +++ /dev/null @@ -1,2 +0,0 @@ -package metrics - From 7b14e1a3c54e30ffc75dc5ff1c5796e2edc8c831 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Tue, 16 Apr 2024 14:58:11 -0600 Subject: [PATCH 09/10] report observations exporter --- cmd/monitoring/main.go | 32 +++++++- pkg/monitoring/exporter/reportobservations.go | 82 +++++++++++++++++++ .../exporter/reportobservations_test.go | 50 +++++++++++ .../metrics/mocks/ReportObservations.go | 38 +++++++++ pkg/monitoring/metrics/reportobservations.go | 4 +- .../metrics/reportobservations_test.go | 2 +- pkg/monitoring/source_txdetails.go | 2 +- 7 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 pkg/monitoring/exporter/reportobservations.go create mode 100644 pkg/monitoring/exporter/reportobservations_test.go create mode 100644 pkg/monitoring/metrics/mocks/ReportObservations.go 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/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/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/reportobservations.go b/pkg/monitoring/metrics/reportobservations.go index f15eea00e..f790a4319 100644 --- a/pkg/monitoring/metrics/reportobservations.go +++ b/pkg/monitoring/metrics/reportobservations.go @@ -9,7 +9,7 @@ import ( //go:generate mockery --name ReportObservations --output ./mocks/ type ReportObservations interface { - SetCount(count uint64, feedInput FeedInput) + SetCount(count uint8, feedInput FeedInput) Cleanup(feedInput FeedInput) } @@ -23,7 +23,7 @@ func NewReportObservations(log commonMonitoring.Logger) *reportObservations { return &reportObservations{newSimpleGauge(log, types.ReportObservationMetric)} } -func (ro *reportObservations) SetCount(count uint64, feedInput FeedInput) { +func (ro *reportObservations) SetCount(count uint8, feedInput FeedInput) { ro.set(float64(count), feedInput.ToPromLabels()) } diff --git a/pkg/monitoring/metrics/reportobservations_test.go b/pkg/monitoring/metrics/reportobservations_test.go index 7b7753184..bc888a80d 100644 --- a/pkg/monitoring/metrics/reportobservations_test.go +++ b/pkg/monitoring/metrics/reportobservations_test.go @@ -24,7 +24,7 @@ func TestReportObservations(t *testing.T) { inputs := FeedInput{NetworkName: t.Name()} // set gauge - assert.NotPanics(t, func() { m.SetCount(uint64(v), inputs) }) + assert.NotPanics(t, func() { m.SetCount(uint8(v), inputs) }) promBal := testutil.ToFloat64(g.With(inputs.ToPromLabels())) assert.Equal(t, float64(v), promBal) diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 9e187c4f1..967c4dd9d 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -81,6 +81,6 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { details = append(details, res) } - // only return successful OCR2 transmit transactions + // only return successful OCR2 transmit transactions (slice/array) return details, nil } From 56a0ed22e40e9921d274f2c6614aff88d602a506 Mon Sep 17 00:00:00 2001 From: aalu1418 <50029043+aalu1418@users.noreply.github.com> Date: Tue, 16 Apr 2024 17:24:58 -0600 Subject: [PATCH 10/10] update TODOs --- pkg/monitoring/source_txdetails.go | 2 +- pkg/monitoring/types/txdetails.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/monitoring/source_txdetails.go b/pkg/monitoring/source_txdetails.go index 967c4dd9d..1d13ca256 100644 --- a/pkg/monitoring/source_txdetails.go +++ b/pkg/monitoring/source_txdetails.go @@ -60,7 +60,7 @@ func (s *txDetailsSource) Fetch(ctx context.Context) (interface{}, error) { continue } - // TODO: worker pool - how many GetTransaction requests in a row? + // 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 diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index 3cb43a3ce..ce526e8d8 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -126,7 +126,7 @@ func ParseTx(tx *solanaGo.Transaction, programAddr solanaGo.PublicKey) (TxDetail // find compute budget program instruction if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.COMPUTE_BUDGET_PROGRAM) { - // TODO: parsing fee calculation + // future: parsing fee calculation foundFee = true } }