diff --git a/chain_capabilities/evm/actions/actions.go b/chain_capabilities/evm/actions/actions.go index 2678c2b22..2a1903f9e 100644 --- a/chain_capabilities/evm/actions/actions.go +++ b/chain_capabilities/evm/actions/actions.go @@ -31,6 +31,7 @@ import ( valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" capcommon "github.com/smartcontractkit/capabilities/chain_capabilities/common" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/evm/config" "github.com/smartcontractkit/capabilities/chain_capabilities/evm/internal/contracts" @@ -44,6 +45,7 @@ type EVM struct { types.EVMService consensusHandler chainconsensus.RequestHandler chainSelector uint64 + gasMeter *gasmeter.Meter keystoneForwarderAddress common.Address forwarderClient contracts.CREForwarderClient ReceiverGasMinimum uint64 @@ -95,6 +97,12 @@ func NewEVM(cfg config.Config, evmService types.EVMService, lggr logger.Logger, return e, nil } +// WithGasMeter enables cre:workflow:gas usage MeterRecords for write reports. +// A nil meter disables emission. +func (e *EVM) WithGasMeter(m *gasmeter.Meter) { + e.gasMeter = m +} + func (e *EVM) initLimiters(limitsFactory limits.Factory) (err error) { e.readPayloadSizeLimiter, err = limits.MakeUpperBoundLimiter(limitsFactory, cresettings.Default.PerWorkflow.ChainRead.PayloadSizeLimit) if err != nil { diff --git a/chain_capabilities/evm/actions/write_report.go b/chain_capabilities/evm/actions/write_report.go index 61ab5a0bb..418b4f9ae 100644 --- a/chain_capabilities/evm/actions/write_report.go +++ b/chain_capabilities/evm/actions/write_report.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" capcommon "github.com/smartcontractkit/capabilities/chain_capabilities/common" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/evm/internal/contracts" @@ -52,6 +53,8 @@ type WriteReport struct { writeReportL1FeeActive limits.RangeLimiter[commoncfg.Timestamp] transmissionScheduler ts.TransmissionScheduler executionTimestamp time.Time + + gasMeter *gasmeter.Meter } func (e *EVM) WriteReport(ctx context.Context, metadata capabilities.RequestMetadata, input *evm.WriteReportRequest) (*capabilities.ResponseAndMetadata[*evm.WriteReportReply], caperrors.Error) { @@ -97,6 +100,7 @@ func (e *EVM) executeWriteReport(ctx context.Context, request *evm.WriteReportRe writeReportL1FeeActive: e.writeReportL1FeeActive, transmissionScheduler: e.transmissionScheduler, executionTimestamp: metadata.ExecutionTimestamp, + gasMeter: e.gasMeter, } return wr.executeWriteReport(ctx, request, metadata, telemetryContext) @@ -147,7 +151,7 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write if err != nil { return nil, capabilities.ResponseMetadata{}, err } - return reply, e.meteringFromReply(reply), nil + return reply, e.meteringFromReply(ctx, metadata, reply), nil case contracts.TransmissionStateInvalidReceiver: txHash, err := txHashRetriever.GetFailedTransmissionHash(ctx) if err != nil { @@ -166,7 +170,7 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write // though the receipt/fee lookup failed (e.g. flaky RPC), as a user error. return nil, capabilities.ResponseMetadata{}, revertReplyBuildError(transmissionInfo, transmissionID, err) } - return reply, e.meteringFromReply(reply), nil + return reply, e.meteringFromReply(ctx, metadata, reply), nil case contracts.TransmissionStateFailed: hadEnoughGas, calculatedReceiverGasBudget := e.attemptHadEnoughGas(request, transmissionInfo) if hadEnoughGas { @@ -191,7 +195,7 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write // reason even if the receipt/fee lookup failed, as a user error. return nil, capabilities.ResponseMetadata{}, revertReplyBuildError(transmissionInfo, transmissionID, err) } - return reply, e.meteringFromReply(reply), nil + return reply, e.meteringFromReply(ctx, metadata, reply), nil } monitoring.LogAndEmitSuccess(ctx, "Retrying a failed transmission after prior attempt had insufficient receiver gas", e.lggr, e.beholderProcessor, e.messageBuilder.BuildWriteReportInsufficientGasRetry(telemetryContext, request, calculatedReceiverGasBudget, transmissionInfo.GasLimit, queuePosition)) @@ -250,7 +254,7 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write if err != nil { return nil, capabilities.ResponseMetadata{}, err } - return reply, e.meteringFromReply(reply), nil + return reply, e.meteringFromReply(ctx, metadata, reply), nil case contracts.TransmissionStateFailed, contracts.TransmissionStateInvalidReceiver: txHash := &transactionResult.TxHash // if this is a re-attempt find the original failed tx hash @@ -274,7 +278,7 @@ func (e *WriteReport) executeWriteReport(ctx context.Context, request *evm.Write // user learns the cause even if the receipt/fee lookup failed, as a user error. return nil, capabilities.ResponseMetadata{}, revertReplyBuildError(newTransmissionInfo, transmissionID, err) } - return reply, e.meteringFromReply(reply), nil + return reply, e.meteringFromReply(ctx, metadata, reply), nil default: errorMsg := getInvalidStateErrorMessage(newTransmissionInfo.State) monitoring.LogAndEmitError(ctx, e.lggr, e.beholderProcessor, e.messageBuilder.BuildWriteReportInvalidTransmissionState(telemetryContext, request, newTransmissionInfo, fmt.Sprintf("WriteReport invalid transmission state with tx status: %d", transactionResult.TxStatus), errorMsg)) @@ -529,12 +533,13 @@ func (e *WriteReport) replyFromReceipt(ctx context.Context, txHash evmtypes.Hash // in the reply, so that every node reports the gas spent on chain, regardless of which node // transmitted. Deriving it from the reply guarantees the metered fee matches the reply's // TransactionFee. An absent fee yields empty metadata. -func (e *WriteReport) meteringFromReply(reply *evm.WriteReportReply) capabilities.ResponseMetadata { +func (e *WriteReport) meteringFromReply(ctx context.Context, metadata capabilities.RequestMetadata, reply *evm.WriteReportReply) capabilities.ResponseMetadata { feeInWei := pb.NewIntFromBigInt(reply.TransactionFee) if feeInWei == nil { e.lggr.Warnw("Transaction fee unavailable in reply; skipping metering", "txHash", hex.EncodeToString(reply.TxHash)) return capabilities.ResponseMetadata{} } + e.gasMeter.Emit(ctx, metadata, hex.EncodeToString(reply.TxHash), feeInWei) return metering.GetResponseMetadataWriteReport(feeInWei, e.chainSelector) } diff --git a/chain_capabilities/evm/go.mod b/chain_capabilities/evm/go.mod index 024513016..6b1cb9d9e 100644 --- a/chain_capabilities/evm/go.mod +++ b/chain_capabilities/evm/go.mod @@ -5,10 +5,10 @@ go 1.27.1 require ( github.com/ethereum/go-ethereum v1.17.4 github.com/google/go-cmp v0.7.0 - github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866 + github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961 github.com/smartcontractkit/chain-selectors v1.0.112 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464 github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 @@ -50,7 +50,6 @@ require ( github.com/dchest/siphash v1.2.3 // indirect github.com/deckarep/golang-set/v2 v2.9.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect - github.com/dominikbraun/graph v0.23.0 // indirect github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.7 // indirect github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect @@ -142,7 +141,7 @@ require ( github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260917115705-1d3a14a9b049 // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 // indirect github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260724153515-bb6a2de39bcb // indirect github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260723212800-b2f21d31b1d2 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20260512230622-65f10f4cd305 // indirect @@ -207,7 +206,6 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect ) replace github.com/fbsobreira/gotron-sdk => github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20250528121202-292529af39df diff --git a/chain_capabilities/evm/go.sum b/chain_capabilities/evm/go.sum index 3b21db683..66d66b550 100644 --- a/chain_capabilities/evm/go.sum +++ b/chain_capabilities/evm/go.sum @@ -91,8 +91,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3h github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= -github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo= -github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -190,7 +188,6 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -455,18 +452,18 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866 h1:nJ3V1j6QSFJ3zfwTO8Ty5TVNEb7g4HUkr9VXXV7hLSg= -github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866/go.mod h1:URIXl+ew48QapONMSLC1faCcGRnm5CRL3jj1PtpJt5E= +github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c h1:R8cKOczBWJ31Spy7ii/+Im269YeWbk6oJ33vDd6cJ3Q= +github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c/go.mod h1:ubztvmJnKJilCw4CzrlHSkKvPYXNeUArML/xY1VzsBg= github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961 h1:2+0wOWEiYcEm0XlS1HwgH09y/3qZ0zjokTdbExY2VQQ= github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961/go.mod h1:h4kazMLsDM6CsKl2OnT9AtCHhhRsFptA/OTM7Hkj2GE= github.com/smartcontractkit/chain-selectors v1.0.112 h1:3VO+ttHsqGzPviMmb1AsIT5+rXVgz/Xx6wmvTpamrzE= github.com/smartcontractkit/chain-selectors v1.0.112/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1 h1:1rGIv4vUt+Et7k5waRbkODOQsMuEakQ8TA9PiMBaPss= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1/go.mod h1:UkZJ6uUU9Hlonfb+k62Pg4yRuvOsoShoOd4tPEB/qpE= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464 h1:DVXwyIGekGqLaFAcSWa3K3S209gAY0WDFwliYdLp1dk= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464/go.mod h1:37Mu0pbAnGmHPgOZ8bQvgP4hzXw/Wu7smKX4cN46UOs= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260917115705-1d3a14a9b049 h1:nQ2bw8QQLT+AuCE6JUVIRT1RAo08wQHR9jWh+MgT2GA= github.com/smartcontractkit/chainlink-common/keystore v1.3.1-0.20260917115705-1d3a14a9b049/go.mod h1:FO9igruzwHPXstyb9J9z83sJ19iLSNSIg+0H/n0wlJU= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744 h1:n+V23NCROMeWFHUyR78Z4dXkUnPV3K8RzOzJpnXuXV0= github.com/smartcontractkit/chainlink-evm v0.3.4-0.20260917132551-2a2b38e8b744/go.mod h1:dEk48o9w++AyLCv12fNKIJf4Ko6iywWWJAGrjfS0vR8= github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20260512150409-b4068bf735e6 h1:JFo7C3FilwhfwGBLAyj2umbL+P4QxGmVi/b8yt9kqvI= @@ -837,5 +834,3 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/chain_capabilities/evm/main.go b/chain_capabilities/evm/main.go index 868f0a4be..833218732 100644 --- a/chain_capabilities/evm/main.go +++ b/chain_capabilities/evm/main.go @@ -9,6 +9,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + chainselectors "github.com/smartcontractkit/chain-selectors" ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types" @@ -22,6 +23,7 @@ import ( "github.com/smartcontractkit/capabilities/libs/chainconsensus/oracle" "github.com/smartcontractkit/capabilities/libs/chainconsensus/poller" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/evm/actions" "github.com/smartcontractkit/capabilities/chain_capabilities/evm/config" @@ -34,6 +36,7 @@ import ( evmcapserver "github.com/smartcontractkit/chainlink-common/pkg/capabilities/v2/chain-capabilities/evm/server" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/resourcemanager" "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/core" @@ -57,10 +60,16 @@ type capabilityGRPCService struct { capability lggr logger.Logger limitsFactory limits.Factory + + // meteringCfg comes from the LOOP environment ([Metering] on the host). + // gasMeter emits cre:workflow:gas usage records for write reports when + // MeterRecordsEnabled; nil otherwise. + meteringCfg resourcemanager.Config } type capability struct { *actions.EVM + gasMeter *gasmeter.Meter id string requestPoller *poller.Poller consensusHandler chainconsensus.Handler @@ -73,7 +82,11 @@ var _ evmcapserver.ClientCapability = &capabilityGRPCService{} func main() { loopserver.ServeNew(CapabilityName, func(s *loop.Server) loop.StandardCapabilities { - return evmcapserver.NewClientServer(&capabilityGRPCService{lggr: s.Logger, limitsFactory: s.LimitsFactory}) + return evmcapserver.NewClientServer(&capabilityGRPCService{ + lggr: s.Logger, + limitsFactory: s.LimitsFactory, + meteringCfg: s.MeteringConfig(), + }) }, loop.WithOtelViews(append(consMetrics.MetricViews(), monitoring.MetricViews()...))) } @@ -169,6 +182,8 @@ func (c *capabilityGRPCService) Initialise(ctx context.Context, dependencies cor if err != nil { return fmt.Errorf("failed to init evm relayer for chainID %d from relayer: %w", cfg.ChainID, err) } + c.gasMeter = gasmeter.New(c.lggr, c.meteringCfg, c.chainSelector, capabilityDonID) + c.WithGasMeter(c.gasMeter) // TODO: add org resolver capabilityID := fmt.Sprintf("%s (%d)", c.id, cfg.ChainID) @@ -199,6 +214,9 @@ func (c *capabilityGRPCService) Initialise(ctx context.Context, dependencies cor } startServices := []interface{ Start(context.Context) error }{c.consensusHandler, c.requestPoller, c.oracle, c.heightProvider, c.triggerService} + if c.gasMeter != nil { + startServices = append(startServices, c.gasMeter) + } for _, service := range startServices { if err := service.Start(ctx); err != nil { return err @@ -263,7 +281,11 @@ func (c *capabilityGRPCService) Start(_ context.Context) error { func (c *capabilityGRPCService) Close() error { c.lggr.Infof("Closing %s", CapabilityName) - return errors.Join(c.EVM.Close(), c.requestPoller.Close(), c.consensusHandler.Close(), c.oracle.Close(context.Background()), c.triggerService.Close(), c.heightProvider.Close()) + errs := errors.Join(c.EVM.Close(), c.requestPoller.Close(), c.consensusHandler.Close(), c.oracle.Close(context.Background()), c.triggerService.Close(), c.heightProvider.Close()) + if c.gasMeter != nil { + errs = errors.Join(errs, c.gasMeter.Close()) + } + return errs } func (c *capabilityGRPCService) HealthReport() map[string]error { diff --git a/chain_capabilities/solana/actions/actions.go b/chain_capabilities/solana/actions/actions.go index e92fa84b4..f42a503d6 100644 --- a/chain_capabilities/solana/actions/actions.go +++ b/chain_capabilities/solana/actions/actions.go @@ -34,6 +34,7 @@ import ( valuespb "github.com/smartcontractkit/chainlink-protos/cre/go/values/pb" capcommon "github.com/smartcontractkit/capabilities/chain_capabilities/common" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/config" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/monitoring" @@ -55,6 +56,13 @@ type Solana struct { transmissionScheduler ts.TransmissionScheduler handler chainconsensus.RequestHandler forwarderState solgo.PublicKey + gasMeter *gasmeter.Meter +} + +// WithGasMeter enables cre:workflow:gas usage MeterRecords for write reports. +// A nil meter disables emission. +func (s *Solana) WithGasMeter(m *gasmeter.Meter) { + s.gasMeter = m } func NewSolana(ctx context.Context, cfg *config.Config, s types.SolanaService, messageBuilder *monitoring.MessageBuilder, diff --git a/chain_capabilities/solana/actions/write_report.go b/chain_capabilities/solana/actions/write_report.go index be79c0e23..0d6043618 100644 --- a/chain_capabilities/solana/actions/write_report.go +++ b/chain_capabilities/solana/actions/write_report.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "errors" "fmt" + "math/big" "strings" "time" @@ -29,6 +30,7 @@ import ( ocrtypes "github.com/smartcontractkit/chainlink-common/pkg/capabilities/consensus/ocr3/types" capcommon "github.com/smartcontractkit/capabilities/chain_capabilities/common" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/metering" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/monitoring" @@ -68,6 +70,8 @@ type WriteReport struct { txComputeLimit limits.BoundLimiter[uint32] reportSizeLimit limits.BoundLimiter[commoncfg.Size] transmissionScheduler ts.TransmissionScheduler + + gasMeter *gasmeter.Meter } func (s *Solana) WriteReport( @@ -122,6 +126,7 @@ func (s *Solana) executeWriteReport(ctx context.Context, request *solcap.WriteRe beholderProcessor: s.beholderProcessor, messageBuilder: s.messageBuilder, transmissionScheduler: s.transmissionScheduler, + gasMeter: s.gasMeter, } return wr.executeWriteReport(ctx, request, telemetryContext, metadata) @@ -618,6 +623,7 @@ func (wr *WriteReport) meteringFromTxSignature(ctx context.Context, telemetryCon monitoring.LogAndEmitError(ctx, wr.lggr, wr.beholderProcessor, wr.messageBuilder.BuildWriteReportTxFeeCalculationError(telemetryContext, request, sig, err.Error())) return capabilities.ResponseMetadata{} } + wr.gasMeter.Emit(ctx, telemetryContext.RequestMetadata, sig.String(), new(big.Int).SetUint64(feeInLamports)) return metering.GetResponseMetadataWriteReport(feeInLamports, wr.chainSelector) } diff --git a/chain_capabilities/solana/go.mod b/chain_capabilities/solana/go.mod index 62b1dc738..6670b8f4b 100644 --- a/chain_capabilities/solana/go.mod +++ b/chain_capabilities/solana/go.mod @@ -6,10 +6,10 @@ require ( github.com/gagliardetto/solana-go v1.23.0 github.com/google/go-cmp v0.7.0 github.com/mr-tron/base58 v1.2.0 - github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866 + github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961 github.com/smartcontractkit/chain-selectors v1.0.104 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464 github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260821155228-fa1d775e2138 github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260917175559-f7d4a86019d4 @@ -131,7 +131,7 @@ require ( github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260317185256-d5f7db87ae70 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288 // indirect github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 // indirect - github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 // indirect github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260521164805-26d78d5e1243 // indirect github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b // indirect github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect diff --git a/chain_capabilities/solana/go.sum b/chain_capabilities/solana/go.sum index e8476d0ce..611a9f60b 100644 --- a/chain_capabilities/solana/go.sum +++ b/chain_capabilities/solana/go.sum @@ -622,8 +622,8 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866 h1:nJ3V1j6QSFJ3zfwTO8Ty5TVNEb7g4HUkr9VXXV7hLSg= -github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260922114021-5cee95428866/go.mod h1:URIXl+ew48QapONMSLC1faCcGRnm5CRL3jj1PtpJt5E= +github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c h1:R8cKOczBWJ31Spy7ii/+Im269YeWbk6oJ33vDd6cJ3Q= +github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20261009182120-372f846f528c/go.mod h1:ubztvmJnKJilCw4CzrlHSkKvPYXNeUArML/xY1VzsBg= github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961 h1:2+0wOWEiYcEm0XlS1HwgH09y/3qZ0zjokTdbExY2VQQ= github.com/smartcontractkit/capabilities/libs v0.0.0-20261001142216-b1dea8dbb961/go.mod h1:h4kazMLsDM6CsKl2OnT9AtCHhhRsFptA/OTM7Hkj2GE= github.com/smartcontractkit/chain-selectors v1.0.104 h1:/n9pPGM5W/+r1eHoWZv4VwX9LNS1af4+ICyhM8zKRNM= @@ -634,10 +634,10 @@ github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8 github.com/smartcontractkit/chainlink-ccip/chains/solana v0.0.0-20260310183131-8d0f0e383288/go.mod h1:jPHlo/IN2YAArI001JJixmm6ZHQwgnAVJXY8VBFiFTc= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288 h1:eEjTgIQn4RW0ZPRepUDYTdgGwaRCMawMwgXkHItUc9U= github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings v0.0.0-20260310183131-8d0f0e383288/go.mod h1:67YbnoglYD61Pz/jTVCgav9wFq7S35OU8UyQSvPllRw= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1 h1:1rGIv4vUt+Et7k5waRbkODOQsMuEakQ8TA9PiMBaPss= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260925190414-e1b87ad2d7b1/go.mod h1:UkZJ6uUU9Hlonfb+k62Pg4yRuvOsoShoOd4tPEB/qpE= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92 h1:Z1OIGukDhJD+Gknr5G07Vtml24E7BwKaIQoWzV/SdqA= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260915184316-2730f1867c92/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464 h1:DVXwyIGekGqLaFAcSWa3K3S209gAY0WDFwliYdLp1dk= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20261006175240-29594528f464/go.mod h1:37Mu0pbAnGmHPgOZ8bQvgP4hzXw/Wu7smKX4cN46UOs= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549 h1:J4vYRJxkBO8Yf//17wScKjYWmS45VZaFWx0zt+4ETEU= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20261005132539-b7d63d5fd549/go.mod h1:Scyk7O80fde5rJF3Ty3fEE3xiS6j84eWbu8/2gl3pIY= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340 h1:PsjEI+5jZIz9AS4eOsLS5VpSWJINf38clXV3wryPyMk= github.com/smartcontractkit/chainlink-common/pkg/monitoring v0.0.0-20251215152504-b1e41f508340/go.mod h1:P/0OSXUlFaxxD4B/P6HWbxYtIRmmWGDJAvanq19879c= github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260521164805-26d78d5e1243 h1:vaFBupfFfImQgqOeuC7Muk2GflbYP6Gpi0Y/TLroFU8= diff --git a/chain_capabilities/solana/main.go b/chain_capabilities/solana/main.go index bcc3ecead..bc30fa81b 100644 --- a/chain_capabilities/solana/main.go +++ b/chain_capabilities/solana/main.go @@ -20,12 +20,14 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/capabilities" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop" + "github.com/smartcontractkit/chainlink-common/pkg/resourcemanager" "github.com/smartcontractkit/chainlink-common/pkg/services" "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/types/core" capcommon "github.com/smartcontractkit/capabilities/chain_capabilities/common" + "github.com/smartcontractkit/capabilities/chain_capabilities/common/gasmeter" ts "github.com/smartcontractkit/capabilities/chain_capabilities/common/transmission_schedule" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/actions" "github.com/smartcontractkit/capabilities/chain_capabilities/solana/config" @@ -55,6 +57,12 @@ type capabilityGRPCService struct { lggr logger.Logger limitsFactory limits.Factory triggerService *trigger.SolanaLogTriggerService + + // meteringCfg comes from the LOOP environment ([Metering] on the host). + // gasMeter emits cre:workflow:gas usage records for write reports when + // MeterRecordsEnabled; nil otherwise. + meteringCfg resourcemanager.Config + gasMeter *gasmeter.Meter } type capability struct { @@ -69,7 +77,11 @@ var _ solcapserver.ClientCapability = &capabilityGRPCService{} func main() { loopserver.ServeNew(CapabilityName, func(s *loop.Server) loop.StandardCapabilities { - return solcapserver.NewClientServer(&capabilityGRPCService{lggr: s.Logger.Named(CapabilityName), limitsFactory: s.LimitsFactory}) + return solcapserver.NewClientServer(&capabilityGRPCService{ + lggr: s.Logger.Named(CapabilityName), + limitsFactory: s.LimitsFactory, + meteringCfg: s.MeteringConfig(), + }) }, loop.WithOtelViews(append(consMetrics.MetricViews(), capmon.MetricViews()...))) } @@ -109,6 +121,9 @@ func (c *capabilityGRPCService) Close() error { if c.Solana != nil { closers = append(closers, c.Solana) } + if c.gasMeter != nil { + closers = append(closers, c.gasMeter) + } return services.CloseAll(closers...) } @@ -240,6 +255,11 @@ func (c *capabilityGRPCService) Initialise(ctx context.Context, dependencies cor if err != nil { return err } + c.gasMeter = gasmeter.New(c.lggr, c.meteringCfg, c.chainSelector, dependencies.CapabilityDonID) + c.WithGasMeter(c.gasMeter) + if c.gasMeter != nil { + toStart = append(toStart, c.gasMeter) + } c.triggerService, err = trigger.NewLogTriggerService(trigger.LogTriggerServiceOpts{ SolanaService: solService,