diff --git a/go.mod b/go.mod index 464a076887..aad64bc391 100644 --- a/go.mod +++ b/go.mod @@ -45,7 +45,7 @@ require ( github.com/smartcontractkit/chain-selectors v1.0.100 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260528204832-58c7145c53f8 github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 - github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260602131523-5168ac1ba014 + github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260603162809-d3f4a3c7b58a github.com/smartcontractkit/chainlink-protos/linking-service/go v0.0.0-20251002192024-d2ad9222409b github.com/smartcontractkit/chainlink-protos/node-platform v0.0.0-20260205130626-db2a2aab956b github.com/smartcontractkit/chainlink-protos/storage-service v0.3.0 diff --git a/go.sum b/go.sum index 9569e9934c..9c07e6d64b 100644 --- a/go.sum +++ b/go.sum @@ -262,8 +262,8 @@ github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.202605282 github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.11-0.20260528204832-58c7145c53f8/go.mod h1:HmUyH2oD9m+GRpKq7q3vuRnm1F2Uczf/Nd1v3ipMSK8= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4 h1:GCzrxDWn3b7jFfEA+WiYRi8CKoegsayiDoJBCjYkneE= github.com/smartcontractkit/chainlink-protos/billing/go v0.0.0-20251024234028-0988426d98f4/go.mod h1:HHGeDUpAsPa0pmOx7wrByCitjQ0mbUxf0R9v+g67uCA= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260602131523-5168ac1ba014 h1:4rxcbbe1qe1yR+HcclvOi/e0CFLcBLfx2fgiWxBMMZ4= -github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20260602131523-5168ac1ba014/go.mod h1:vTFHTCbLui4Vn8fTmAadfE3rdnvfrDwOmMujmW857D0= +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-20260205130626-db2a2aab956b h1:36knUpKHHAZ86K4FGWXtx8i/EQftGdk2bqCoEu/Cha8= diff --git a/pkg/capabilities/v2/chain-capabilities/solana/client.pb.go b/pkg/capabilities/v2/chain-capabilities/solana/client.pb.go index 3120d05d97..c6bc2ed97c 100644 --- a/pkg/capabilities/v2/chain-capabilities/solana/client.pb.go +++ b/pkg/capabilities/v2/chain-capabilities/solana/client.pb.go @@ -2953,6 +2953,7 @@ type FilterLogTriggerRequest struct { ContractIdlJson []byte `protobuf:"bytes,4,opt,name=contract_idl_json,json=contractIdlJson,proto3" json:"contract_idl_json,omitempty"` Subkeys []*SubkeyConfig `protobuf:"bytes,5,rep,name=subkeys,proto3" json:"subkeys,omitempty"` CpiFilterConfig *CPIFilterConfig `protobuf:"bytes,6,opt,name=cpi_filter_config,json=cpiFilterConfig,proto3,oneof" json:"cpi_filter_config,omitempty"` + IncludeReverted bool `protobuf:"varint,7,opt,name=include_reverted,json=includeReverted,proto3" json:"include_reverted,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3029,6 +3030,13 @@ func (x *FilterLogTriggerRequest) GetCpiFilterConfig() *CPIFilterConfig { return nil } +func (x *FilterLogTriggerRequest) GetIncludeReverted() bool { + if x != nil { + return x.IncludeReverted + } + return false +} + type Log struct { state protoimpl.MessageState `protogen:"open.v1"` ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` // Chain identifier @@ -3557,7 +3565,7 @@ const file_capabilities_blockchain_solana_v1alpha_client_proto_rawDesc = "" + "\x0fCPIFilterConfig\x12!\n" + "\fdest_address\x18\x01 \x01(\fR\vdestAddress\x12\x1f\n" + "\vmethod_name\x18\x02 \x01(\fR\n" + - "methodName\"\xe2\x02\n" + + "methodName\"\x8d\x03\n" + "\x17FilterLogTriggerRequest\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + "\aaddress\x18\x02 \x01(\fR\aaddress\x12\x1d\n" + @@ -3565,7 +3573,8 @@ const file_capabilities_blockchain_solana_v1alpha_client_proto_rawDesc = "" + "event_name\x18\x03 \x01(\tR\teventName\x12*\n" + "\x11contract_idl_json\x18\x04 \x01(\fR\x0fcontractIdlJson\x12N\n" + "\asubkeys\x18\x05 \x03(\v24.capabilities.blockchain.solana.v1alpha.SubkeyConfigR\asubkeys\x12h\n" + - "\x11cpi_filter_config\x18\x06 \x01(\v27.capabilities.blockchain.solana.v1alpha.CPIFilterConfigH\x00R\x0fcpiFilterConfig\x88\x01\x01B\x14\n" + + "\x11cpi_filter_config\x18\x06 \x01(\v27.capabilities.blockchain.solana.v1alpha.CPIFilterConfigH\x00R\x0fcpiFilterConfig\x88\x01\x01\x12)\n" + + "\x10include_reverted\x18\a \x01(\bR\x0fincludeRevertedB\x14\n" + "\x12_cpi_filter_config\"\xd4\x02\n" + "\x03Log\x12\x19\n" + "\bchain_id\x18\x01 \x01(\tR\achainId\x12\x1b\n" + diff --git a/pkg/chains/solana/proto_helpers.go b/pkg/chains/solana/proto_helpers.go index 608d6b1c5c..0866533473 100644 --- a/pkg/chains/solana/proto_helpers.go +++ b/pkg/chains/solana/proto_helpers.go @@ -358,6 +358,29 @@ func ConvertGetAccountInfoOptsToProto(o *solana.GetAccountInfoOpts) *GetAccountI } } +func ConvertGetAccountInfoRequestFromProto(p *GetAccountInfoWithOptsRequest) (solana.GetAccountInfoRequest, error) { + if p == nil { + return solana.GetAccountInfoRequest{}, fmt.Errorf("nil GetAccountInfoWithOptsRequest") + } + addr, err := ConvertPublicKeyFromProto(p.GetAccount()) + if err != nil { + return solana.GetAccountInfoRequest{}, err + } + return solana.GetAccountInfoRequest{ + Account: addr, + Opts: ConvertGetAccountInfoOptsFromProto(p.GetOpts()), + IsExternal: p.GetIsExternal(), + }, nil +} + +func ConvertGetAccountInfoRequestToProto(r solana.GetAccountInfoRequest) *GetAccountInfoWithOptsRequest { + return &GetAccountInfoWithOptsRequest{ + Account: r.Account[:], + Opts: ConvertGetAccountInfoOptsToProto(r.Opts), + IsExternal: r.IsExternal, + } +} + func ConvertGetMultipleAccountsOptsFromProto(p *GetMultipleAccountsOpts) *solana.GetMultipleAccountsOpts { if p == nil { return nil @@ -825,11 +848,11 @@ func ConvertGetTransactionRequestFromProto(p *GetTransactionRequest) (solana.Get if err != nil { return solana.GetTransactionRequest{}, err } - return solana.GetTransactionRequest{Signature: sig}, nil + return solana.GetTransactionRequest{Signature: sig, IsExternal: p.GetIsExternal()}, nil } func ConvertGetTransactionRequestToProto(r solana.GetTransactionRequest) *GetTransactionRequest { - return &GetTransactionRequest{Signature: r.Signature[:]} + return &GetTransactionRequest{Signature: r.Signature[:], IsExternal: r.IsExternal} } func ConvertGetBalanceReplyFromProto(p *GetBalanceReply) *solana.GetBalanceReply { @@ -996,8 +1019,9 @@ func ConvertGetMultipleAccountsRequestFromProto(p *GetMultipleAccountsWithOptsRe } accts, _ := ConvertPublicKeysFromProto(p.Accounts) return &solana.GetMultipleAccountsRequest{ - Accounts: accts, - Opts: ConvertGetMultipleAccountsOptsFromProto(p.Opts), + Accounts: accts, + Opts: ConvertGetMultipleAccountsOptsFromProto(p.Opts), + IsExternal: p.GetIsExternal(), } } @@ -1006,8 +1030,9 @@ func ConvertGetMultipleAccountsRequestToProto(r *solana.GetMultipleAccountsReque return nil } return &GetMultipleAccountsWithOptsRequest{ - Accounts: ConvertPublicKeysToProto(r.Accounts), - Opts: ConvertGetMultipleAccountsOptsToProto(r.Opts), + Accounts: ConvertPublicKeysToProto(r.Accounts), + Opts: ConvertGetMultipleAccountsOptsToProto(r.Opts), + IsExternal: r.IsExternal, } } @@ -1150,6 +1175,7 @@ func ConvertSimulateTXRequestFromProto(p *SimulateTXRequest) (solana.SimulateTXR Receiver: recv, EncodedTransaction: p.EncodedTransaction, Opts: ConvertSimulateTXOptsFromProto(p.Opts), + IsExternal: p.GetIsExternal(), }, nil } @@ -1158,6 +1184,7 @@ func ConvertSimulateTXRequestToProto(r solana.SimulateTXRequest) *SimulateTXRequ Receiver: r.Receiver[:], EncodedTransaction: r.EncodedTransaction, Opts: ConvertSimulateTXOptsToProto(r.Opts), + IsExternal: r.IsExternal, } } diff --git a/pkg/chains/solana/proto_helpers_test.go b/pkg/chains/solana/proto_helpers_test.go index 2ecd631c3f..1dba017623 100644 --- a/pkg/chains/solana/proto_helpers_test.go +++ b/pkg/chains/solana/proto_helpers_test.go @@ -386,6 +386,73 @@ func TestGetSignatureStatusesConverters(t *testing.T) { require.Equal(t, conv.ConfirmationStatusType_CONFIRMATION_STATUS_TYPE_CONFIRMED, rep2.Results[0].ConfirmationStatus) } +func TestExternalRequestProtoRoundTrip(t *testing.T) { + t.Run("GetAccountInfoRequest", func(t *testing.T) { + pk := typesolana.PublicKey{} + copy(pk[:], mkBytes(typesolana.PublicKeyLength, 0xAB)) + d := typesolana.GetAccountInfoRequest{ + Account: pk, + Opts: &typesolana.GetAccountInfoOpts{ + Encoding: typesolana.EncodingBase64, + Commitment: typesolana.CommitmentFinalized, + }, + IsExternal: true, + } + pb := conv.ConvertGetAccountInfoRequestToProto(d) + got, err := conv.ConvertGetAccountInfoRequestFromProto(pb) + require.NoError(t, err) + require.Equal(t, d, got) + }) + + t.Run("GetMultipleAccountsRequest", func(t *testing.T) { + d := &typesolana.GetMultipleAccountsRequest{ + Accounts: []typesolana.PublicKey{ + {1}, + func() (pk typesolana.PublicKey) { copy(pk[:], mkBytes(typesolana.PublicKeyLength, 0xCD)); return pk }(), + }, + Opts: &typesolana.GetMultipleAccountsOpts{ + Encoding: typesolana.EncodingJSONParsed, + Commitment: typesolana.CommitmentProcessed, + }, + IsExternal: true, + } + pb := conv.ConvertGetMultipleAccountsRequestToProto(d) + got := conv.ConvertGetMultipleAccountsRequestFromProto(pb) + require.Equal(t, d.Accounts[0], got.Accounts[0]) + require.Equal(t, d.Accounts[1], got.Accounts[1]) + require.Equal(t, d.IsExternal, got.IsExternal) + require.Equal(t, d.Opts, got.Opts) + }) + + t.Run("GetTransactionRequest", func(t *testing.T) { + var sig typesolana.Signature + copy(sig[:], mkBytes(typesolana.SignatureLength, 0xEF)) + d := typesolana.GetTransactionRequest{Signature: sig, IsExternal: true} + pb := conv.ConvertGetTransactionRequestToProto(d) + got, err := conv.ConvertGetTransactionRequestFromProto(pb) + require.NoError(t, err) + require.Equal(t, d, got) + }) + + t.Run("SimulateTXRequest", func(t *testing.T) { + var recv typesolana.PublicKey + copy(recv[:], mkBytes(typesolana.PublicKeyLength, 0x11)) + d := typesolana.SimulateTXRequest{ + Receiver: recv, + EncodedTransaction: "txdata", + Opts: &typesolana.SimulateTXOpts{ + SigVerify: true, + Commitment: typesolana.CommitmentConfirmed, + }, + IsExternal: true, + } + pb := conv.ConvertSimulateTXRequestToProto(d) + got, err := conv.ConvertSimulateTXRequestFromProto(pb) + require.NoError(t, err) + require.Equal(t, d, got) + }) +} + func TestErrorJoinBehavior_PublicKeys(t *testing.T) { in := [][]byte{ mkBytes(typesolana.PublicKeyLength-1, 0x01), diff --git a/pkg/chains/solana/solana.pb.go b/pkg/chains/solana/solana.pb.go index 86a4362d87..a9c263669a 100644 --- a/pkg/chains/solana/solana.pb.go +++ b/pkg/chains/solana/solana.pb.go @@ -648,6 +648,7 @@ type GetAccountInfoWithOptsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Account []byte `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` // 32-byte Pubkey Opts *GetAccountInfoOpts `protobuf:"bytes,2,opt,name=opts,proto3" json:"opts,omitempty"` + IsExternal bool `protobuf:"varint,3,opt,name=is_external,json=isExternal,proto3" json:"is_external,omitempty"` // if true, limits like response size limit may be applied unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -696,6 +697,13 @@ func (x *GetAccountInfoWithOptsRequest) GetOpts() *GetAccountInfoOpts { return nil } +func (x *GetAccountInfoWithOptsRequest) GetIsExternal() bool { + if x != nil { + return x.IsExternal + } + return false +} + // Reply for GetBalance. type GetBalanceReply struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1237,6 +1245,7 @@ type GetMultipleAccountsWithOptsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Accounts [][]byte `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts,omitempty"` // list of 32-byte Pubkeys Opts *GetMultipleAccountsOpts `protobuf:"bytes,2,opt,name=opts,proto3" json:"opts,omitempty"` + IsExternal bool `protobuf:"varint,3,opt,name=is_external,json=isExternal,proto3" json:"is_external,omitempty"` // if true, limits like response size limit may be applied unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1285,6 +1294,13 @@ func (x *GetMultipleAccountsWithOptsRequest) GetOpts() *GetMultipleAccountsOpts return nil } +func (x *GetMultipleAccountsWithOptsRequest) GetIsExternal() bool { + if x != nil { + return x.IsExternal + } + return false +} + // Reply for GetSignatureStatuses. type GetSignatureStatusesReply struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2415,7 +2431,8 @@ func (x *GetTransactionReply) GetMeta() *TransactionMeta { // GetTransaction request. type GetTransactionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` // 64-byte signature + Signature []byte `protobuf:"bytes,1,opt,name=signature,proto3" json:"signature,omitempty"` // 64-byte signature + IsExternal bool `protobuf:"varint,2,opt,name=is_external,json=isExternal,proto3" json:"is_external,omitempty"` // if true, limits like response size limit may be applied unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2457,6 +2474,13 @@ func (x *GetTransactionRequest) GetSignature() []byte { return nil } +func (x *GetTransactionRequest) GetIsExternal() bool { + if x != nil { + return x.IsExternal + } + return false +} + // RPC read context. type RPCContext struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -2646,6 +2670,7 @@ type SimulateTXRequest struct { Receiver []byte `protobuf:"bytes,1,opt,name=receiver,proto3" json:"receiver,omitempty"` // 32-byte program id (target) EncodedTransaction string `protobuf:"bytes,2,opt,name=encoded_transaction,json=encodedTransaction,proto3" json:"encoded_transaction,omitempty"` // base64/base58 tx Opts *SimulateTXOpts `protobuf:"bytes,3,opt,name=opts,proto3" json:"opts,omitempty"` + IsExternal bool `protobuf:"varint,4,opt,name=is_external,json=isExternal,proto3" json:"is_external,omitempty"` // if true, limits like response size limit may be applied unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -2701,6 +2726,13 @@ func (x *SimulateTXRequest) GetOpts() *SimulateTXOpts { return nil } +func (x *SimulateTXRequest) GetIsExternal() bool { + if x != nil { + return x.IsExternal + } + return false +} + // Accounts to return during simulation. type SimulateTransactionAccountsOpts struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -3701,6 +3733,7 @@ type QueryTrackedLogsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` FilterQuery []*Expression `protobuf:"bytes,1,rep,name=filterQuery,proto3" json:"filterQuery,omitempty"` // filter tree LimitAndSort *chain_common.LimitAndSort `protobuf:"bytes,2,opt,name=limit_and_sort,json=limitAndSort,proto3" json:"limit_and_sort,omitempty"` // paging + FilterName string `protobuf:"bytes,3,opt,name=filter_name,json=filterName,proto3" json:"filter_name,omitempty"` // registered filter name for query scoping unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3749,6 +3782,13 @@ func (x *QueryTrackedLogsRequest) GetLimitAndSort() *chain_common.LimitAndSort { return nil } +func (x *QueryTrackedLogsRequest) GetFilterName() string { + if x != nil { + return x.FilterName + } + return "" +} + type QueryTrackedLogsReply struct { state protoimpl.MessageState `protogen:"open.v1"` Logs []*Log `protobuf:"bytes,1,rep,name=logs,proto3" json:"logs,omitempty"` @@ -4083,10 +4123,12 @@ const file_solana_proto_rawDesc = "" + "\vrpc_context\x18\x01 \x01(\v2\x17.loop.solana.RPCContextR\n" + "rpcContext\x12/\n" + "\x05value\x18\x02 \x01(\v2\x14.loop.solana.AccountH\x00R\x05value\x88\x01\x01B\b\n" + - "\x06_value\"n\n" + + "\x06_value\"\x8f\x01\n" + "\x1dGetAccountInfoWithOptsRequest\x12\x18\n" + "\aaccount\x18\x01 \x01(\fR\aaccount\x123\n" + - "\x04opts\x18\x02 \x01(\v2\x1f.loop.solana.GetAccountInfoOptsR\x04opts\"'\n" + + "\x04opts\x18\x02 \x01(\v2\x1f.loop.solana.GetAccountInfoOptsR\x04opts\x12\x1f\n" + + "\vis_external\x18\x03 \x01(\bR\n" + + "isExternal\"'\n" + "\x0fGetBalanceReply\x12\x14\n" + "\x05value\x18\x01 \x01(\x04R\x05value\"d\n" + "\x11GetBalanceRequest\x12\x12\n" + @@ -4132,10 +4174,12 @@ const file_solana_proto_rawDesc = "" + " GetMultipleAccountsWithOptsReply\x128\n" + "\vrpc_context\x18\x01 \x01(\v2\x17.loop.solana.RPCContextR\n" + "rpcContext\x129\n" + - "\x05value\x18\x02 \x03(\v2#.loop.solana.OptionalAccountWrapperR\x05value\"z\n" + + "\x05value\x18\x02 \x03(\v2#.loop.solana.OptionalAccountWrapperR\x05value\"\x9b\x01\n" + "\"GetMultipleAccountsWithOptsRequest\x12\x1a\n" + "\baccounts\x18\x01 \x03(\fR\baccounts\x128\n" + - "\x04opts\x18\x02 \x01(\v2$.loop.solana.GetMultipleAccountsOptsR\x04opts\"^\n" + + "\x04opts\x18\x02 \x01(\v2$.loop.solana.GetMultipleAccountsOptsR\x04opts\x12\x1f\n" + + "\vis_external\x18\x03 \x01(\bR\n" + + "isExternal\"^\n" + "\x19GetSignatureStatusesReply\x12A\n" + "\aresults\x18\x01 \x03(\v2'.loop.solana.GetSignatureStatusesResultR\aresults\"1\n" + "\x1bGetSignatureStatusesRequest\x12\x12\n" + @@ -4225,9 +4269,11 @@ const file_solana_proto_rawDesc = "" + "\x04meta\x18\x04 \x01(\v2\x1c.loop.solana.TransactionMetaH\x02R\x04meta\x88\x01\x01B\r\n" + "\v_block_timeB\x0e\n" + "\f_transactionB\a\n" + - "\x05_meta\"5\n" + + "\x05_meta\"V\n" + "\x15GetTransactionRequest\x12\x1c\n" + - "\tsignature\x18\x01 \x01(\fR\tsignature\" \n" + + "\tsignature\x18\x01 \x01(\fR\tsignature\x12\x1f\n" + + "\vis_external\x18\x02 \x01(\bR\n" + + "isExternal\" \n" + "\n" + "RPCContext\x12\x12\n" + "\x04slot\x18\x01 \x01(\x04R\x04slot\"\xf0\x01\n" + @@ -4243,11 +4289,13 @@ const file_solana_proto_rawDesc = "" + "\x03err\x18\x01 \x01(\tR\x03err\x12\x12\n" + "\x04logs\x18\x02 \x03(\tR\x04logs\x120\n" + "\baccounts\x18\x03 \x03(\v2\x14.loop.solana.AccountR\baccounts\x12%\n" + - "\x0eunits_consumed\x18\x04 \x01(\x04R\runitsConsumed\"\x91\x01\n" + + "\x0eunits_consumed\x18\x04 \x01(\x04R\runitsConsumed\"\xb2\x01\n" + "\x11SimulateTXRequest\x12\x1a\n" + "\breceiver\x18\x01 \x01(\fR\breceiver\x12/\n" + "\x13encoded_transaction\x18\x02 \x01(\tR\x12encodedTransaction\x12/\n" + - "\x04opts\x18\x03 \x01(\v2\x1b.loop.solana.SimulateTXOptsR\x04opts\"v\n" + + "\x04opts\x18\x03 \x01(\v2\x1b.loop.solana.SimulateTXOptsR\x04opts\x12\x1f\n" + + "\vis_external\x18\x04 \x01(\bR\n" + + "isExternal\"v\n" + "\x1fSimulateTransactionAccountsOpts\x125\n" + "\bencoding\x18\x01 \x01(\x0e2\x19.loop.solana.EncodingTypeR\bencoding\x12\x1c\n" + "\taddresses\x18\x02 \x03(\fR\taddresses\"\x8e\x01\n" + @@ -4322,10 +4370,12 @@ const file_solana_proto_rawDesc = "" + "\x04data\x18\t \x01(\fR\x04data\x12!\n" + "\fsequence_num\x18\n" + " \x01(\x03R\vsequenceNum\x12\x14\n" + - "\x05error\x18\v \x01(\tR\x05error\"\x9b\x01\n" + + "\x05error\x18\v \x01(\tR\x05error\"\xbc\x01\n" + "\x17QueryTrackedLogsRequest\x129\n" + "\vfilterQuery\x18\x01 \x03(\v2\x17.loop.solana.ExpressionR\vfilterQuery\x12E\n" + - "\x0elimit_and_sort\x18\x02 \x01(\v2\x1f.loop.chain.common.LimitAndSortR\flimitAndSort\"=\n" + + "\x0elimit_and_sort\x18\x02 \x01(\v2\x1f.loop.chain.common.LimitAndSortR\flimitAndSort\x12\x1f\n" + + "\vfilter_name\x18\x03 \x01(\tR\n" + + "filterName\"=\n" + "\x15QueryTrackedLogsReply\x12$\n" + "\x04logs\x18\x01 \x03(\v2\x10.loop.solana.LogR\x04logs\"P\n" + "\x1aRegisterLogTrackingRequest\x122\n" + diff --git a/pkg/chains/solana/solana.proto b/pkg/chains/solana/solana.proto index c629a81dad..3133a197ec 100644 --- a/pkg/chains/solana/solana.proto +++ b/pkg/chains/solana/solana.proto @@ -107,6 +107,7 @@ message GetAccountInfoWithOptsReply { message GetAccountInfoWithOptsRequest { bytes account = 1; // 32-byte Pubkey GetAccountInfoOpts opts = 2; + bool is_external = 3; // if true, limits like response size limit may be applied } // Reply for GetBalance. @@ -172,6 +173,7 @@ message GetMultipleAccountsWithOptsReply { message GetMultipleAccountsWithOptsRequest { repeated bytes accounts = 1; // list of 32-byte Pubkeys GetMultipleAccountsOpts opts = 2; + bool is_external = 3; // if true, limits like response size limit may be applied } // Reply for GetSignatureStatuses. @@ -304,6 +306,7 @@ message GetTransactionReply { // GetTransaction request. message GetTransactionRequest { bytes signature = 1; // 64-byte signature + bool is_external = 2; // if true, limits like response size limit may be applied } // RPC read context. @@ -332,6 +335,7 @@ message SimulateTXRequest { bytes receiver = 1; // 32-byte program id (target) string encoded_transaction = 2; // base64/base58 tx SimulateTXOpts opts = 3; + bool is_external = 4; // if true, limits like response size limit may be applied } // Accounts to return during simulation. @@ -446,6 +450,7 @@ message Log { message QueryTrackedLogsRequest { repeated Expression filterQuery = 1; // filter tree loop.chain.common.LimitAndSort limit_and_sort = 2; // paging + string filter_name = 3; // registered filter name for query scoping } message QueryTrackedLogsReply { diff --git a/pkg/http/limited_transport.go b/pkg/http/limited_transport.go new file mode 100644 index 0000000000..365b6f7834 --- /dev/null +++ b/pkg/http/limited_transport.go @@ -0,0 +1,90 @@ +package http + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" +) + +type contextKey string + +const responseLimitCtxKey contextKey = "responseLimitCtxKey" + +// LimitedTransport wraps an http.RoundTripper and limits the size of the response body. Limit is set via context using WithResponseSizeLimit +type LimitedTransport struct { + // RoundTripper is the underlying http.RoundTripper to use for the actual request. + // This will typically be http.DefaultTransport or a custom *http.Transport. + RoundTripper http.RoundTripper +} + +// RoundTrip implements the http.RoundTripper interface for LimitedTransport. +func (t *LimitedTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Perform the actual HTTP request using the underlying RoundTripper. + resp, err := t.RoundTripper.RoundTrip(req) + if err != nil { + return nil, err + } + + // If the response body is not nil, wrap it with an io.limitReader. + // This will ensure that only up to MaxResponseSize bytes can be read. + respLimit := GetResponseSizeLimit(req.Context()) + if resp.Body != nil && respLimit > 0 { + resp.Body = limitReader(resp.Body, int64(respLimit)) + } + + return resp, nil +} + +// WithResponseSizeLimit - sets a limit on the size of the response body for HTTP requests made with the LimitedTransport. +func WithResponseSizeLimit(ctx context.Context, limit uint32) context.Context { + if limit > 0 { + return context.WithValue(ctx, responseLimitCtxKey, limit) + } + + return ctx +} + +func GetResponseSizeLimit(ctx context.Context) uint32 { + limit, ok := ctx.Value(responseLimitCtxKey).(uint32) + if !ok { + return 0 + } + return limit +} + +var errResponseTooLarge = errors.New("response is too large") + +// limitReader returns a Reader that reads from r +// but stops with EOF after n bytes. +// The underlying implementation is a *limitedReader. +func limitReader(r io.ReadCloser, n int64) *limitedReader { + return &limitedReader{R: r, N: n, Limit: n} +} + +// A limitedReader reads from R but limits the amount of +// data returned to just N bytes. Each call to Read +// updates N to reflect the new amount remaining. +// Read returns EOF when N <= 0 or when the underlying R returns EOF. +type limitedReader struct { + R io.ReadCloser // underlying reader + N int64 // max bytes remaining + Limit int64 // original limit for error reporting +} + +func (l *limitedReader) Read(p []byte) (n int, err error) { + if l.N <= 0 { + return 0, fmt.Errorf("reached read limit of %d bytes: %w", l.Limit, errResponseTooLarge) + } + if int64(len(p)) > l.N { + p = p[0:l.N] + } + n, err = l.R.Read(p) + l.N -= int64(n) + return +} + +func (l *limitedReader) Close() error { + return l.R.Close() +} diff --git a/pkg/loop/internal/relayer/solana.go b/pkg/loop/internal/relayer/solana.go index 0a3a1e8fae..d497cfac9d 100644 --- a/pkg/loop/internal/relayer/solana.go +++ b/pkg/loop/internal/relayer/solana.go @@ -74,7 +74,7 @@ func (sc *SolClient) UnregisterLogTracking(ctx context.Context, filterName strin return net.WrapRPCErr(err) } -func (sc *SolClient) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort) ([]*solana.Log, error) { +func (sc *SolClient) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort, filterName string) ([]*solana.Log, error) { pExprs, err := solpb.ConvertExpressionsToProto(filterQuery) if err != nil { return nil, net.WrapRPCErr(err) @@ -88,6 +88,7 @@ func (sc *SolClient) QueryTrackedLogs(ctx context.Context, filterQuery []query.E pReq := &solpb.QueryTrackedLogsRequest{ FilterQuery: pExprs, LimitAndSort: protoLimitAndSort, + FilterName: filterName, } pResp, err := sc.grpcClient.QueryTrackedLogs(ctx, pReq) @@ -117,10 +118,7 @@ func (sc *SolClient) GetBalance(ctx context.Context, req solana.GetBalanceReques } func (sc *SolClient) GetAccountInfoWithOpts(ctx context.Context, req solana.GetAccountInfoRequest) (*solana.GetAccountInfoReply, error) { - pReq := &solpb.GetAccountInfoWithOptsRequest{ - Account: req.Account[:], - Opts: solpb.ConvertGetAccountInfoOptsToProto(req.Opts), - } + pReq := solpb.ConvertGetAccountInfoRequestToProto(req) pResp, err := sc.grpcClient.GetAccountInfoWithOpts(ctx, pReq) if err != nil { return nil, net.WrapRPCErr(err) @@ -311,7 +309,7 @@ func (s *solServer) QueryTrackedLogs(ctx context.Context, req *solpb.QueryTracke return nil, net.WrapRPCErr(err) } - logs, err := s.impl.QueryTrackedLogs(ctx, dExprs, ls) + logs, err := s.impl.QueryTrackedLogs(ctx, dExprs, ls, req.GetFilterName()) if err != nil { return nil, net.WrapRPCErr(err) } @@ -339,14 +337,11 @@ func (s *solServer) GetBalance(ctx context.Context, req *solpb.GetBalanceRequest } func (s *solServer) GetAccountInfoWithOpts(ctx context.Context, req *solpb.GetAccountInfoWithOptsRequest) (*solpb.GetAccountInfoWithOptsReply, error) { - addr, err := solpb.ConvertPublicKeyFromProto(req.GetAccount()) + dReq, err := solpb.ConvertGetAccountInfoRequestFromProto(req) if err != nil { return nil, net.WrapRPCErr(err) } - opts := solpb.ConvertGetAccountInfoOptsFromProto(req.GetOpts()) - - dReq := solana.GetAccountInfoRequest{Account: addr, Opts: opts} dResp, err := s.impl.GetAccountInfoWithOpts(ctx, dReq) if err != nil { return nil, net.WrapRPCErr(err) diff --git a/pkg/loop/internal/relayerset/relayerset_test.go b/pkg/loop/internal/relayerset/relayerset_test.go index 5d02ec0b8b..4814bc9d30 100644 --- a/pkg/loop/internal/relayerset/relayerset_test.go +++ b/pkg/loop/internal/relayerset/relayerset_test.go @@ -645,6 +645,7 @@ func Test_RelayerSet_SolanaService(t *testing.T) { Encoding: soltypes.EncodingJSONParsed, Commitment: soltypes.CommitmentFinalized, }, + IsExternal: true, } slot := uint64(22) lamports := uint64(33) @@ -673,6 +674,7 @@ func Test_RelayerSet_SolanaService(t *testing.T) { Encoding: soltypes.EncodingBase64, Commitment: soltypes.CommitmentProcessed, }, + IsExternal: true, } slot := uint64(22) lamports := uint64(33) @@ -736,7 +738,7 @@ func Test_RelayerSet_SolanaService(t *testing.T) { run: func(t *testing.T, sol types.SolanaService, mockSol *mocks2.SolanaService) { var sig soltypes.Signature copy(sig[:], []byte{1, 2, 3, 4}) - req := soltypes.GetTransactionRequest{Signature: sig} + req := soltypes.GetTransactionRequest{Signature: sig, IsExternal: true} expTime := soltypes.UnixTimeSeconds(11) expFee := uint64(33) expSlot := uint64(17) @@ -808,6 +810,7 @@ func Test_RelayerSet_SolanaService(t *testing.T) { Commitment: soltypes.CommitmentProcessed, ReplaceRecentBlockhash: true, }, + IsExternal: true, } mockSol.EXPECT(). SimulateTX(mock.Anything, req). @@ -892,13 +895,14 @@ func Test_RelayerSet_SolanaService(t *testing.T) { {BlockNumber: 1, LogIndex: 0}, {BlockNumber: 2, LogIndex: 5}, } + filterName := "query-filter" expLimitAndSort := query.NewLimitAndSort(query.CountLimit(10), query.SortByTimestamp{}) mockSol.EXPECT(). - QueryTrackedLogs(mock.Anything, filterExpr, expLimitAndSort). + QueryTrackedLogs(mock.Anything, filterExpr, expLimitAndSort, filterName). Return(expected, nil) - out, err := sol.QueryTrackedLogs(ctx, filterExpr, expLimitAndSort) + out, err := sol.QueryTrackedLogs(ctx, filterExpr, expLimitAndSort, filterName) require.NoError(t, err) require.Len(t, out, 2) require.Equal(t, int64(2), out[1].BlockNumber) diff --git a/pkg/loop/internal/relayerset/solana.go b/pkg/loop/internal/relayerset/solana.go index 06e3facc19..ab0decff59 100644 --- a/pkg/loop/internal/relayerset/solana.go +++ b/pkg/loop/internal/relayerset/solana.go @@ -12,7 +12,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/net" "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/pb/relayerset" "github.com/smartcontractkit/chainlink-common/pkg/types" - "github.com/smartcontractkit/chainlink-common/pkg/types/chains/solana" ) // solClient wraps the SolanaRelayerSetClient by attaching a RelayerID to SolClient requests. @@ -176,7 +175,7 @@ func (ss *solServer) QueryTrackedLogs(ctx context.Context, req *solpb.QueryTrack return nil, net.WrapRPCErr(err) } - logs, err := solService.QueryTrackedLogs(ctx, dExprs, ls) + logs, err := solService.QueryTrackedLogs(ctx, dExprs, ls, req.GetFilterName()) if err != nil { return nil, net.WrapRPCErr(err) } @@ -214,14 +213,11 @@ func (ss *solServer) GetAccountInfoWithOpts(ctx context.Context, req *solpb.GetA return nil, err } - addr, err := solpb.ConvertPublicKeyFromProto(req.GetAccount()) + dReq, err := solpb.ConvertGetAccountInfoRequestFromProto(req) if err != nil { return nil, net.WrapRPCErr(err) } - opts := solpb.ConvertGetAccountInfoOptsFromProto(req.GetOpts()) - - dReq := solana.GetAccountInfoRequest{Account: addr, Opts: opts} dResp, err := solService.GetAccountInfoWithOpts(ctx, dReq) if err != nil { return nil, net.WrapRPCErr(err) diff --git a/pkg/types/chains/solana/solana.go b/pkg/types/chains/solana/solana.go index c350a6ff86..2cd91ab520 100644 --- a/pkg/types/chains/solana/solana.go +++ b/pkg/types/chains/solana/solana.go @@ -350,6 +350,8 @@ type SimulateTXOpts struct { type GetAccountInfoRequest struct { Account PublicKey Opts *GetAccountInfoOpts + // If true, limits like response size limit may be applied. + IsExternal bool } type GetAccountInfoReply struct { @@ -360,6 +362,8 @@ type GetAccountInfoReply struct { type GetMultipleAccountsRequest struct { Accounts []PublicKey Opts *GetMultipleAccountsOpts + // If true, limits like response size limit may be applied. + IsExternal bool } type GetMultipleAccountsReply struct { @@ -477,6 +481,8 @@ type TransactionResultEnvelope struct { // arguments for solana-rpc GetTransaction call type GetTransactionRequest struct { Signature Signature + // If true, limits like response size limit may be applied. + IsExternal bool } // result of solana-rpc GetTransaction call @@ -517,7 +523,9 @@ type SimulateTXRequest struct { Receiver PublicKey // Encoded EncodedTransaction string - Opts *SimulateTXOpts + Opts *SimulateTXOpts + // If true, limits like response size limit may be applied. + IsExternal bool } type SimulateTXReply struct { diff --git a/pkg/types/mocks/solana_service.go b/pkg/types/mocks/solana_service.go index 36e5ac0024..cfd7648378 100644 --- a/pkg/types/mocks/solana_service.go +++ b/pkg/types/mocks/solana_service.go @@ -611,9 +611,9 @@ func (_c *SolanaService_GetTransaction_Call) RunAndReturn(run func(context.Conte return _c } -// QueryTrackedLogs provides a mock function with given fields: ctx, filterQuery, limitAndSort -func (_m *SolanaService) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort) ([]*solana.Log, error) { - ret := _m.Called(ctx, filterQuery, limitAndSort) +// QueryTrackedLogs provides a mock function with given fields: ctx, filterQuery, limitAndSort, filterName +func (_m *SolanaService) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort, filterName string) ([]*solana.Log, error) { + ret := _m.Called(ctx, filterQuery, limitAndSort, filterName) if len(ret) == 0 { panic("no return value specified for QueryTrackedLogs") @@ -621,19 +621,19 @@ func (_m *SolanaService) QueryTrackedLogs(ctx context.Context, filterQuery []que var r0 []*solana.Log var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort) ([]*solana.Log, error)); ok { - return rf(ctx, filterQuery, limitAndSort) + if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort, string) ([]*solana.Log, error)); ok { + return rf(ctx, filterQuery, limitAndSort, filterName) } - if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort) []*solana.Log); ok { - r0 = rf(ctx, filterQuery, limitAndSort) + if rf, ok := ret.Get(0).(func(context.Context, []query.Expression, query.LimitAndSort, string) []*solana.Log); ok { + r0 = rf(ctx, filterQuery, limitAndSort, filterName) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*solana.Log) } } - if rf, ok := ret.Get(1).(func(context.Context, []query.Expression, query.LimitAndSort) error); ok { - r1 = rf(ctx, filterQuery, limitAndSort) + if rf, ok := ret.Get(1).(func(context.Context, []query.Expression, query.LimitAndSort, string) error); ok { + r1 = rf(ctx, filterQuery, limitAndSort, filterName) } else { r1 = ret.Error(1) } @@ -650,13 +650,14 @@ type SolanaService_QueryTrackedLogs_Call struct { // - ctx context.Context // - filterQuery []query.Expression // - limitAndSort query.LimitAndSort -func (_e *SolanaService_Expecter) QueryTrackedLogs(ctx interface{}, filterQuery interface{}, limitAndSort interface{}) *SolanaService_QueryTrackedLogs_Call { - return &SolanaService_QueryTrackedLogs_Call{Call: _e.mock.On("QueryTrackedLogs", ctx, filterQuery, limitAndSort)} +// - filterName string +func (_e *SolanaService_Expecter) QueryTrackedLogs(ctx interface{}, filterQuery interface{}, limitAndSort interface{}, filterName interface{}) *SolanaService_QueryTrackedLogs_Call { + return &SolanaService_QueryTrackedLogs_Call{Call: _e.mock.On("QueryTrackedLogs", ctx, filterQuery, limitAndSort, filterName)} } -func (_c *SolanaService_QueryTrackedLogs_Call) Run(run func(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort)) *SolanaService_QueryTrackedLogs_Call { +func (_c *SolanaService_QueryTrackedLogs_Call) Run(run func(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort, filterName string)) *SolanaService_QueryTrackedLogs_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].([]query.Expression), args[2].(query.LimitAndSort)) + run(args[0].(context.Context), args[1].([]query.Expression), args[2].(query.LimitAndSort), args[3].(string)) }) return _c } @@ -666,7 +667,7 @@ func (_c *SolanaService_QueryTrackedLogs_Call) Return(_a0 []*solana.Log, _a1 err return _c } -func (_c *SolanaService_QueryTrackedLogs_Call) RunAndReturn(run func(context.Context, []query.Expression, query.LimitAndSort) ([]*solana.Log, error)) *SolanaService_QueryTrackedLogs_Call { +func (_c *SolanaService_QueryTrackedLogs_Call) RunAndReturn(run func(context.Context, []query.Expression, query.LimitAndSort, string) ([]*solana.Log, error)) *SolanaService_QueryTrackedLogs_Call { _c.Call.Return(run) return _c } diff --git a/pkg/types/relayer.go b/pkg/types/relayer.go index dc5c0f459e..24a4a722fa 100644 --- a/pkg/types/relayer.go +++ b/pkg/types/relayer.go @@ -250,11 +250,12 @@ type SolanaService interface { // noop guaranteed when filterName doesn't exist UnregisterLogTracking(ctx context.Context, filterName string) error - // QueryTrackedLogs retrieves logs from the log storage based on the provided - // query expression, sorting, and confidence level. It only returns logs that were - // collected through previously registered log filters. + // QueryTrackedLogs retrieves logs from log storage based on the provided query + // expression and sorting. When filterName is non-empty, results are scoped to + // that registered filter; an empty filterName queries across all filters (legacy). + // Confidence is expressed via query.Confidence in filterQuery (a no-op for Solana). QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, - limitAndSort query.LimitAndSort) ([]*solana.Log, error) + limitAndSort query.LimitAndSort, filterName string) ([]*solana.Log, error) // GetLatestLPBlock retrieves current LatestBlock from cache perspective GetLatestLPBlock(ctx context.Context) (*solana.LPBlock, error) @@ -573,7 +574,7 @@ func (uss *UnimplementedSolanaService) RegisterLogTracking(ctx context.Context, func (uss *UnimplementedSolanaService) UnregisterLogTracking(ctx context.Context, filterName string) error { return status.Errorf(codes.Unimplemented, "method UnregisterLogTracking not implemented") } -func (uss *UnimplementedSolanaService) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort) ([]*solana.Log, error) { +func (uss *UnimplementedSolanaService) QueryTrackedLogs(ctx context.Context, filterQuery []query.Expression, limitAndSort query.LimitAndSort, filterName string) ([]*solana.Log, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryTrackedLogs not implemented") } func (uss *UnimplementedSolanaService) GetBalance(ctx context.Context, req solana.GetBalanceRequest) (*solana.GetBalanceReply, error) {