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
3 changes: 1 addition & 2 deletions chain_capabilities/solana/actions/forwarder_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -186,6 +188,7 @@ func (lr *logReader) registerInProgressFilter(ctx context.Context) error {
}

lr.sigInProgress = sigInProgress
lr.filterName = filterName
return nil
}

Expand All @@ -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)
}
Expand Down
15 changes: 8 additions & 7 deletions chain_capabilities/solana/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
30 changes: 16 additions & 14 deletions chain_capabilities/solana/go.sum

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

3 changes: 1 addition & 2 deletions chain_capabilities/solana/monitoring/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 2 additions & 18 deletions chain_capabilities/solana/trigger/trigger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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())
Expand Down
122 changes: 122 additions & 0 deletions chain_capabilities/solana/trigger/trigger_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading