Skip to content

Commit

Permalink
SOM: transaction cost (#669)
Browse files Browse the repository at this point in the history
* parse compute unit price from tx details

* fee metrics

* wip: exporters

* average calculation + overflow checking

* use avg from common

* update to pinned common
  • Loading branch information
aalu1418 committed Apr 23, 2024
1 parent b9eefe1 commit 5c6f400
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 52 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/pelletier/go-toml/v2 v2.1.1
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.17.0
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419161010-71a18c1ab9a2
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596
github.com/smartcontractkit/libocr v0.0.0-20240326191951-2bbe9382d052
github.com/stretchr/testify v1.9.0
go.uber.org/multierr v1.11.0
Expand Down Expand Up @@ -50,6 +50,7 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -456,8 +456,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419161010-71a18c1ab9a2 h1:1gkiOu6GfzfeDu6XyYB0G9A6HYsz1lDWx8gM1jReSqI=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419161010-71a18c1ab9a2/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596 h1:Wo+i2cPSO8eBQ3wgJFi1rBuh01n4yHcVZTkb1hYXti4=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks=
github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss=
github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU=
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/lib/pq v1.10.9
github.com/onsi/gomega v1.30.0
github.com/rs/zerolog v1.30.0
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419205832-845fa69af8d9
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596
github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240422131138-ec3bcf1a0821
github.com/smartcontractkit/chainlink-testing-framework v1.28.3
github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240422131834-9efe7cab4c4d
Expand Down
4 changes: 2 additions & 2 deletions integration-tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1412,8 +1412,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq
github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE=
github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs=
github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419205832-845fa69af8d9 h1:elDIBChe7ByPNvCyrSjMLTPKrgY+sKgzzlWe2p3wokY=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240419205832-845fa69af8d9/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596 h1:Wo+i2cPSO8eBQ3wgJFi1rBuh01n4yHcVZTkb1hYXti4=
github.com/smartcontractkit/chainlink-common v0.1.7-0.20240422215914-3b758c48e596/go.mod h1:GTDBbovHUSAUk+fuGIySF2A/whhdtHGaWmU61BoERks=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419131812-73d148593d92 h1:MvaNzuaQh1vX4CAYLM8qFd99cf0ZF1JNwtDZtLU7WvU=
github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240419131812-73d148593d92/go.mod h1:uATrrJ8IsuBkOBJ46USuf73gz9gZy5k5bzGE5/ji/rc=
github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=
Expand Down
97 changes: 97 additions & 0 deletions pkg/monitoring/exporter/fees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package exporter

import (
"context"

commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring"
"github.com/smartcontractkit/chainlink-common/pkg/utils/mathutil"

"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics"
"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"
)

func NewFeesFactory(
log commonMonitoring.Logger,
metrics metrics.Fees,
) commonMonitoring.ExporterFactory {
return &feesFactory{
log,
metrics,
}
}

type feesFactory struct {
log commonMonitoring.Logger
metrics metrics.Fees
}

func (p *feesFactory) NewExporter(
params commonMonitoring.ExporterParams,
) (commonMonitoring.Exporter, error) {
return &feesExporter{
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 feesExporter struct {
label metrics.FeedInput // static for each feed
log commonMonitoring.Logger
metrics metrics.Fees
}

func (f *feesExporter) 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
}

// calculate average of non empty TxDetails
var feeArr []uint64
var computeUnitsArr []fees.ComputeUnitPrice
for _, d := range details {
if d.Empty() {
continue
}
feeArr = append(feeArr, d.Fee)
computeUnitsArr = append(computeUnitsArr, d.ComputeUnitPrice)
}
if len(feeArr) == 0 || len(computeUnitsArr) == 0 {
f.log.Errorf("exporter could not find non-empty TxDetails")
return
}

fee, err := mathutil.Avg(feeArr...)
if err != nil {
f.log.Errorf("fee average: %w", err)
return
}
computeUnits, err := mathutil.Avg(computeUnitsArr...)
if err != nil {
f.log.Errorf("computeUnits average: %w", err)
return
}

f.metrics.Set(fee, computeUnits, f.label)
}

func (f *feesExporter) Cleanup(_ context.Context) {
f.metrics.Cleanup(f.label)
}
53 changes: 53 additions & 0 deletions pkg/monitoring/exporter/fees_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"
)

func TestFees(t *testing.T) {
ctx := utils.Context(t)
lgr, logs := logger.TestObserved(t, zapcore.ErrorLevel)
m := mocks.NewFees(t)
m.On("Set", mock.Anything, mock.Anything, mock.Anything).Once()
m.On("Cleanup", mock.Anything).Once()

factory := NewFeesFactory(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{{Fee: 1, ComputeUnitPrice: 1}})
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 return average
// skip empty
m.On("Set", uint64(1), fees.ComputeUnitPrice(10), mock.Anything).Once()
exporter.Export(ctx, []types.TxDetails{{}, {Fee: 2}, {ComputeUnitPrice: 20}})
}
39 changes: 39 additions & 0 deletions pkg/monitoring/metrics/fees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package metrics

import (
commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring"

"github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"
)

//go:generate mockery --name Fees --output ./mocks/

type Fees interface {
Set(txFee uint64, computeUnitPrice fees.ComputeUnitPrice, feedInput FeedInput)
Cleanup(feedInput FeedInput)
}

var _ Fees = (*feeMetrics)(nil)

type feeMetrics struct {
txFee simpleGauge
computeUnit simpleGauge
}

func NewFees(log commonMonitoring.Logger) *feeMetrics {
return &feeMetrics{
txFee: newSimpleGauge(log, types.TxFeeMetric),
computeUnit: newSimpleGauge(log, types.ComputeUnitPriceMetric),
}
}

func (sh *feeMetrics) Set(txFee uint64, computeUnitPrice fees.ComputeUnitPrice, feedInput FeedInput) {
sh.txFee.set(float64(txFee), feedInput.ToPromLabels())
sh.computeUnit.set(float64(computeUnitPrice), feedInput.ToPromLabels())
}

func (sh *feeMetrics) Cleanup(feedInput FeedInput) {
sh.txFee.delete(feedInput.ToPromLabels())
sh.computeUnit.delete(feedInput.ToPromLabels())
}
45 changes: 45 additions & 0 deletions pkg/monitoring/metrics/fees_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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"
"github.com/smartcontractkit/chainlink-solana/pkg/solana/fees"
)

func TestFees(t *testing.T) {
lgr := logger.Test(t)
m := NewFees(lgr)

// fetching gauges
gFees, ok := gauges[types.TxFeeMetric]
require.True(t, ok)
gComputeUnits, ok := gauges[types.ComputeUnitPriceMetric]
require.True(t, ok)

v0 := 1
v1 := 10
l := FeedInput{NetworkID: t.Name()}

// set gauge
assert.NotPanics(t, func() {
m.Set(uint64(v0), fees.ComputeUnitPrice(v1), l)
})
num := testutil.ToFloat64(gFees.With(l.ToPromLabels()))
assert.Equal(t, float64(v0), num)
num = testutil.ToFloat64(gComputeUnits.With(l.ToPromLabels()))
assert.Equal(t, float64(v1), num)

// cleanup gauges
assert.Equal(t, 1, testutil.CollectAndCount(gFees))
assert.Equal(t, 1, testutil.CollectAndCount(gComputeUnits))
assert.NotPanics(t, func() { m.Cleanup(l) })
assert.Equal(t, 0, testutil.CollectAndCount(gFees))
assert.Equal(t, 0, testutil.CollectAndCount(gComputeUnits))
}
16 changes: 9 additions & 7 deletions pkg/monitoring/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,15 @@ func init() {
nodeLabels,
)

// init gauge for observation count tracking
gauges[types.ReportObservationMetric] = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: types.ReportObservationMetric,
},
feedLabels,
)
// init gauges for tx details tracking
for _, txDetailMetric := range types.TxDetailsMetrics {
gauges[txDetailMetric] = promauto.NewGaugeVec(
prometheus.GaugeOpts{
Name: txDetailMetric,
},
feedLabels,
)
}

// init gauge for slot height
gauges[types.SlotHeightMetric] = promauto.NewGaugeVec(
Expand Down
40 changes: 40 additions & 0 deletions pkg/monitoring/metrics/mocks/Fees.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/monitoring/types/examples.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import "github.com/gagliardetto/solana-go"

var (
sampleTxResultSigner = solana.MustPublicKeyFromBase58("9YR7YttJFfptQJSo5xrnYoAw1fJyVonC1vxUSqzAgyjY")
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"]}}`
)

0 comments on commit 5c6f400

Please sign in to comment.