diff --git a/chain_capabilities/solana/actions/forwarder_client.go b/chain_capabilities/solana/actions/forwarder_client.go index dbbdec93d..e08caffa2 100644 --- a/chain_capabilities/solana/actions/forwarder_client.go +++ b/chain_capabilities/solana/actions/forwarder_client.go @@ -123,8 +123,7 @@ func (fc *forwarderClient) InvokeOnReport(ctx context.Context, receiver solana.P var resolvedComputeConfig *soltypes.ComputeConfig if gasConfig != nil { resolvedComputeConfig = &soltypes.ComputeConfig{ - ComputeLimit: &gasConfig.ComputeLimit, - ComputeMaxPrice: &gasConfig.ComputeMaxPrice, + ComputeLimit: &gasConfig.ComputeLimit, } } diff --git a/chain_capabilities/solana/actions/transmission_info_provider.go b/chain_capabilities/solana/actions/transmission_info_provider.go index 89862c53e..bc1140727 100644 --- a/chain_capabilities/solana/actions/transmission_info_provider.go +++ b/chain_capabilities/solana/actions/transmission_info_provider.go @@ -31,6 +31,7 @@ type logReader struct { types.SolanaService forwarderProgramID solana.PublicKey sigInProgress soltypes.EventSignature + filterName string } // OnChainTransmissionInfoProvider uses the ExecutionState PDA for success/failure and @@ -172,8 +173,9 @@ func signatureFromInProgressLogs(inProgressLogs []*soltypes.Log) (solana.Signatu func (lr *logReader) registerInProgressFilter(ctx context.Context) error { idlJSON := []byte(contracts.FetchForwarderIDL()) sigInProgress := soltypes.EventSignature(lptypes.NewEventSignatureFromName(eventReportInProgress)) + filterName := eventReportInProgress + "_" + lr.forwarderProgramID.String() err := lr.RegisterLogTracking(ctx, soltypes.LPFilterQuery{ - Name: eventReportInProgress + "_" + lr.forwarderProgramID.String(), + Name: filterName, Address: soltypes.PublicKey(lr.forwarderProgramID), EventName: eventReportInProgress, EventSig: sigInProgress, @@ -186,6 +188,7 @@ func (lr *logReader) registerInProgressFilter(ctx context.Context) error { } lr.sigInProgress = sigInProgress + lr.filterName = filterName return nil } @@ -199,7 +202,7 @@ func (lr *logReader) queryInProgress(ctx context.Context, transmissionID [32]byt }), } - logs, err := lr.QueryTrackedLogs(ctx, exprs, limit) + logs, err := lr.QueryTrackedLogs(ctx, exprs, limit, lr.filterName) if err != nil { return nil, fmt.Errorf("failed to query tracked logs: %w", err) } diff --git a/chain_capabilities/solana/go.mod b/chain_capabilities/solana/go.mod index d713f5bb4..df3471d32 100644 --- a/chain_capabilities/solana/go.mod +++ b/chain_capabilities/solana/go.mod @@ -6,11 +6,11 @@ require ( github.com/gagliardetto/solana-go v1.14.0 github.com/mr-tron/base58 v1.2.0 github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260317175318-b042efa4107b - github.com/smartcontractkit/capabilities/libs v0.0.0-20260223172632-a716db2e04a0 + github.com/smartcontractkit/capabilities/libs v0.0.0-20260602154159-3bc5aa37c661 github.com/smartcontractkit/chain-selectors v1.0.100 - github.com/smartcontractkit/chainlink-common v0.11.2-0.20260511105412-c0c74c7c893e - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7 - github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260420191419-ea62f88cbdb4 + github.com/smartcontractkit/chainlink-common v0.11.2-0.20260603183656-aba62bd2ed21 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260603162809-d3f4a3c7b58a + github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260603183901-cb1823142783 github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260421131224-c46cbfe7bc6c github.com/smartcontractkit/libocr v0.0.0-20260403184524-b6409238958d github.com/stretchr/testify v1.11.1 @@ -129,9 +129,9 @@ 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.10 // indirect - github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20251210101658-1c5c8e4c4f15 // indirect - github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260326180413-c69f27e37a13 // indirect + github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 // indirect + github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260521164805-26d78d5e1243 // indirect + github.com/smartcontractkit/chainlink-framework/multinode 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/node-platform v0.0.0-20260319180422-b5808c964785 // indirect github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad // indirect @@ -152,6 +152,7 @@ require ( github.com/zeebo/xxh3 v1.0.2 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.12.2 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 // indirect diff --git a/chain_capabilities/solana/go.sum b/chain_capabilities/solana/go.sum index 5cdbb7f7d..08dc0e80f 100644 --- a/chain_capabilities/solana/go.sum +++ b/chain_capabilities/solana/go.sum @@ -617,8 +617,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/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260317175318-b042efa4107b h1:ZVvSFgoeeq44fOYvG9HxlWG4fKNrxTN8FvNxqsLnuFU= github.com/smartcontractkit/capabilities/chain_capabilities/common v0.0.0-20260317175318-b042efa4107b/go.mod h1:2a/FnHqe64PHOVC8L+kcxOuPZcgzZt33eplgHmFr5AI= -github.com/smartcontractkit/capabilities/libs v0.0.0-20260223172632-a716db2e04a0 h1:FXr4PieKZ5KLm8+kScb2SortgOkYrnu+Pd0jjRh3Hns= -github.com/smartcontractkit/capabilities/libs v0.0.0-20260223172632-a716db2e04a0/go.mod h1:v0O0Au8RE00Z89QxBE6I2q9bR9r3+RO1gLD3oaO2WB0= +github.com/smartcontractkit/capabilities/libs v0.0.0-20260602154159-3bc5aa37c661 h1:r+6yObrvJ/683T2P4JMgnxazER6ED5wPD9elva7ctZg= +github.com/smartcontractkit/capabilities/libs v0.0.0-20260602154159-3bc5aa37c661/go.mod h1:LS7F8U2YZNc0Vt8f6SVWUUigGLxdxZMpyC7VCcUTagg= github.com/smartcontractkit/chain-selectors v1.0.100 h1:wpiSpmI/eFjY+wx/nPr5VuNF4hki0prIBMKEaQWn3g4= github.com/smartcontractkit/chain-selectors v1.0.100/go.mod h1:qy7whtgG5g+7z0jt0nRyii9bLND9m15NZTzuQPkMZ5w= github.com/smartcontractkit/chainlink-ccip v0.1.1-solana.0.20260317185256-d5f7db87ae70 h1:bpzTG/8qwnbnIQPcilnM8lPd/Or4Q22cnakzawds2NQ= @@ -627,24 +627,24 @@ 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.20260511105412-c0c74c7c893e h1:ohdyUY4e3I0n2xt6ahFpcdFsV7wzEoMFLSQ4sU4yGmQ= -github.com/smartcontractkit/chainlink-common v0.11.2-0.20260511105412-c0c74c7c893e/go.mod h1:G2AII0QmWzXx8Ag9IKnGN3h/gwwNnhHUOCviJievdvo= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10 h1:FJAFgXS9oqASnkS03RE1HQwYQQxrO4l46O5JSzxqLgg= -github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.10/go.mod h1:oiDa54M0FwxevWwyAX773lwdWvFYYlYHHQV1LQ5HpWY= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260603183656-aba62bd2ed21 h1:rO7NT5QY0c7lOjhrsbOBPddVKQKVmoCGsmqlZUi2Des= +github.com/smartcontractkit/chainlink-common v0.11.2-0.20260603183656-aba62bd2ed21/go.mod h1:KpkphovBePsYfxuVhU9jy62TA531IUsURDXB2JtjCFQ= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0 h1:NExKM/D0HneOq/N5LGTbkV4VOa0UHCvfTNEb4GqYpto= +github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260601211238-9f526774fef0/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= 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-20251210101658-1c5c8e4c4f15 h1:IXF7+k8I1YY/yvXC1wnS3FAAggtCy6ByEQ9hv/F2FvQ= -github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20251210101658-1c5c8e4c4f15/go.mod h1:HG/aei0MgBOpsyRLexdKGtOUO8yjSJO3iUu0Uu8KBm4= -github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260326180413-c69f27e37a13 h1:3KLLkTCIAy9CvT35Ey0k6pcWX/u+qsm3Y/58TI5VSAg= -github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260326180413-c69f27e37a13/go.mod h1:Y7h84PqCe/Vimf2h1Nc6tMiOJStDbtM33fEUeaaF5xk= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7 h1:iljEJss3WOwcsMkWy72Yn2zvjw7Gyxc+RXL7r8YKM6g= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260526195338-adcf8013a1b7/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= +github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260521164805-26d78d5e1243 h1:vaFBupfFfImQgqOeuC7Muk2GflbYP6Gpi0Y/TLroFU8= +github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260521164805-26d78d5e1243/go.mod h1:HG/aei0MgBOpsyRLexdKGtOUO8yjSJO3iUu0Uu8KBm4= +github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243 h1:71PGTkjdFZ0JrloEC2Fs8eHl1b1gmUuH+bq7q23usKk= +github.com/smartcontractkit/chainlink-framework/multinode v0.0.0-20260521164805-26d78d5e1243/go.mod h1:7ketk4ischPQW/JQgmyHz6zdzLUJv1VC29SiSgosydQ= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260603162809-d3f4a3c7b58a h1:3FVh/0WPoh0DgB+qtn6vAhtzuj+bWqnfe3MWQcc2JGs= +github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260603162809-d3f4a3c7b58a/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b h1:QuI6SmQFK/zyUlVWEf0GMkiUYBPY4lssn26nKSd/bOM= github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b/go.mod h1:qSTSwX3cBP3FKQwQacdjArqv0g6QnukjV4XuzO6UyoY= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785 h1:oli+2uLU6jcrJGCuYFqk3475hiwL17SWlITWLv+tx/w= github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260319180422-b5808c964785/go.mod h1:dkR2uYg9XYJuT1JASkPzWE51jjFkVb86P7a/yXe5/GM= -github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260420191419-ea62f88cbdb4 h1:WaQzRyMCmi7gddZraPtHSqCjZ5dnVYFYXqEtq2DsQjw= -github.com/smartcontractkit/chainlink-solana v1.1.2-0.20260420191419-ea62f88cbdb4/go.mod h1:sUsEwLtVPBlz0wPcysaolS+HVj9cOAt4jYhwE6J8dXg= +github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260603183901-cb1823142783 h1:xS2hlMtcSAxwmJMN/iOSn8q81joxabwMPwaVJ4lhwSw= +github.com/smartcontractkit/chainlink-solana v1.3.1-0.20260603183901-cb1823142783/go.mod h1:BAhAILBy2r1CmcZLg4fQj9+lJ+WD1IYr+0NdrLS87ks= github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260421131224-c46cbfe7bc6c h1:Hn/80PyYFrQhRlNSaq9HY4cjc/7AuP9zyWLle22t34A= github.com/smartcontractkit/chainlink-solana/contracts v0.0.0-20260421131224-c46cbfe7bc6c/go.mod h1:C5pZsbYX3qkhZTYWr1aYJi9QMfonFAun+Jl1npQ7UJA= github.com/smartcontractkit/freeport v0.1.3-0.20250828155247-add56fa28aad h1:lgHxTHuzJIF3Vj6LSMOnjhqKgRqYW+0MV2SExtCYL1Q= @@ -744,6 +744,8 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0 h1:w3zlHYETbDwXyWHZlyyR58ZC39XGi8rAhkBgUgJ9d5w= +go.opentelemetry.io/contrib/bridges/prometheus v0.68.0/go.mod h1:GR/mClR2nn7vE8RLwxKjoBNg+QtgdDhRzxVa93koy5o= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI= diff --git a/chain_capabilities/solana/monitoring/messages.go b/chain_capabilities/solana/monitoring/messages.go index 9dd99bf2c..6da7ffb62 100644 --- a/chain_capabilities/solana/monitoring/messages.go +++ b/chain_capabilities/solana/monitoring/messages.go @@ -164,8 +164,7 @@ func convertWriteReportRequest(req *solanacappb.WriteReportRequest) *WriteReport Receiver: req.Receiver, RemainingAccounts: convertRemainingAccounts(req.RemainingAccounts), ComputeConfig: &ComputeConfig{ - ComputeLimit: req.GetComputeConfig().GetComputeLimit(), - ComputeMaxPrice: req.GetComputeConfig().GetComputeMaxPrice(), + ComputeLimit: req.GetComputeConfig().GetComputeLimit(), }, } if req.Report != nil { diff --git a/chain_capabilities/solana/trigger/trigger.go b/chain_capabilities/solana/trigger/trigger.go index 6a3fdc5f3..1959f4e60 100644 --- a/chain_capabilities/solana/trigger/trigger.go +++ b/chain_capabilities/solana/trigger/trigger.go @@ -45,9 +45,6 @@ func validateFilterConfig(config *solanacappb.FilterLogTriggerRequest) error { if config.EventName == "" { return fmt.Errorf("event name cannot be empty") } - if config.Name == "" { - return fmt.Errorf("filter name cannot be empty") - } if len(config.ContractIdlJson) == 0 { return fmt.Errorf("event idl json cannot be empty") } @@ -82,7 +79,7 @@ func (lts *SolanaLogTriggerService) ToLogPollerFilter(triggerID string, config * SubkeyPaths: getSubkeyPaths(config.Subkeys), Retention: lts.retention, MaxLogsKept: lts.maxLogsKept, - IncludeReverted: true, + IncludeReverted: config.IncludeReverted, CPIFilterConfig: cpiFilterConfig, }, nil } @@ -313,19 +310,6 @@ func (lts *SolanaLogTriggerService) RegisterLogTrigger(ctx context.Context, trig lts.lggr.Debugf("RegisterLogTracking id: %s", lpFilter.Name) - // Diagnostic: log what we're sending (before gRPC / ToProto) - hasCPI := lpFilter.CPIFilterConfig != nil - lts.lggr.Infow("[DEBUG] RegisterLogTracking sending", - "filterName", lpFilter.Name, - "hasCPIFilterConfig", hasCPI, - ) - if hasCPI { - lts.lggr.Infow("[DEBUG] RegisterLogTracking sending CPI config", - "filterName", lpFilter.Name, - "methodName", lpFilter.CPIFilterConfig.MethodName, - ) - } - err = lts.SolanaService.RegisterLogTracking(ctx, *lpFilter) if err != nil { summary := fmt.Sprintf("failed to register log-tracking: '%v' for triggerID: %s", err, triggerID) @@ -454,7 +438,7 @@ func (lts *SolanaLogTriggerService) startPolling(ctx context.Context, telemetryC query.NewSortBySequence(query.Asc), ) - logs, err := lts.SolanaService.QueryTrackedLogs(ctx, expressions, limitAndSort) + logs, err := lts.SolanaService.QueryTrackedLogs(ctx, expressions, limitAndSort, lts.generateFilterID(triggerID)) if err != nil { summary := fmt.Sprintf("Failed to query tracked logs for trigger %s: %v", triggerID, err) lts.logAndEmitError(ctx, telemetryContext, triggerID, summary, err.Error()) diff --git a/chain_capabilities/solana/trigger/trigger_integration_test.go b/chain_capabilities/solana/trigger/trigger_integration_test.go index e99475516..cf22fb6f8 100644 --- a/chain_capabilities/solana/trigger/trigger_integration_test.go +++ b/chain_capabilities/solana/trigger/trigger_integration_test.go @@ -30,6 +30,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/settings/limits" "github.com/smartcontractkit/chainlink-common/pkg/sqlutil/sqltest" "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/query" logreadtest "github.com/smartcontractkit/chainlink-solana/contracts/generated/log_read_test" relayer "github.com/smartcontractkit/chainlink-solana/pkg/solana" "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" @@ -146,6 +147,127 @@ func TestSolanaLogTrigger(t *testing.T) { _ = lp.Close() } +func TestSolanaLogTriggerQueryIsolationByFilterName(t *testing.T) { + dbURL := sqltest.TestURL(t) + db := sqltest.NewDB(t, dbURL) + lggr := logger.Test(t) + + cfg := config.NewDefault() + rpcURL, programID := setupValidatorAndTestContract(t) + sc, err := client.NewClient(rpcURL, cfg, 5*time.Second, lggr) + require.NoError(t, err) + + mc := client.NewMultiClient(func(context.Context) (client.ReaderWriter, error) { + return sc, nil + }) + + chainID, err := mc.ChainID(t.Context()) + require.NoError(t, err) + orm := logpoller.NewORM(chainID.String(), db, lggr) + lp, err := logpoller.New(logger.Sugared(lggr), orm, mc, config.NewDefault(), chainID.String()) + require.NoError(t, err) + + require.NoError(t, lp.Start(t.Context())) + defer func() { + _ = lp.Close() + }() + + // Only LogPoller is used; avoid Reader() mock expectations that would fail at teardown. + chain := solanamocks.NewChain(t) + chain.EXPECT().LogPoller().Return(lp) + rel := relayer.NewRelayer(lggr, chain, nil) + + triggerSvc, err := NewLogTriggerService(LogTriggerServiceOpts{ + SolanaService: rel, + Logger: lggr, + Triggers: NewSolanaLogTriggerStore(), + Retention: 24 * time.Hour, + MaxLogsKept: 1000, + BeholderProcessor: test.NopBeholderProcessor{}, + MessageBuilder: monitoring.NewMessageBuilder(types.ChainInfo{}, capabilities.CapabilityInfo{}, ""), + }) + require.NoError(t, err) + + idl, err := loadContractIDLJson() + require.NoError(t, err) + + address, err := solana.PublicKeyFromBase58(programID) + require.NoError(t, err) + + const triggerAID = "isolation-trigger-a" + const triggerBID = "isolation-trigger-b" + + reqA := logTriggerSubkeyGteRequest("filter-a", address, idl, 50) + reqB := logTriggerSubkeyGteRequest("filter-b", address, idl, 200) + + filterA, err := triggerSvc.ToLogPollerFilter(triggerAID, reqA) + require.NoError(t, err) + filterB, err := triggerSvc.ToLogPollerFilter(triggerBID, reqB) + require.NoError(t, err) + + ctx := t.Context() + require.NoError(t, rel.RegisterLogTracking(ctx, *filterA)) + require.NoError(t, rel.RegisterLogTracking(ctx, *filterB)) + + signerKeypair, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + rpcClient := rpc.New(rpcURL) + utils.FundAccounts(t, []solana.PrivateKey{signerKeypair}, rpcClient) + time.Sleep(time.Second) + + // Both filters ingest every matching event; subkey thresholds apply at query time. + _, err = emitLogReadTestEvent(t, sc, programID, signerKeypair, 100) + require.NoError(t, err) + _, err = emitLogReadTestEvent(t, sc, programID, signerKeypair, 250) + require.NoError(t, err) + + solSvc, err := rel.Solana() + require.NoError(t, err) + + exprA, err := BuildQueryExpressions(reqA, -1) + require.NoError(t, err) + exprB, err := BuildQueryExpressions(reqB, -1) + require.NoError(t, err) + + limit := query.NewLimitAndSort(query.CountLimit(100), query.NewSortBySequence(query.Asc)) + filterNameA := triggerAID + SuffixLogTriggerFilterID + filterNameB := triggerBID + SuffixLogTriggerFilterID + + require.Eventually(t, func() bool { + logs, qerr := solSvc.QueryTrackedLogs(ctx, exprA, limit, filterNameA) + return qerr == nil && len(logs) == 2 + }, 30*time.Second, 500*time.Millisecond, "filter A should return both events matching >= 50") + + require.Eventually(t, func() bool { + logs, qerr := solSvc.QueryTrackedLogs(ctx, exprB, limit, filterNameB) + return qerr == nil && len(logs) == 1 + }, 30*time.Second, 500*time.Millisecond, "filter B should return only the event matching >= 200") +} + +func logTriggerSubkeyGteRequest(name string, address solana.PublicKey, idl string, threshold uint64) *solanacappb.FilterLogTriggerRequest { + valueBytes := make([]byte, 8) + binary.BigEndian.PutUint64(valueBytes, threshold) + + return &solanacappb.FilterLogTriggerRequest{ + Name: name, + Address: address[:], + EventName: "TestEvent", + ContractIdlJson: []byte(idl), + Subkeys: []*solanacappb.SubkeyConfig{ + {Path: []string{"StrVal"}}, + { + Path: []string{"U64Value"}, + Comparers: []*solanacappb.ValueComparator{ + { + Value: valueBytes, + Operator: solanacappb.ComparisonOperator_COMPARISON_OPERATOR_GTE, + }, + }, + }, + }, + } +} + func TestSolanaLogTriggerWithSubkeyPaths(t *testing.T) { dbURL := sqltest.TestURL(t) db := sqltest.NewDB(t, dbURL) diff --git a/chain_capabilities/solana/trigger/trigger_test.go b/chain_capabilities/solana/trigger/trigger_test.go index 31fa3f02c..f6cece98b 100644 --- a/chain_capabilities/solana/trigger/trigger_test.go +++ b/chain_capabilities/solana/trigger/trigger_test.go @@ -165,7 +165,7 @@ func TestRegisterLogTrigger(t *testing.T) { mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 150}, nil).Maybe() mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() mockSolana.EXPECT().UnregisterLogTracking(mock.Anything, mock.AnythingOfType("string")).Return(nil).Once() ch, err := service.RegisterLogTrigger(ctx, testTriggerID, testRequestMetadata(), request) @@ -212,7 +212,7 @@ func TestRegisterLogTrigger(t *testing.T) { mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 150}, nil).Maybe() mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() // First registration should succeed ch1, err := service.RegisterLogTrigger(ctx, testTriggerID, testRequestMetadata(), request) @@ -252,7 +252,7 @@ func TestRegisterLogTrigger(t *testing.T) { mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 150}, nil).Maybe() mockSolana.EXPECT().GetFiltersNames(mock.Anything).Return([]string{}, nil).Maybe() mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() require.NoError(t, service.Start(ctx)) @@ -275,7 +275,7 @@ func TestUnregisterLogTrigger(t *testing.T) { mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 150}, nil).Maybe() mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() mockSolana.EXPECT().UnregisterLogTracking(mock.Anything, mock.AnythingOfType("string")).Return(nil).Once() // Register first @@ -318,7 +318,7 @@ func TestUnregisterLogTrigger(t *testing.T) { mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 150}, nil).Maybe() mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() mockSolana.EXPECT().UnregisterLogTracking(mock.Anything, mock.AnythingOfType("string")).Return(errors.New("unregister failed")).Once() // Register first @@ -361,6 +361,16 @@ func TestToLogPollerFilter(t *testing.T) { assert.Equal(t, expectedPaths, ([][]string)(filter.SubkeyPaths)) assert.Equal(t, service.retention, filter.Retention) assert.Equal(t, service.maxLogsKept, filter.MaxLogsKept) + assert.False(t, filter.IncludeReverted) + }) + + t.Run("passes IncludeReverted from request", func(t *testing.T) { + request := createTestRequest() + request.IncludeReverted = true + + filter, err := service.ToLogPollerFilter(testTriggerID, request) + require.NoError(t, err) + assert.True(t, filter.IncludeReverted) }) t.Run("returns error for empty request with invalid address", func(t *testing.T) { @@ -566,8 +576,8 @@ func TestStartPolling(t *testing.T) { createTestLog(102, testPublicKey), } - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(expectedLogs, nil).Once() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, triggerID+SuffixLogTriggerFilterID).Return(expectedLogs, nil).Once() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() telemetryContext := monitoring.TelemetryContext{ TsStart: time.Now().UnixMilli(), @@ -605,7 +615,7 @@ func TestStartPolling(t *testing.T) { logCh := make(chan capabilities.TriggerAndId[*solanacappb.Log], 10) // Return empty logs to simulate no new blocks - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() telemetryContext := createTestTelemetryContext() startPollingAsync(ctx, t, service, telemetryContext, config, triggerID, startingBlock, logCh) @@ -650,7 +660,7 @@ func TestStartPolling(t *testing.T) { startingBlock := int64(100) logCh := make(chan capabilities.TriggerAndId[*solanacappb.Log], 10) - mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("query failed")).Maybe() + mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("query failed")).Maybe() telemetryContext := createTestTelemetryContext() startPollingAsync(ctx, t, service, telemetryContext, config, triggerID, startingBlock, logCh) @@ -679,7 +689,7 @@ func TestStartPolling(t *testing.T) { firstBatch := []*solana.Log{createTestLog(101, testPublicKey)} - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(firstBatch, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(firstBatch, nil).Maybe() meta = testRequestMetadata() ctx = meta.ContextWithCRE(ctx) @@ -705,8 +715,8 @@ func TestStartPolling(t *testing.T) { logCh := make(chan capabilities.TriggerAndId[*solanacappb.Log], 10) polled := make(chan struct{}, 1) - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything). - Run(func(_ context.Context, _ []query.Expression, _ query.LimitAndSort) { + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Run(func(_ context.Context, _ []query.Expression, _ query.LimitAndSort, _ string) { select { case polled <- struct{}{}: default: @@ -767,7 +777,7 @@ func TestStartPolling(t *testing.T) { createTestLog(105, testPublicKey), } - mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(manyLogs, nil).Maybe() + mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(manyLogs, nil).Maybe() meta = testRequestMetadata() ctx = meta.ContextWithCRE(ctx) @@ -818,8 +828,8 @@ func TestStartPolling(t *testing.T) { emptyLogs := []*solana.Log{} var queryCallCount atomic.Int32 - mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything). - RunAndReturn(func(_ context.Context, _ []query.Expression, _ query.LimitAndSort) ([]*solana.Log, error) { + mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything). + RunAndReturn(func(_ context.Context, _ []query.Expression, _ query.LimitAndSort, _ string) ([]*solana.Log, error) { queryCallCount.Add(1) if queryCallCount.Load() == 1 { return firstBatchLogs, nil @@ -870,8 +880,8 @@ func TestStartPolling(t *testing.T) { logCh := make(chan capabilities.TriggerAndId[*solanacappb.Log], 10) // First call fails, second succeeds - mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("transient error")).Once() - mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{createTestLog(101, testPublicKey)}, nil).Maybe() + mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("transient error")).Once() + mockSolanaService.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{createTestLog(101, testPublicKey)}, nil).Maybe() meta = testRequestMetadata() ctx = meta.ContextWithCRE(ctx) @@ -909,7 +919,7 @@ func TestStartPolling(t *testing.T) { createTestLog(102, testPublicKey), } - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return(logsWithNil, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(logsWithNil, nil).Maybe() meta = testRequestMetadata() ctx = meta.ContextWithCRE(ctx) @@ -1045,7 +1055,7 @@ func TestSolanaLogTriggerService_Integration(t *testing.T) { mockSolana.EXPECT().RegisterLogTracking(mock.Anything, mock.AnythingOfType("solana.LPFilterQuery")).Return(nil).Once() mockSolana.EXPECT().GetSlotHeight(mock.Anything, mock.Anything).Return(&solana.GetSlotHeightReply{Height: 102}, nil).Maybe() - mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() + mockSolana.EXPECT().QueryTrackedLogs(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]*solana.Log{}, nil).Maybe() mockSolana.EXPECT().UnregisterLogTracking(mock.Anything, mock.AnythingOfType("string")).Return(nil).Once() ch, err := service.RegisterLogTrigger(ctx, testTriggerID, testRequestMetadata(), request) @@ -1284,17 +1294,6 @@ func TestValidateFilterConfig(t *testing.T) { assert.Contains(t, err.Error(), "event name cannot be empty") }) - t.Run("empty filter name", func(t *testing.T) { - config := &solanacappb.FilterLogTriggerRequest{ - Address: make([]byte, 32), - EventName: "TestEvent", - Name: "", - } - err := validateFilterConfig(config) - require.Error(t, err) - assert.Contains(t, err.Error(), "filter name cannot be empty") - }) - t.Run("empty event idl json", func(t *testing.T) { config := &solanacappb.FilterLogTriggerRequest{ Address: make([]byte, 32),