diff --git a/go.mod b/go.mod index 1de849d35a..8fb97c4ab1 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/smartcontractkit/chainlink-data-streams v0.1.12-0.20260227110503-42b236799872 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1 github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563 - github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260325164211-c77e73c79080 + github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20251020150604-8ab84f7bad1a github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20251021173435-e86785845942 github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260226130359-963f935e0396 diff --git a/go.sum b/go.sum index 5165bf8ea6..ace87d52e2 100644 --- a/go.sum +++ b/go.sum @@ -660,8 +660,8 @@ github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8 github.com/smartcontractkit/chainlink-evm/gethwrappers v0.0.0-20251022073203-7d8ae8cf67c1/go.mod h1:oyfOm4k0uqmgZIfxk1elI/59B02shbbJQiiUdPdbMgI= github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563 h1:ACpDbAxG4fa4sA83dbtYcrnlpE/y7thNIZfHxTv2ZLs= github.com/smartcontractkit/chainlink-framework/capabilities v0.0.0-20250818175541-3389ac08a563/go.mod h1:jP5mrOLFEYZZkl7EiCHRRIMSSHCQsYypm1OZSus//iI= -github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260325164211-c77e73c79080 h1:H1VUXAOzhPOSTQdLHs+eI75SBEjBDwqUkmZPHb6cQ2c= -github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260325164211-c77e73c79080/go.mod h1:kGprqyjsz6qFNVszOQoHc24wfvCjyipNZFste/3zcbs= +github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57 h1:sCrr1Oy/JZstf/Oi2cRuU4mDN1BRUKfXP2CKByCMADg= +github.com/smartcontractkit/chainlink-framework/chains v0.0.0-20260326122810-b657beadfb57/go.mod h1:kGprqyjsz6qFNVszOQoHc24wfvCjyipNZFste/3zcbs= github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20251020150604-8ab84f7bad1a h1:pr0VFI7AWlDVJBEkcvzXWd97V8w8QMNjRdfPVa/IQLk= github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20251020150604-8ab84f7bad1a/go.mod h1:jo+cUqNcHwN8IF7SInQNXDZ8qzBsyMpnLdYbDswviFc= github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20251021173435-e86785845942 h1:T/eCDsUI8EJT4n5zSP4w1mz4RHH+ap8qieA17QYfBhk= diff --git a/pkg/transmitter/dual_contract_transmitter.go b/pkg/transmitter/dual_contract_transmitter.go index 1fa292757b..9e68ebf29d 100644 --- a/pkg/transmitter/dual_contract_transmitter.go +++ b/pkg/transmitter/dual_contract_transmitter.go @@ -3,7 +3,6 @@ package transmitter import ( "context" "database/sql" - "encoding/hex" stderrors "errors" "fmt" "strings" @@ -20,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-evm/pkg/keys" "github.com/smartcontractkit/chainlink-evm/pkg/logpoller" + "github.com/smartcontractkit/chainlink-evm/pkg/txmgr" ) // TODO: Remove when new dual transmitter contracts are merged @@ -120,7 +120,14 @@ func (oc *dualContractTransmitter) Transmit(ctx context.Context, reportCtx ocrty oc.lggr.Warnw("failed to generate tx metadata for report", "err", err) } - oc.lggr.Debugw("Transmitting report", "report", hex.EncodeToString(report), "rawReportCtx", rawReportCtx, "contractAddress", oc.contractAddress, "txMeta", txMeta) + if txMeta == nil { + txMeta = &txmgr.TxMeta{} + } + transactionLifecycleID := generateTransactionLifecycleIDForOCR2(reportCtx.ReportTimestamp) + txMeta.TransactionLifecycleID = &transactionLifecycleID + oc.lggr.Infow("Transmitting report", "configDigest", "0x"+reportCtx.ReportTimestamp.ConfigDigest.Hex(), "epoch", reportCtx.ReportTimestamp.Epoch, "round", reportCtx.ReportTimestamp.Round, "contractAddress", + oc.contractAddress, "txMeta", txMeta, "transactionLifecycleID", transactionLifecycleID) + // Primary transmission payload, err := oc.contractABI.Pack("transmit", rawReportCtx, []byte(report), rs, ss, vs) @@ -129,8 +136,11 @@ func (oc *dualContractTransmitter) Transmit(ctx context.Context, reportCtx ocrty } transactionErr := errors.Wrap(oc.transmitter.CreateEthTransaction(ctx, oc.contractAddress, payload, txMeta), "failed to send primary Eth transaction") - - oc.lggr.Debugw("Created primary transaction", "error", transactionErr) + if transactionErr != nil { + oc.lggr.Errorw("Failed to create primary Eth transaction", "error", transactionErr, "transactionLifecycleID", transactionLifecycleID) + } else { + oc.lggr.Debugw("Created primary transaction", "transactionLifecycleID", transactionLifecycleID) + } // Secondary transmission secondaryPayload, err := oc.dualTransmissionABI.Pack("transmitSecondary", rawReportCtx, []byte(report), rs, ss, vs) @@ -139,7 +149,11 @@ func (oc *dualContractTransmitter) Transmit(ctx context.Context, reportCtx ocrty } err = errors.Wrap(oc.transmitter.CreateSecondaryEthTransaction(ctx, secondaryPayload, txMeta), "failed to send secondary Eth transaction") - oc.lggr.Debugw("Created secondary transaction", "error", err) + if err != nil { + oc.lggr.Errorw("Failed to create secondary Eth transaction", "error", err, "transactionLifecycleID", transactionLifecycleID) + } else { + oc.lggr.Debugw("Created secondary transaction", "transactionLifecycleID", transactionLifecycleID) + } return stderrors.Join(transactionErr, err) } @@ -242,6 +256,10 @@ func (oc *dualContractTransmitter) lockSecondary(ctx context.Context) error { return nil } +func generateTransactionLifecycleIDForOCR2(reportTimestamp ocrtypes.ReportTimestamp) string { + return fmt.Sprintf("0x%s:%d:%d", reportTimestamp.ConfigDigest.Hex(), reportTimestamp.Epoch, reportTimestamp.Round) +} + func (oc *dualContractTransmitter) Start(ctx context.Context) error { return oc.lockTransmitters(ctx) } diff --git a/pkg/transmitter/dual_contract_transmitter_test.go b/pkg/transmitter/dual_contract_transmitter_test.go index 0743485197..80c1b43150 100644 --- a/pkg/transmitter/dual_contract_transmitter_test.go +++ b/pkg/transmitter/dual_contract_transmitter_test.go @@ -147,6 +147,20 @@ func Test_dualContractTransmitter_Transmit_SignaturesAreTransmitted(t *testing.T require.Equal(t, transmitter.lastSecondaryPayload, withSignaturesPayloadSecondary, "secondary payload not equal") } +func Test_generateTracingIDForOCR2(t *testing.T) { + t.Parallel() + + digestBytes, err := hex.DecodeString("000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776") + require.NoError(t, err) + + reportTimestamp := types.ReportTimestamp{Epoch: 42, Round: 7} + copy(reportTimestamp.ConfigDigest[:], digestBytes) + + transactionLifecycleID := generateTransactionLifecycleIDForOCR2(reportTimestamp) + + assert.Equal(t, "0x000130da6b9315bd59af6b0a3f5463c0d0a39e92eaa34cbcbdbace7b3bfcc776:42:7", transactionLifecycleID) +} + func createDualContractTransmitter(ctx context.Context, t *testing.T, transmitter Transmitter, ops ...OCRTransmitterOption) *dualContractTransmitter { contractABI, err := abi.JSON(strings.NewReader(ocr2aggregator.OCR2AggregatorMetaData.ABI)) require.NoError(t, err) diff --git a/pkg/txm/clientwrappers/dualbroadcast/meta_client.go b/pkg/txm/clientwrappers/dualbroadcast/meta_client.go index 9423973063..2b7a989f8e 100644 --- a/pkg/txm/clientwrappers/dualbroadcast/meta_client.go +++ b/pkg/txm/clientwrappers/dualbroadcast/meta_client.go @@ -595,6 +595,6 @@ func (a *MetaClient) SendOperation(ctx context.Context, tx *types.Transaction, a return fmt.Errorf("failed to update signed attempt for txID: %v, err: %w", tx.ID, err) } a.lggr.Infow("Intercepted attempt for tx", "txID", tx.ID, "hash", signedTx.Hash(), "toAddress", meta.ToAddress, "gasLimit", meta.GasLimit, - "TipCap", tip, "FeeCap", meta.MaxFeePerGas) + "TipCap", tip, "FeeCap", meta.MaxFeePerGas, "transactionLifecycleID", tx.GetTransactionLifecycleID(a.lggr)) return a.c.SendTransaction(ctx, signedTx) } diff --git a/pkg/txm/txm.go b/pkg/txm/txm.go index 8ce3da1087..454f58ddaf 100644 --- a/pkg/txm/txm.go +++ b/pkg/txm/txm.go @@ -177,7 +177,7 @@ func (t *Txm) HealthReport() map[string]error { func (t *Txm) CreateTransaction(ctx context.Context, txRequest *types.TxRequest) (tx *types.Transaction, err error) { tx, err = t.txStore.CreateTransaction(ctx, txRequest) if err == nil { - t.lggr.Infow("Created transaction", "tx", tx) + t.lggr.Infow("Created transaction", "txID", tx.ID, "tx", tx, "transactionLifecycleID", tx.GetTransactionLifecycleID(t.lggr)) } return } @@ -188,7 +188,10 @@ func (t *Txm) Trigger(address common.Address) { if !exists { return } - triggerCh <- struct{}{} + select { + case triggerCh <- struct{}{}: + default: + } }) { t.lggr.Error("Txm unstarted") } @@ -333,7 +336,7 @@ func (t *Txm) sendTransactionWithError(ctx context.Context, tx *types.Transactio } start := time.Now() txErr := t.client.SendTransaction(ctx, tx, attempt) - t.lggr.Infow("Broadcasted attempt", "tx", tx, "attempt", attempt, "duration", time.Since(start), "txErr: ", txErr) + t.lggr.Infow("Broadcasted attempt", "tx", tx, "attempt", attempt, "transactionLifecycleID", tx.GetTransactionLifecycleID(t.lggr), "duration", time.Since(start), "txErr", txErr) if txErr != nil && t.errorHandler != nil { if err = t.errorHandler.HandleError(ctx, tx, txErr, t.txStore, t.SetNonce, false); err != nil { return @@ -440,6 +443,7 @@ func (t *Txm) extractMetrics(ctx context.Context, txs []*types.Transaction) []ui if tx.InitialBroadcastAt != nil { t.Metrics.RecordTimeUntilTxConfirmed(ctx, float64(time.Since(*tx.InitialBroadcastAt))) } + t.lggr.Infow("Confirmed transaction", "txID", tx.ID, "transactionLifecycleID", tx.GetTransactionLifecycleID(t.lggr)) } return confirmedTxIDs } diff --git a/pkg/txm/types/transaction.go b/pkg/txm/types/transaction.go index f33bcbef1b..d3efe366fc 100644 --- a/pkg/txm/types/transaction.go +++ b/pkg/txm/types/transaction.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil" clnull "github.com/smartcontractkit/chainlink-common/pkg/utils/null" @@ -106,6 +107,18 @@ func (t *Transaction) GetMeta() (*TxMeta, error) { return &m, nil } +func (t *Transaction) GetTransactionLifecycleID(lggr logger.SugaredLogger) string { + meta, err := t.GetMeta() + if err != nil { + lggr.Errorw("failed to get meta of the transaction", "err", err) + return "" + } + if meta != nil && meta.TransactionLifecycleID != nil { + return *meta.TransactionLifecycleID + } + return "" +} + type Attempt struct { ID uint64 TxID uint64 @@ -184,6 +197,9 @@ type TxMeta struct { // Dual Broadcast DualBroadcast *bool `json:"DualBroadcast,omitempty"` DualBroadcastParams *string `json:"DualBroadcastParams,omitempty"` + + // TransactionLifecycleID is used for tracing the entire lifecycle of a transaction from OCR Transmit to confirmation on-chain. + TransactionLifecycleID *string `json:"TransactionLifecycleID,omitempty"` } type QueueingTxStrategy struct {