Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions chain_capabilities/evm/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 11 additions & 6 deletions chain_capabilities/evm/actions/write_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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))
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down Expand Up @@ -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)
}

Expand Down
8 changes: 3 additions & 5 deletions chain_capabilities/evm/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
17 changes: 6 additions & 11 deletions chain_capabilities/evm/go.sum

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

26 changes: 24 additions & 2 deletions chain_capabilities/evm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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()...)))
}

Expand Down Expand Up @@ -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)
Comment thread
DylanTinianov marked this conversation as resolved.

// TODO: add org resolver
capabilityID := fmt.Sprintf("%s (%d)", c.id, cfg.ChainID)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions chain_capabilities/solana/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Expand Down
Loading
Loading