diff --git a/.mockery.yaml b/.mockery.yaml index b07b652c9..91e44a850 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -24,6 +24,7 @@ packages: TONService: SolanaService: AptosService: + StellarService: github.com/smartcontractkit/chainlink-common/pkg/types/core: interfaces: CapabilitiesRegistry: diff --git a/pkg/chains/stellar/generate.go b/pkg/chains/stellar/generate.go new file mode 100644 index 000000000..1bc203d54 --- /dev/null +++ b/pkg/chains/stellar/generate.go @@ -0,0 +1,2 @@ +//go:generate go run ./generate +package stellar diff --git a/pkg/chains/stellar/generate/main.go b/pkg/chains/stellar/generate/main.go new file mode 100644 index 000000000..fa793b94f --- /dev/null +++ b/pkg/chains/stellar/generate/main.go @@ -0,0 +1,11 @@ +package main + +import "github.com/smartcontractkit/chainlink-protos/cre/go/installer/pkg" + +func main() { + gen := &pkg.ProtocGen{Plugins: []pkg.Plugin{pkg.GoPlugin, {Name: "go-grpc"}}} + gen.AddSourceDirectories("../..", ".") + if err := gen.GenerateFile("stellar.proto", "."); err != nil { + panic(err) + } +} diff --git a/pkg/chains/stellar/proto_helpers.go b/pkg/chains/stellar/proto_helpers.go new file mode 100644 index 000000000..31a9620bf --- /dev/null +++ b/pkg/chains/stellar/proto_helpers.go @@ -0,0 +1,205 @@ +package stellar + +import ( + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + + "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" +) + +// xdrToBytes base64-decodes a domain XDR string to raw binary. +func xdrToBytes(x stellar.XDR) ([]byte, error) { + b, err := base64.StdEncoding.DecodeString(string(x)) + if err != nil { + return nil, fmt.Errorf("invalid base64 XDR %q: %w", x, err) + } + return b, nil +} + +// bytesToXDR base64-encodes raw binary XDR to the domain type. +func bytesToXDR(b []byte) stellar.XDR { + return stellar.XDR(base64.StdEncoding.EncodeToString(b)) +} + +// hashToBytes hex-decodes a domain hash string to raw bytes. +func hashToBytes(h string) ([]byte, error) { + b, err := hex.DecodeString(h) + if err != nil { + return nil, fmt.Errorf("invalid hex hash %q: %w", h, err) + } + return b, nil +} + +// bytesToHash hex-encodes raw hash bytes to a string. +func bytesToHash(b []byte) string { + return hex.EncodeToString(b) +} + +// ---- GetLedgerEntries ---- + +// ConvertGetLedgerEntriesRequestToProto converts a domain GetLedgerEntriesRequest to its proto representation. +func ConvertGetLedgerEntriesRequestToProto(req stellar.GetLedgerEntriesRequest) (*GetLedgerEntriesRequest, error) { + keys := make([][]byte, len(req.Keys)) + var errs []error + for i, k := range req.Keys { + b, err := xdrToBytes(k) + if err != nil { + errs = append(errs, fmt.Errorf("key[%d]: %w", i, err)) + continue + } + keys[i] = b + } + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return &GetLedgerEntriesRequest{Keys: keys}, nil +} + +// ConvertGetLedgerEntriesRequestFromProto converts a proto GetLedgerEntriesRequest to the domain type. +func ConvertGetLedgerEntriesRequestFromProto(p *GetLedgerEntriesRequest) (stellar.GetLedgerEntriesRequest, error) { + if p == nil { + return stellar.GetLedgerEntriesRequest{}, fmt.Errorf("get ledger entries request is nil") + } + if len(p.GetKeys()) == 0 { + return stellar.GetLedgerEntriesRequest{}, fmt.Errorf("ledger entry keys are empty") + } + rawKeys := p.GetKeys() + keys := make([]stellar.XDR, len(rawKeys)) + for i, k := range rawKeys { + keys[i] = bytesToXDR(k) + } + return stellar.GetLedgerEntriesRequest{Keys: keys}, nil +} + +// ConvertLedgerEntryResultToProto converts a domain LedgerEntryResult to its proto representation. +func ConvertLedgerEntryResultToProto(r stellar.LedgerEntryResult) (*LedgerEntryResult, error) { + keyXDR, err := xdrToBytes(r.KeyXDR) + if err != nil { + return nil, fmt.Errorf("key_xdr: %w", err) + } + dataXDR, err := xdrToBytes(r.DataXDR) + if err != nil { + return nil, fmt.Errorf("data_xdr: %w", err) + } + extXDR, err := xdrToBytes(r.ExtensionXDR) + if err != nil { + return nil, fmt.Errorf("extension_xdr: %w", err) + } + pr := &LedgerEntryResult{ + KeyXdr: keyXDR, + DataXdr: dataXDR, + LastModifiedLedger: r.LastModifiedLedger, + ExtensionXdr: extXDR, + } + if r.LiveUntilLedgerSeq != nil { + pr.HasLiveUntilLedgerSeq = true + pr.LiveUntilLedgerSeq = *r.LiveUntilLedgerSeq + } + return pr, nil +} + +// ConvertLedgerEntryResultFromProto converts a proto LedgerEntryResult to the domain type. +func ConvertLedgerEntryResultFromProto(p *LedgerEntryResult) (stellar.LedgerEntryResult, error) { + if p == nil { + return stellar.LedgerEntryResult{}, fmt.Errorf("ledger entry result is nil") + } + r := stellar.LedgerEntryResult{ + KeyXDR: bytesToXDR(p.GetKeyXdr()), + DataXDR: bytesToXDR(p.GetDataXdr()), + LastModifiedLedger: p.GetLastModifiedLedger(), + ExtensionXDR: bytesToXDR(p.GetExtensionXdr()), + } + if p.GetHasLiveUntilLedgerSeq() { + v := p.GetLiveUntilLedgerSeq() + r.LiveUntilLedgerSeq = &v + } + return r, nil +} + +// ConvertGetLedgerEntriesResponseToProto converts a domain GetLedgerEntriesResponse to its proto representation. +func ConvertGetLedgerEntriesResponseToProto(resp stellar.GetLedgerEntriesResponse) (*GetLedgerEntriesResponse, error) { + entries := make([]*LedgerEntryResult, 0, len(resp.Entries)) + var errs []error + for i, e := range resp.Entries { + protoEntry, err := ConvertLedgerEntryResultToProto(e) + if err != nil { + errs = append(errs, fmt.Errorf("entry[%d]: %w", i, err)) + continue + } + entries = append(entries, protoEntry) + } + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return &GetLedgerEntriesResponse{ + Entries: entries, + LatestLedger: resp.LatestLedger, + }, nil +} + +// ConvertGetLedgerEntriesResponseFromProto converts a proto GetLedgerEntriesResponse to the domain type. +func ConvertGetLedgerEntriesResponseFromProto(p *GetLedgerEntriesResponse) (stellar.GetLedgerEntriesResponse, error) { + if p == nil { + return stellar.GetLedgerEntriesResponse{}, fmt.Errorf("get ledger entries response is nil") + } + entries := make([]stellar.LedgerEntryResult, 0, len(p.GetEntries())) + var errs []error + for i, pe := range p.GetEntries() { + e, err := ConvertLedgerEntryResultFromProto(pe) + if err != nil { + errs = append(errs, fmt.Errorf("entry[%d]: %w", i, err)) + continue + } + entries = append(entries, e) + } + if len(errs) > 0 { + return stellar.GetLedgerEntriesResponse{}, errors.Join(errs...) + } + return stellar.GetLedgerEntriesResponse{ + Entries: entries, + LatestLedger: p.GetLatestLedger(), + }, nil +} + +// ---- GetLatestLedger ---- + +// ConvertGetLatestLedgerResponseToProto converts a domain GetLatestLedgerResponse to its proto representation. +func ConvertGetLatestLedgerResponseToProto(resp stellar.GetLatestLedgerResponse) (*GetLatestLedgerResponse, error) { + hash, err := hashToBytes(string(resp.Hash)) + if err != nil { + return nil, fmt.Errorf("hash: %w", err) + } + headerXDR, err := xdrToBytes(resp.LedgerHeaderXDR) + if err != nil { + return nil, fmt.Errorf("ledger_header_xdr: %w", err) + } + metaXDR, err := xdrToBytes(resp.LedgerMetadataXDR) + if err != nil { + return nil, fmt.Errorf("ledger_metadata_xdr: %w", err) + } + return &GetLatestLedgerResponse{ + Hash: hash, + ProtocolVersion: resp.ProtocolVersion, + Sequence: resp.Sequence, + LedgerCloseTime: resp.LedgerCloseTime, + LedgerHeaderXdr: headerXDR, + LedgerMetadataXdr: metaXDR, + }, nil +} + +// ConvertGetLatestLedgerResponseFromProto converts a proto GetLatestLedgerResponse to the domain type. +func ConvertGetLatestLedgerResponseFromProto(p *GetLatestLedgerResponse) (stellar.GetLatestLedgerResponse, error) { + if p == nil { + return stellar.GetLatestLedgerResponse{}, fmt.Errorf("get latest ledger response is nil") + } + return stellar.GetLatestLedgerResponse{ + Hash: stellar.LedgerHash(bytesToHash(p.GetHash())), + ProtocolVersion: p.GetProtocolVersion(), + Sequence: p.GetSequence(), + LedgerCloseTime: p.GetLedgerCloseTime(), + LedgerHeaderXDR: bytesToXDR(p.GetLedgerHeaderXdr()), + LedgerMetadataXDR: bytesToXDR(p.GetLedgerMetadataXdr()), + }, nil +} diff --git a/pkg/chains/stellar/stellar.pb.go b/pkg/chains/stellar/stellar.pb.go new file mode 100644 index 000000000..a347ce554 --- /dev/null +++ b/pkg/chains/stellar/stellar.pb.go @@ -0,0 +1,377 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v5.29.3 +// source: stellar.proto + +package stellar + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// GetLedgerEntriesRequest fetches ledger entries by XDR-encoded keys. +type GetLedgerEntriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Keys [][]byte `protobuf:"bytes,1,rep,name=keys,proto3" json:"keys,omitempty"` // each key as binary XDR (LedgerKey) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetLedgerEntriesRequest) Reset() { + *x = GetLedgerEntriesRequest{} + mi := &file_stellar_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetLedgerEntriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLedgerEntriesRequest) ProtoMessage() {} + +func (x *GetLedgerEntriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_stellar_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLedgerEntriesRequest.ProtoReflect.Descriptor instead. +func (*GetLedgerEntriesRequest) Descriptor() ([]byte, []int) { + return file_stellar_proto_rawDescGZIP(), []int{0} +} + +func (x *GetLedgerEntriesRequest) GetKeys() [][]byte { + if x != nil { + return x.Keys + } + return nil +} + +// LedgerEntryResult is a single ledger entry. +type LedgerEntryResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + KeyXdr []byte `protobuf:"bytes,1,opt,name=key_xdr,json=keyXdr,proto3" json:"key_xdr,omitempty"` // LedgerKey binary XDR + DataXdr []byte `protobuf:"bytes,2,opt,name=data_xdr,json=dataXdr,proto3" json:"data_xdr,omitempty"` // LedgerEntry binary XDR + LastModifiedLedger uint32 `protobuf:"varint,3,opt,name=last_modified_ledger,json=lastModifiedLedger,proto3" json:"last_modified_ledger,omitempty"` + // has_live_until_ledger_seq indicates whether live_until_ledger_seq is set. + HasLiveUntilLedgerSeq bool `protobuf:"varint,4,opt,name=has_live_until_ledger_seq,json=hasLiveUntilLedgerSeq,proto3" json:"has_live_until_ledger_seq,omitempty"` + LiveUntilLedgerSeq uint32 `protobuf:"varint,5,opt,name=live_until_ledger_seq,json=liveUntilLedgerSeq,proto3" json:"live_until_ledger_seq,omitempty"` + ExtensionXdr []byte `protobuf:"bytes,6,opt,name=extension_xdr,json=extensionXdr,proto3" json:"extension_xdr,omitempty"` // LedgerEntry extension binary XDR; empty if absent + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LedgerEntryResult) Reset() { + *x = LedgerEntryResult{} + mi := &file_stellar_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LedgerEntryResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LedgerEntryResult) ProtoMessage() {} + +func (x *LedgerEntryResult) ProtoReflect() protoreflect.Message { + mi := &file_stellar_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LedgerEntryResult.ProtoReflect.Descriptor instead. +func (*LedgerEntryResult) Descriptor() ([]byte, []int) { + return file_stellar_proto_rawDescGZIP(), []int{1} +} + +func (x *LedgerEntryResult) GetKeyXdr() []byte { + if x != nil { + return x.KeyXdr + } + return nil +} + +func (x *LedgerEntryResult) GetDataXdr() []byte { + if x != nil { + return x.DataXdr + } + return nil +} + +func (x *LedgerEntryResult) GetLastModifiedLedger() uint32 { + if x != nil { + return x.LastModifiedLedger + } + return 0 +} + +func (x *LedgerEntryResult) GetHasLiveUntilLedgerSeq() bool { + if x != nil { + return x.HasLiveUntilLedgerSeq + } + return false +} + +func (x *LedgerEntryResult) GetLiveUntilLedgerSeq() uint32 { + if x != nil { + return x.LiveUntilLedgerSeq + } + return 0 +} + +func (x *LedgerEntryResult) GetExtensionXdr() []byte { + if x != nil { + return x.ExtensionXdr + } + return nil +} + +// GetLedgerEntriesResponse contains the requested ledger entries. +type GetLedgerEntriesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Entries []*LedgerEntryResult `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` + LatestLedger uint32 `protobuf:"varint,2,opt,name=latest_ledger,json=latestLedger,proto3" json:"latest_ledger,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetLedgerEntriesResponse) Reset() { + *x = GetLedgerEntriesResponse{} + mi := &file_stellar_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetLedgerEntriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLedgerEntriesResponse) ProtoMessage() {} + +func (x *GetLedgerEntriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_stellar_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLedgerEntriesResponse.ProtoReflect.Descriptor instead. +func (*GetLedgerEntriesResponse) Descriptor() ([]byte, []int) { + return file_stellar_proto_rawDescGZIP(), []int{2} +} + +func (x *GetLedgerEntriesResponse) GetEntries() []*LedgerEntryResult { + if x != nil { + return x.Entries + } + return nil +} + +func (x *GetLedgerEntriesResponse) GetLatestLedger() uint32 { + if x != nil { + return x.LatestLedger + } + return 0 +} + +// GetLatestLedgerResponse holds current ledger state. +type GetLatestLedgerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` // 32-byte raw ledger hash + ProtocolVersion uint32 `protobuf:"varint,2,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + Sequence uint32 `protobuf:"varint,3,opt,name=sequence,proto3" json:"sequence,omitempty"` + LedgerCloseTime int64 `protobuf:"varint,4,opt,name=ledger_close_time,json=ledgerCloseTime,proto3" json:"ledger_close_time,omitempty"` + LedgerHeaderXdr []byte `protobuf:"bytes,5,opt,name=ledger_header_xdr,json=ledgerHeaderXdr,proto3" json:"ledger_header_xdr,omitempty"` // LedgerHeader binary XDR + LedgerMetadataXdr []byte `protobuf:"bytes,6,opt,name=ledger_metadata_xdr,json=ledgerMetadataXdr,proto3" json:"ledger_metadata_xdr,omitempty"` // LedgerCloseMetaV2 binary XDR + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetLatestLedgerResponse) Reset() { + *x = GetLatestLedgerResponse{} + mi := &file_stellar_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetLatestLedgerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetLatestLedgerResponse) ProtoMessage() {} + +func (x *GetLatestLedgerResponse) ProtoReflect() protoreflect.Message { + mi := &file_stellar_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetLatestLedgerResponse.ProtoReflect.Descriptor instead. +func (*GetLatestLedgerResponse) Descriptor() ([]byte, []int) { + return file_stellar_proto_rawDescGZIP(), []int{3} +} + +func (x *GetLatestLedgerResponse) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *GetLatestLedgerResponse) GetProtocolVersion() uint32 { + if x != nil { + return x.ProtocolVersion + } + return 0 +} + +func (x *GetLatestLedgerResponse) GetSequence() uint32 { + if x != nil { + return x.Sequence + } + return 0 +} + +func (x *GetLatestLedgerResponse) GetLedgerCloseTime() int64 { + if x != nil { + return x.LedgerCloseTime + } + return 0 +} + +func (x *GetLatestLedgerResponse) GetLedgerHeaderXdr() []byte { + if x != nil { + return x.LedgerHeaderXdr + } + return nil +} + +func (x *GetLatestLedgerResponse) GetLedgerMetadataXdr() []byte { + if x != nil { + return x.LedgerMetadataXdr + } + return nil +} + +var File_stellar_proto protoreflect.FileDescriptor + +const file_stellar_proto_rawDesc = "" + + "\n" + + "\rstellar.proto\x12\floop.stellar\x1a\x1bgoogle/protobuf/empty.proto\"-\n" + + "\x17GetLedgerEntriesRequest\x12\x12\n" + + "\x04keys\x18\x01 \x03(\fR\x04keys\"\x8b\x02\n" + + "\x11LedgerEntryResult\x12\x17\n" + + "\akey_xdr\x18\x01 \x01(\fR\x06keyXdr\x12\x19\n" + + "\bdata_xdr\x18\x02 \x01(\fR\adataXdr\x120\n" + + "\x14last_modified_ledger\x18\x03 \x01(\rR\x12lastModifiedLedger\x128\n" + + "\x19has_live_until_ledger_seq\x18\x04 \x01(\bR\x15hasLiveUntilLedgerSeq\x121\n" + + "\x15live_until_ledger_seq\x18\x05 \x01(\rR\x12liveUntilLedgerSeq\x12#\n" + + "\rextension_xdr\x18\x06 \x01(\fR\fextensionXdr\"z\n" + + "\x18GetLedgerEntriesResponse\x129\n" + + "\aentries\x18\x01 \x03(\v2\x1f.loop.stellar.LedgerEntryResultR\aentries\x12#\n" + + "\rlatest_ledger\x18\x02 \x01(\rR\flatestLedger\"\xfc\x01\n" + + "\x17GetLatestLedgerResponse\x12\x12\n" + + "\x04hash\x18\x01 \x01(\fR\x04hash\x12)\n" + + "\x10protocol_version\x18\x02 \x01(\rR\x0fprotocolVersion\x12\x1a\n" + + "\bsequence\x18\x03 \x01(\rR\bsequence\x12*\n" + + "\x11ledger_close_time\x18\x04 \x01(\x03R\x0fledgerCloseTime\x12*\n" + + "\x11ledger_header_xdr\x18\x05 \x01(\fR\x0fledgerHeaderXdr\x12.\n" + + "\x13ledger_metadata_xdr\x18\x06 \x01(\fR\x11ledgerMetadataXdr2\xbe\x01\n" + + "\aStellar\x12a\n" + + "\x10GetLedgerEntries\x12%.loop.stellar.GetLedgerEntriesRequest\x1a&.loop.stellar.GetLedgerEntriesResponse\x12P\n" + + "\x0fGetLatestLedger\x12\x16.google.protobuf.Empty\x1a%.loop.stellar.GetLatestLedgerResponseBAZ?github.com/smartcontractkit/chainlink-common/pkg/chains/stellarb\x06proto3" + +var ( + file_stellar_proto_rawDescOnce sync.Once + file_stellar_proto_rawDescData []byte +) + +func file_stellar_proto_rawDescGZIP() []byte { + file_stellar_proto_rawDescOnce.Do(func() { + file_stellar_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_stellar_proto_rawDesc), len(file_stellar_proto_rawDesc))) + }) + return file_stellar_proto_rawDescData +} + +var file_stellar_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_stellar_proto_goTypes = []any{ + (*GetLedgerEntriesRequest)(nil), // 0: loop.stellar.GetLedgerEntriesRequest + (*LedgerEntryResult)(nil), // 1: loop.stellar.LedgerEntryResult + (*GetLedgerEntriesResponse)(nil), // 2: loop.stellar.GetLedgerEntriesResponse + (*GetLatestLedgerResponse)(nil), // 3: loop.stellar.GetLatestLedgerResponse + (*emptypb.Empty)(nil), // 4: google.protobuf.Empty +} +var file_stellar_proto_depIdxs = []int32{ + 1, // 0: loop.stellar.GetLedgerEntriesResponse.entries:type_name -> loop.stellar.LedgerEntryResult + 0, // 1: loop.stellar.Stellar.GetLedgerEntries:input_type -> loop.stellar.GetLedgerEntriesRequest + 4, // 2: loop.stellar.Stellar.GetLatestLedger:input_type -> google.protobuf.Empty + 2, // 3: loop.stellar.Stellar.GetLedgerEntries:output_type -> loop.stellar.GetLedgerEntriesResponse + 3, // 4: loop.stellar.Stellar.GetLatestLedger:output_type -> loop.stellar.GetLatestLedgerResponse + 3, // [3:5] is the sub-list for method output_type + 1, // [1:3] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_stellar_proto_init() } +func file_stellar_proto_init() { + if File_stellar_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_stellar_proto_rawDesc), len(file_stellar_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_stellar_proto_goTypes, + DependencyIndexes: file_stellar_proto_depIdxs, + MessageInfos: file_stellar_proto_msgTypes, + }.Build() + File_stellar_proto = out.File + file_stellar_proto_goTypes = nil + file_stellar_proto_depIdxs = nil +} diff --git a/pkg/chains/stellar/stellar.proto b/pkg/chains/stellar/stellar.proto new file mode 100644 index 000000000..b0bf6d118 --- /dev/null +++ b/pkg/chains/stellar/stellar.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; +option go_package = "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar"; + +import "google/protobuf/empty.proto"; + +package loop.stellar; + +service Stellar { + rpc GetLedgerEntries(GetLedgerEntriesRequest) returns (GetLedgerEntriesResponse); + rpc GetLatestLedger(google.protobuf.Empty) returns (GetLatestLedgerResponse); +} + +// GetLedgerEntriesRequest fetches ledger entries by XDR-encoded keys. +message GetLedgerEntriesRequest { + repeated bytes keys = 1; // each key as binary XDR (LedgerKey) +} + +// LedgerEntryResult is a single ledger entry. +message LedgerEntryResult { + bytes key_xdr = 1; // LedgerKey binary XDR + bytes data_xdr = 2; // LedgerEntry binary XDR + uint32 last_modified_ledger = 3; + // has_live_until_ledger_seq indicates whether live_until_ledger_seq is set. + bool has_live_until_ledger_seq = 4; + uint32 live_until_ledger_seq = 5; + bytes extension_xdr = 6; // LedgerEntry extension binary XDR; empty if absent +} + +// GetLedgerEntriesResponse contains the requested ledger entries. +message GetLedgerEntriesResponse { + repeated LedgerEntryResult entries = 1; + uint32 latest_ledger = 2; +} + +// GetLatestLedgerResponse holds current ledger state. +message GetLatestLedgerResponse { + bytes hash = 1; // 32-byte raw ledger hash + uint32 protocol_version = 2; + uint32 sequence = 3; + int64 ledger_close_time = 4; + bytes ledger_header_xdr = 5; // LedgerHeader binary XDR + bytes ledger_metadata_xdr = 6; // LedgerCloseMetaV2 binary XDR +} diff --git a/pkg/chains/stellar/stellar_grpc.pb.go b/pkg/chains/stellar/stellar_grpc.pb.go new file mode 100644 index 000000000..7f86cc3a6 --- /dev/null +++ b/pkg/chains/stellar/stellar_grpc.pb.go @@ -0,0 +1,160 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: stellar.proto + +package stellar + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Stellar_GetLedgerEntries_FullMethodName = "/loop.stellar.Stellar/GetLedgerEntries" + Stellar_GetLatestLedger_FullMethodName = "/loop.stellar.Stellar/GetLatestLedger" +) + +// StellarClient is the client API for Stellar service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type StellarClient interface { + GetLedgerEntries(ctx context.Context, in *GetLedgerEntriesRequest, opts ...grpc.CallOption) (*GetLedgerEntriesResponse, error) + GetLatestLedger(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetLatestLedgerResponse, error) +} + +type stellarClient struct { + cc grpc.ClientConnInterface +} + +func NewStellarClient(cc grpc.ClientConnInterface) StellarClient { + return &stellarClient{cc} +} + +func (c *stellarClient) GetLedgerEntries(ctx context.Context, in *GetLedgerEntriesRequest, opts ...grpc.CallOption) (*GetLedgerEntriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetLedgerEntriesResponse) + err := c.cc.Invoke(ctx, Stellar_GetLedgerEntries_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *stellarClient) GetLatestLedger(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetLatestLedgerResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetLatestLedgerResponse) + err := c.cc.Invoke(ctx, Stellar_GetLatestLedger_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// StellarServer is the server API for Stellar service. +// All implementations must embed UnimplementedStellarServer +// for forward compatibility. +type StellarServer interface { + GetLedgerEntries(context.Context, *GetLedgerEntriesRequest) (*GetLedgerEntriesResponse, error) + GetLatestLedger(context.Context, *emptypb.Empty) (*GetLatestLedgerResponse, error) + mustEmbedUnimplementedStellarServer() +} + +// UnimplementedStellarServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedStellarServer struct{} + +func (UnimplementedStellarServer) GetLedgerEntries(context.Context, *GetLedgerEntriesRequest) (*GetLedgerEntriesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetLedgerEntries not implemented") +} +func (UnimplementedStellarServer) GetLatestLedger(context.Context, *emptypb.Empty) (*GetLatestLedgerResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetLatestLedger not implemented") +} +func (UnimplementedStellarServer) mustEmbedUnimplementedStellarServer() {} +func (UnimplementedStellarServer) testEmbeddedByValue() {} + +// UnsafeStellarServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to StellarServer will +// result in compilation errors. +type UnsafeStellarServer interface { + mustEmbedUnimplementedStellarServer() +} + +func RegisterStellarServer(s grpc.ServiceRegistrar, srv StellarServer) { + // If the following call pancis, it indicates UnimplementedStellarServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Stellar_ServiceDesc, srv) +} + +func _Stellar_GetLedgerEntries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetLedgerEntriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StellarServer).GetLedgerEntries(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Stellar_GetLedgerEntries_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StellarServer).GetLedgerEntries(ctx, req.(*GetLedgerEntriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Stellar_GetLatestLedger_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StellarServer).GetLatestLedger(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Stellar_GetLatestLedger_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StellarServer).GetLatestLedger(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +// Stellar_ServiceDesc is the grpc.ServiceDesc for Stellar service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Stellar_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "loop.stellar.Stellar", + HandlerType: (*StellarServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetLedgerEntries", + Handler: _Stellar_GetLedgerEntries_Handler, + }, + { + MethodName: "GetLatestLedger", + Handler: _Stellar_GetLatestLedger_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "stellar.proto", +} diff --git a/pkg/loop/internal/pb/relayerset/helper.go b/pkg/loop/internal/pb/relayerset/helper.go index e32703c0c..512f746c5 100644 --- a/pkg/loop/internal/pb/relayerset/helper.go +++ b/pkg/loop/internal/pb/relayerset/helper.go @@ -6,6 +6,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/chains/aptos" "github.com/smartcontractkit/chainlink-common/pkg/chains/evm" "github.com/smartcontractkit/chainlink-common/pkg/chains/solana" + "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" "github.com/smartcontractkit/chainlink-common/pkg/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/pb" ) @@ -15,6 +16,7 @@ type HasChainServers interface { EVMServer() evm.EVMServer TONServer() ton.TONServer AptosServer() aptos.AptosServer + StellarServer() stellar.StellarServer ContractReaderServer() pb.ContractReaderServer } @@ -30,6 +32,7 @@ func RegisterRelayerSetServerWithDependants(s grpc.ServiceRegistrar, srv Relayer evm.RegisterEVMServer(s, h.EVMServer()) ton.RegisterTONServer(s, h.TONServer()) aptos.RegisterAptosServer(s, h.AptosServer()) + stellar.RegisterStellarServer(s, h.StellarServer()) pb.RegisterContractReaderServer(s, h.ContractReaderServer()) } } diff --git a/pkg/loop/internal/relayer/relayer.go b/pkg/loop/internal/relayer/relayer.go index 26220c9b5..4bbc4a95e 100644 --- a/pkg/loop/internal/relayer/relayer.go +++ b/pkg/loop/internal/relayer/relayer.go @@ -17,6 +17,7 @@ import ( aptospb "github.com/smartcontractkit/chainlink-common/pkg/chains/aptos" evmpb "github.com/smartcontractkit/chainlink-common/pkg/chains/evm" solpb "github.com/smartcontractkit/chainlink-common/pkg/chains/solana" + stelpb "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" tonpb "github.com/smartcontractkit/chainlink-common/pkg/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/core/services/capability" @@ -170,6 +171,9 @@ func (p *pluginRelayerServer) NewRelayer(ctx context.Context, request *pb.NewRel if aptosService, ok := r.(types.AptosService); ok { aptospb.RegisterAptosServer(s, newAptosServer(aptosService, p.BrokerExt)) } + if stellarService, ok := r.(types.StellarService); ok { + stelpb.RegisterStellarServer(s, newStellarServer(stellarService, p.BrokerExt)) + } }, rRes, ksRes, ksCSARes, crRes) if err != nil { return nil, err @@ -183,11 +187,12 @@ type relayerClient struct { *net.BrokerExt *goplugin.ServiceClient - relayer pb.RelayerClient - evmClient evmpb.EVMClient - tonClient tonpb.TONClient - solClient solpb.SolanaClient - aptosClient aptospb.AptosClient + relayer pb.RelayerClient + evmClient evmpb.EVMClient + tonClient tonpb.TONClient + solClient solpb.SolanaClient + aptosClient aptospb.AptosClient + stellarClient stelpb.StellarClient } func newRelayerClient(b *net.BrokerExt, conn grpc.ClientConnInterface) *relayerClient { @@ -199,6 +204,7 @@ func newRelayerClient(b *net.BrokerExt, conn grpc.ClientConnInterface) *relayerC tonpb.NewTONClient(conn), solpb.NewSolanaClient(conn), aptospb.NewAptosClient(conn), + stelpb.NewStellarClient(conn), } } @@ -474,6 +480,12 @@ func (r *relayerClient) Aptos() (types.AptosService, error) { }, nil } +func (r *relayerClient) Stellar() (types.StellarService, error) { + return &StellarClient{ + r.stellarClient, + }, nil +} + var _ pb.RelayerServer = (*relayerServer)(nil) // relayerServer exposes [Relayer] as a GRPC [pb.RelayerServer]. diff --git a/pkg/loop/internal/relayer/stellar.go b/pkg/loop/internal/relayer/stellar.go new file mode 100644 index 000000000..b534f063d --- /dev/null +++ b/pkg/loop/internal/relayer/stellar.go @@ -0,0 +1,96 @@ +package relayer + +import ( + "context" + "fmt" + + "google.golang.org/protobuf/types/known/emptypb" + + stelpb "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" + "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/net" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" +) + +var _ types.StellarService = (*StellarClient)(nil) + +// StellarClient wraps a stelpb.StellarClient gRPC stub and exposes it as types.StellarService. +type StellarClient struct { + grpcClient stelpb.StellarClient +} + +// NewStellarClient returns a StellarClient that delegates to the provided gRPC client. +func NewStellarClient(client stelpb.StellarClient) *StellarClient { + return &StellarClient{grpcClient: client} +} + +func (sc *StellarClient) GetLedgerEntries(ctx context.Context, req stellar.GetLedgerEntriesRequest) (stellar.GetLedgerEntriesResponse, error) { + pReq, err := stelpb.ConvertGetLedgerEntriesRequestToProto(req) + if err != nil { + return stellar.GetLedgerEntriesResponse{}, fmt.Errorf("invalid GetLedgerEntries request: %w", err) + } + pResp, err := sc.grpcClient.GetLedgerEntries(ctx, pReq) + if err != nil { + return stellar.GetLedgerEntriesResponse{}, net.WrapRPCErr(err) + } + resp, err := stelpb.ConvertGetLedgerEntriesResponseFromProto(pResp) + if err != nil { + return stellar.GetLedgerEntriesResponse{}, fmt.Errorf("invalid GetLedgerEntries response: %w", err) + } + return resp, nil +} + +func (sc *StellarClient) GetLatestLedger(ctx context.Context) (stellar.GetLatestLedgerResponse, error) { + pResp, err := sc.grpcClient.GetLatestLedger(ctx, &emptypb.Empty{}) + if err != nil { + return stellar.GetLatestLedgerResponse{}, net.WrapRPCErr(err) + } + resp, err := stelpb.ConvertGetLatestLedgerResponseFromProto(pResp) + if err != nil { + return stellar.GetLatestLedgerResponse{}, fmt.Errorf("invalid GetLatestLedger response: %w", err) + } + return resp, nil +} + +// stellarServer wraps types.StellarService and exposes it as a stelpb.StellarServer gRPC endpoint. +type stellarServer struct { + stelpb.UnimplementedStellarServer + + *net.BrokerExt + + impl types.StellarService +} + +var _ stelpb.StellarServer = (*stellarServer)(nil) + +func newStellarServer(impl types.StellarService, b *net.BrokerExt) *stellarServer { + return &stellarServer{impl: impl, BrokerExt: b.WithName("StellarServer")} +} + +func (s *stellarServer) GetLedgerEntries(ctx context.Context, req *stelpb.GetLedgerEntriesRequest) (*stelpb.GetLedgerEntriesResponse, error) { + dReq, err := stelpb.ConvertGetLedgerEntriesRequestFromProto(req) + if err != nil { + return nil, fmt.Errorf("invalid GetLedgerEntries request: %w", err) + } + dResp, err := s.impl.GetLedgerEntries(ctx, dReq) + if err != nil { + return nil, net.WrapRPCErr(err) + } + pResp, err := stelpb.ConvertGetLedgerEntriesResponseToProto(dResp) + if err != nil { + return nil, fmt.Errorf("invalid GetLedgerEntries response: %w", err) + } + return pResp, nil +} + +func (s *stellarServer) GetLatestLedger(ctx context.Context, _ *emptypb.Empty) (*stelpb.GetLatestLedgerResponse, error) { + dResp, err := s.impl.GetLatestLedger(ctx) + if err != nil { + return nil, net.WrapRPCErr(err) + } + pResp, err := stelpb.ConvertGetLatestLedgerResponseToProto(dResp) + if err != nil { + return nil, fmt.Errorf("invalid GetLatestLedger response: %w", err) + } + return pResp, nil +} diff --git a/pkg/loop/internal/relayer/stellar_test.go b/pkg/loop/internal/relayer/stellar_test.go new file mode 100644 index 000000000..6a0539458 --- /dev/null +++ b/pkg/loop/internal/relayer/stellar_test.go @@ -0,0 +1,159 @@ +package relayer + +import ( + "context" + "net" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + + stelpb "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + loopnet "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/net" + "github.com/smartcontractkit/chainlink-common/pkg/types" + stellartypes "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" +) + +func TestStellarDomainRoundTripThroughGRPC(t *testing.T) { + t.Parallel() + + lis := bufconn.Listen(1024 * 1024) + s := grpc.NewServer() + + svc := &staticStellarService{} + stelpb.RegisterStellarServer(s, newStellarServer(svc, &loopnet.BrokerExt{ + BrokerConfig: loopnet.BrokerConfig{ + Logger: logger.Test(t), + }, + })) + + go func() { _ = s.Serve(lis) }() + defer s.Stop() + + ctx := t.Context() + conn, err := grpc.DialContext(ctx, "bufnet", //nolint:staticcheck + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) { + return lis.Dial() + }), + ) + require.NoError(t, err) + defer conn.Close() + + client := &StellarClient{grpcClient: stelpb.NewStellarClient(conn)} + + t.Run("GetLedgerEntries_WithLiveUntil", func(t *testing.T) { + liveUntil := uint32(500) + svc.getLedgerEntries = func(_ context.Context, req stellartypes.GetLedgerEntriesRequest) (stellartypes.GetLedgerEntriesResponse, error) { + require.Equal(t, []stellartypes.XDR{"a2V5MQ=="}, req.Keys) // base64("key1") + return stellartypes.GetLedgerEntriesResponse{ + LatestLedger: 50, + Entries: []stellartypes.LedgerEntryResult{ + { + KeyXDR: "a2V5MQ==", // base64("key1") + DataXDR: "ZGF0YTE=", // base64("data1") + LastModifiedLedger: 30, + LiveUntilLedgerSeq: &liveUntil, + }, + }, + }, nil + } + + resp, err := client.GetLedgerEntries(ctx, stellartypes.GetLedgerEntriesRequest{Keys: []stellartypes.XDR{"a2V5MQ=="}}) + require.NoError(t, err) + require.Equal(t, uint32(50), resp.LatestLedger) + require.Len(t, resp.Entries, 1) + require.Equal(t, stellartypes.XDR("a2V5MQ=="), resp.Entries[0].KeyXDR) + require.Equal(t, stellartypes.XDR("ZGF0YTE="), resp.Entries[0].DataXDR) + require.Equal(t, uint32(30), resp.Entries[0].LastModifiedLedger) + require.NotNil(t, resp.Entries[0].LiveUntilLedgerSeq) + require.Equal(t, liveUntil, *resp.Entries[0].LiveUntilLedgerSeq) + }) + + t.Run("GetLedgerEntries_NoLiveUntil", func(t *testing.T) { + svc.getLedgerEntries = func(_ context.Context, _ stellartypes.GetLedgerEntriesRequest) (stellartypes.GetLedgerEntriesResponse, error) { + return stellartypes.GetLedgerEntriesResponse{ + LatestLedger: 60, + Entries: []stellartypes.LedgerEntryResult{ + { + KeyXDR: "a2V5Mg==", // base64("key2") + DataXDR: "data2XDR", // valid 8-char base64 + LastModifiedLedger: 40, + LiveUntilLedgerSeq: nil, + }, + }, + }, nil + } + + resp, err := client.GetLedgerEntries(ctx, stellartypes.GetLedgerEntriesRequest{Keys: []stellartypes.XDR{"a2V5Mg=="}}) + require.NoError(t, err) + require.Len(t, resp.Entries, 1) + require.Nil(t, resp.Entries[0].LiveUntilLedgerSeq) + require.Equal(t, uint32(60), resp.LatestLedger) + }) + + t.Run("GetLedgerEntries_MixedLiveUntil", func(t *testing.T) { + // Two entries in one response: one with LiveUntilLedgerSeq set, one without. + // Guards against the loop in ConvertGetLedgerEntriesResponseFromProto carrying + // the HasLiveUntilLedgerSeq bool from one entry into the next. + // "azE=", "azI=", "ZDE=", "ZDI=" are valid 4-char base64 values. + liveUntil := uint32(777) + svc.getLedgerEntries = func(_ context.Context, _ stellartypes.GetLedgerEntriesRequest) (stellartypes.GetLedgerEntriesResponse, error) { + return stellartypes.GetLedgerEntriesResponse{ + LatestLedger: 70, + Entries: []stellartypes.LedgerEntryResult{ + {KeyXDR: "azE=", DataXDR: "ZDE=", LastModifiedLedger: 10, LiveUntilLedgerSeq: &liveUntil}, + {KeyXDR: "azI=", DataXDR: "ZDI=", LastModifiedLedger: 20, LiveUntilLedgerSeq: nil}, + }, + }, nil + } + + resp, err := client.GetLedgerEntries(ctx, stellartypes.GetLedgerEntriesRequest{Keys: []stellartypes.XDR{"azE=", "azI="}}) + require.NoError(t, err) + require.Len(t, resp.Entries, 2) + require.NotNil(t, resp.Entries[0].LiveUntilLedgerSeq) + require.Equal(t, liveUntil, *resp.Entries[0].LiveUntilLedgerSeq) + require.Nil(t, resp.Entries[1].LiveUntilLedgerSeq) + }) + + t.Run("GetLatestLedger", func(t *testing.T) { + svc.getLatestLedger = func(_ context.Context) (stellartypes.GetLatestLedgerResponse, error) { + return stellartypes.GetLatestLedgerResponse{ + Hash: "deadbeef", // valid 4-byte hex + ProtocolVersion: 21, + Sequence: 1234, + LedgerCloseTime: 9876543210, + }, nil + } + + resp, err := client.GetLatestLedger(ctx) + require.NoError(t, err) + require.Equal(t, stellartypes.LedgerHash("deadbeef"), resp.Hash) + require.Equal(t, uint32(21), resp.ProtocolVersion) + require.Equal(t, uint32(1234), resp.Sequence) + require.Equal(t, int64(9876543210), resp.LedgerCloseTime) + }) +} + +type staticStellarService struct { + types.UnimplementedStellarService + getLedgerEntries func(ctx context.Context, req stellartypes.GetLedgerEntriesRequest) (stellartypes.GetLedgerEntriesResponse, error) + getLatestLedger func(ctx context.Context) (stellartypes.GetLatestLedgerResponse, error) +} + +func (s *staticStellarService) GetLedgerEntries(ctx context.Context, req stellartypes.GetLedgerEntriesRequest) (stellartypes.GetLedgerEntriesResponse, error) { + if s.getLedgerEntries == nil { + return s.UnimplementedStellarService.GetLedgerEntries(ctx, req) + } + return s.getLedgerEntries(ctx, req) +} + +func (s *staticStellarService) GetLatestLedger(ctx context.Context) (stellartypes.GetLatestLedgerResponse, error) { + if s.getLatestLedger == nil { + return s.UnimplementedStellarService.GetLatestLedger(ctx) + } + return s.getLatestLedger(ctx) +} diff --git a/pkg/loop/internal/relayerset/client.go b/pkg/loop/internal/relayerset/client.go index 3c858080c..7df56c38a 100644 --- a/pkg/loop/internal/relayerset/client.go +++ b/pkg/loop/internal/relayerset/client.go @@ -10,6 +10,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/chains/aptos" "github.com/smartcontractkit/chainlink-common/pkg/chains/evm" "github.com/smartcontractkit/chainlink-common/pkg/chains/solana" + "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" "github.com/smartcontractkit/chainlink-common/pkg/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/goplugin" @@ -28,26 +29,28 @@ type Client struct { log logger.Logger - relayerSetClient relayerset.RelayerSetClient - contractReaderClient pb.ContractReaderClient - evmRelayerSetClient evm.EVMClient - tonRelayerSetClient ton.TONClient - solanaRelayerSetClient solana.SolanaClient - aptosRelayerSetClient aptos.AptosClient + relayerSetClient relayerset.RelayerSetClient + contractReaderClient pb.ContractReaderClient + evmRelayerSetClient evm.EVMClient + tonRelayerSetClient ton.TONClient + solanaRelayerSetClient solana.SolanaClient + aptosRelayerSetClient aptos.AptosClient + stellarRelayerSetClient stellar.StellarClient } func NewRelayerSetClient(log logger.Logger, b *net.BrokerExt, conn grpc.ClientConnInterface) *Client { b = b.WithName("ChainRelayerClient") return &Client{ - log: log, - BrokerExt: b, - ServiceClient: goplugin.NewServiceClient(b, conn), - relayerSetClient: relayerset.NewRelayerSetClient(conn), - evmRelayerSetClient: evm.NewEVMClient(conn), - tonRelayerSetClient: ton.NewTONClient(conn), - solanaRelayerSetClient: solana.NewSolanaClient(conn), - aptosRelayerSetClient: aptos.NewAptosClient(conn), - contractReaderClient: pb.NewContractReaderClient(conn)} + log: log, + BrokerExt: b, + ServiceClient: goplugin.NewServiceClient(b, conn), + contractReaderClient: pb.NewContractReaderClient(conn), + relayerSetClient: relayerset.NewRelayerSetClient(conn), + evmRelayerSetClient: evm.NewEVMClient(conn), + tonRelayerSetClient: ton.NewTONClient(conn), + solanaRelayerSetClient: solana.NewSolanaClient(conn), + aptosRelayerSetClient: aptos.NewAptosClient(conn), + stellarRelayerSetClient: stellar.NewStellarClient(conn)} } func (k *Client) Get(ctx context.Context, relayID types.RelayID) (core.Relayer, error) { @@ -211,6 +214,16 @@ func (k *Client) Aptos(relayID types.RelayID) (types.AptosService, error) { }), nil } +func (k *Client) Stellar(relayID types.RelayID) (types.StellarService, error) { + if k.stellarRelayerSetClient == nil { + return nil, errors.New("stellarRelayerSetClient can't be nil") + } + return rel.NewStellarClient(&stellarClient{ + relayID: relayID, + client: k.stellarRelayerSetClient, + }), nil +} + func (k *Client) NewPluginProvider(ctx context.Context, relayID types.RelayID, relayArgs core.RelayArgs, pluginArgs core.PluginArgs) (uint32, error) { // TODO at a later phase these credentials should be set as part of the relay config and not as a separate field var mercuryCredentials *relayerset.MercuryCredentials diff --git a/pkg/loop/internal/relayerset/relayer.go b/pkg/loop/internal/relayerset/relayer.go index d80de1f6f..9eb31f47f 100644 --- a/pkg/loop/internal/relayerset/relayer.go +++ b/pkg/loop/internal/relayerset/relayer.go @@ -51,6 +51,10 @@ func (r *relayer) Aptos() (types.AptosService, error) { return r.relayerSetClient.Aptos(r.relayerID) } +func (r *relayer) Stellar() (types.StellarService, error) { + return r.relayerSetClient.Stellar(r.relayerID) +} + func (r *relayer) NewContractReader(ctx context.Context, contractReaderConfig []byte) (types.ContractReader, error) { return r.relayerSetClient.NewContractReader(ctx, r.relayerID, contractReaderConfig) } diff --git a/pkg/loop/internal/relayerset/relayerset_test.go b/pkg/loop/internal/relayerset/relayerset_test.go index 49c5ce314..7afd94d87 100644 --- a/pkg/loop/internal/relayerset/relayerset_test.go +++ b/pkg/loop/internal/relayerset/relayerset_test.go @@ -21,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/chains/aptos" evmtypes "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" soltypes "github.com/smartcontractkit/chainlink-common/pkg/types/chains/solana" + stellartypes "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" "github.com/smartcontractkit/chainlink-common/pkg/types/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/types/core" "github.com/smartcontractkit/chainlink-common/pkg/types/core/mocks" @@ -1527,6 +1528,132 @@ func (r *testRelaySetPlugin) GRPCServer(broker *plugin.GRPCBroker, server *grpc. return nil } +func Test_RelayerSet_StellarService(t *testing.T) { + ctx := t.Context() + stopCh := make(chan struct{}) + log := logger.Test(t) + + relayer1 := mocks.NewRelayer(t) + relayers := map[types.RelayID]core.Relayer{ + {Network: "N1", ChainID: "C1"}: relayer1, + } + + pluginName := "stellar-relayerset-test" + client, server := plugin.TestPluginGRPCConn( + t, + true, + map[string]plugin.Plugin{ + pluginName: &testRelaySetPlugin{ + log: log, + impl: &TestRelayerSet{relayers: relayers}, + brokerExt: &net.BrokerExt{ + BrokerConfig: net.BrokerConfig{ + StopCh: stopCh, + Logger: log, + }, + }, + }, + }, + ) + defer client.Close() + defer server.Stop() + + relayerSetClient, err := client.Dispense(pluginName) + require.NoError(t, err) + rc, ok := relayerSetClient.(*Client) + require.True(t, ok) + + retrievedRelayer, err := rc.Get(ctx, types.RelayID{Network: "N1", ChainID: "C1"}) + require.NoError(t, err) + + tests := []struct { + name string + run func(t *testing.T, svc types.StellarService, mockSvc *mocks2.StellarService) + }{ + { + name: "GetLedgerEntries_WithLiveUntil", + run: func(t *testing.T, svc types.StellarService, mockSvc *mocks2.StellarService) { + liveUntil := uint32(9999) + mockSvc.EXPECT().GetLedgerEntries(mock.Anything, stellartypes.GetLedgerEntriesRequest{ + Keys: []stellartypes.XDR{"a2V5MQ=="}, // base64("key1") + }).Return(stellartypes.GetLedgerEntriesResponse{ + LatestLedger: 80, + Entries: []stellartypes.LedgerEntryResult{ + { + KeyXDR: "a2V5MQ==", // base64("key1") + DataXDR: "ZGF0YTE=", // base64("data1") + LastModifiedLedger: 70, + LiveUntilLedgerSeq: &liveUntil, + }, + }, + }, nil) + + resp, err := svc.GetLedgerEntries(ctx, stellartypes.GetLedgerEntriesRequest{Keys: []stellartypes.XDR{"a2V5MQ=="}}) + require.NoError(t, err) + require.Equal(t, uint32(80), resp.LatestLedger) + require.Len(t, resp.Entries, 1) + require.NotNil(t, resp.Entries[0].LiveUntilLedgerSeq) + require.Equal(t, liveUntil, *resp.Entries[0].LiveUntilLedgerSeq) + require.Equal(t, stellartypes.XDR("a2V5MQ=="), resp.Entries[0].KeyXDR) + }, + }, + { + name: "GetLedgerEntries_NoLiveUntil", + run: func(t *testing.T, svc types.StellarService, mockSvc *mocks2.StellarService) { + mockSvc.EXPECT().GetLedgerEntries(mock.Anything, stellartypes.GetLedgerEntriesRequest{ + Keys: []stellartypes.XDR{"a2V5Mg=="}, // base64("key2") + }).Return(stellartypes.GetLedgerEntriesResponse{ + LatestLedger: 90, + Entries: []stellartypes.LedgerEntryResult{ + { + KeyXDR: "a2V5Mg==", // base64("key2") + DataXDR: "data2XDR", // valid 8-char base64 + LastModifiedLedger: 85, + LiveUntilLedgerSeq: nil, + }, + }, + }, nil) + + resp, err := svc.GetLedgerEntries(ctx, stellartypes.GetLedgerEntriesRequest{Keys: []stellartypes.XDR{"a2V5Mg=="}}) + require.NoError(t, err) + require.Equal(t, uint32(90), resp.LatestLedger) + require.Len(t, resp.Entries, 1) + require.Nil(t, resp.Entries[0].LiveUntilLedgerSeq) + }, + }, + { + name: "GetLatestLedger", + run: func(t *testing.T, svc types.StellarService, mockSvc *mocks2.StellarService) { + mockSvc.EXPECT().GetLatestLedger(mock.Anything).Return(stellartypes.GetLatestLedgerResponse{ + Hash: "deadbeef", // valid 4-byte hex + ProtocolVersion: 22, + Sequence: 4321, + LedgerCloseTime: 9000000, + }, nil) + + resp, err := svc.GetLatestLedger(ctx) + require.NoError(t, err) + require.Equal(t, stellartypes.LedgerHash("deadbeef"), resp.Hash) + require.Equal(t, uint32(22), resp.ProtocolVersion) + require.Equal(t, uint32(4321), resp.Sequence) + require.Equal(t, int64(9000000), resp.LedgerCloseTime) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockSvc := mocks2.NewStellarService(t) + relayer1.On("Stellar", mock.Anything, mock.Anything).Return(mockSvc, nil).Once() + + fetchedSvc, err := retrievedRelayer.Stellar() + require.NoError(t, err) + + tc.run(t, fetchedSvc, mockSvc) + }) + } +} + func generateFixtureQuery() []query.Expression { exprs := make([]query.Expression, 0) diff --git a/pkg/loop/internal/relayerset/server.go b/pkg/loop/internal/relayerset/server.go index 216467f82..9f960c9a2 100644 --- a/pkg/loop/internal/relayerset/server.go +++ b/pkg/loop/internal/relayerset/server.go @@ -16,6 +16,7 @@ import ( aptospb "github.com/smartcontractkit/chainlink-common/pkg/chains/aptos" evmpb "github.com/smartcontractkit/chainlink-common/pkg/chains/evm" solpb "github.com/smartcontractkit/chainlink-common/pkg/chains/solana" + stelpb "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" tonpb "github.com/smartcontractkit/chainlink-common/pkg/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/loop/internal/net" @@ -44,6 +45,7 @@ type Server struct { ton *tonServer evm *evmServer aptos *aptosServer + stellar *stellarServer contractReader *readerServer serverResources net.Resources @@ -65,6 +67,7 @@ func NewRelayerSetServer(log logger.Logger, underlying core.RelayerSet, broker * server.ton = &tonServer{parent: server} server.evm = &evmServer{parent: server} server.aptos = &aptosServer{parent: server} + server.stellar = &stellarServer{parent: server} server.contractReader = &readerServer{parent: server} return server, net.Resource{ @@ -77,6 +80,7 @@ func (s *Server) SolanaServer() solpb.SolanaServer { return s.sol } func (s *Server) TONServer() tonpb.TONServer { return s.ton } func (s *Server) EVMServer() evmpb.EVMServer { return s.evm } func (s *Server) AptosServer() aptospb.AptosServer { return s.aptos } +func (s *Server) StellarServer() stelpb.StellarServer { return s.stellar } func (s *Server) ContractReaderServer() pb.ContractReaderServer { return s.contractReader } func (s *Server) Close() error { diff --git a/pkg/loop/internal/relayerset/stellar.go b/pkg/loop/internal/relayerset/stellar.go new file mode 100644 index 000000000..7f9079f08 --- /dev/null +++ b/pkg/loop/internal/relayerset/stellar.go @@ -0,0 +1,89 @@ +package relayerset + +import ( + "context" + "fmt" + + stelpb "github.com/smartcontractkit/chainlink-common/pkg/chains/stellar" + "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" + + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" +) + +// stellarClient wraps a StellarClient by attaching a RelayerID to every request via context metadata. +type stellarClient struct { + relayID types.RelayID + client stelpb.StellarClient +} + +var _ stelpb.StellarClient = (*stellarClient)(nil) + +func (sc *stellarClient) GetLedgerEntries(ctx context.Context, in *stelpb.GetLedgerEntriesRequest, opts ...grpc.CallOption) (*stelpb.GetLedgerEntriesResponse, error) { + return sc.client.GetLedgerEntries(appendRelayID(ctx, sc.relayID), in, opts...) +} + +func (sc *stellarClient) GetLatestLedger(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*stelpb.GetLatestLedgerResponse, error) { + return sc.client.GetLatestLedger(appendRelayID(ctx, sc.relayID), in, opts...) +} + +// stellarServer implements stelpb.StellarServer by routing each RPC through the RelayerSet. +type stellarServer struct { + stelpb.UnimplementedStellarServer + parent *Server +} + +var _ stelpb.StellarServer = (*stellarServer)(nil) + +func (ss *stellarServer) GetLedgerEntries(ctx context.Context, req *stelpb.GetLedgerEntriesRequest) (*stelpb.GetLedgerEntriesResponse, error) { + svc, err := ss.parent.getStellarService(ctx) + if err != nil { + return nil, err + } + dReq, err := stelpb.ConvertGetLedgerEntriesRequestFromProto(req) + if err != nil { + return nil, fmt.Errorf("invalid GetLedgerEntries request: %w", err) + } + dResp, err := svc.GetLedgerEntries(ctx, dReq) + if err != nil { + return nil, net.WrapRPCErr(err) + } + pResp, err := stelpb.ConvertGetLedgerEntriesResponseToProto(dResp) + if err != nil { + return nil, fmt.Errorf("invalid GetLedgerEntries response: %w", err) + } + return pResp, nil +} + +func (ss *stellarServer) GetLatestLedger(ctx context.Context, _ *emptypb.Empty) (*stelpb.GetLatestLedgerResponse, error) { + svc, err := ss.parent.getStellarService(ctx) + if err != nil { + return nil, err + } + dResp, err := svc.GetLatestLedger(ctx) + if err != nil { + return nil, net.WrapRPCErr(err) + } + pResp, err := stelpb.ConvertGetLatestLedgerResponseToProto(dResp) + if err != nil { + return nil, fmt.Errorf("invalid GetLatestLedger response: %w", err) + } + return pResp, nil +} + +// getStellarService extracts the RelayID from context metadata and returns the StellarService +// for the corresponding relayer. +func (s *Server) getStellarService(ctx context.Context) (types.StellarService, error) { + id, err := readRelayID(ctx) + if err != nil { + return nil, err + } + idT := relayerset.RelayerId{Network: id.Network, ChainId: id.ChainID} + r, err := s.getRelayer(ctx, &idT) + if err != nil { + return nil, err + } + return r.Stellar() +} diff --git a/pkg/types/chains/stellar/stellar.go b/pkg/types/chains/stellar/stellar.go new file mode 100644 index 000000000..79c25008a --- /dev/null +++ b/pkg/types/chains/stellar/stellar.go @@ -0,0 +1,62 @@ +package stellar + +import "context" + +// XDR is a base64-encoded XDR-serialized value (envelope, data, result, key, etc.). +type XDR string + +// LedgerHash is the hex-encoded SHA-256 hash of a Stellar ledger. +type LedgerHash string + +// Client wraps Stellar RPC calls via the type/chains/stellar domain types. +// Both methods map 1:1 to the Stellar RPC API. +type Client interface { + // GetLedgerEntries fetches ledger entries by XDR key (used for sequence number lookups). + GetLedgerEntries(ctx context.Context, req GetLedgerEntriesRequest) (GetLedgerEntriesResponse, error) + // GetLatestLedger returns current ledger info (used for timeout detection). + GetLatestLedger(ctx context.Context) (GetLatestLedgerResponse, error) +} + +// GetLedgerEntriesRequest fetches ledger entries by XDR-encoded keys. +type GetLedgerEntriesRequest struct { + // Keys is a slice of base64-encoded XDR ledger keys. + Keys []XDR +} + +// LedgerEntryResult is a single ledger entry returned from GetLedgerEntries. +type LedgerEntryResult struct { + // KeyXDR is the base64-encoded XDR ledger key matching the request. + KeyXDR XDR + // DataXDR is the base64-encoded XDR ledger entry data. + DataXDR XDR + // LastModifiedLedger is the ledger sequence of the last modification. + LastModifiedLedger uint32 + // LiveUntilLedgerSeq is the ledger until which the entry is live; nil if not applicable. + LiveUntilLedgerSeq *uint32 + // ExtensionXDR is the base64-encoded XDR ledger entry extension; empty if absent. + ExtensionXDR XDR +} + +// GetLedgerEntriesResponse contains the requested ledger entries. +type GetLedgerEntriesResponse struct { + // Entries holds all found ledger entries (may be fewer than keys requested). + Entries []LedgerEntryResult + // LatestLedger is the latest ledger sequence number at query time. + LatestLedger uint32 +} + +// GetLatestLedgerResponse holds the current ledger state. +type GetLatestLedgerResponse struct { + // Hash is the hex-encoded latest ledger hash. + Hash LedgerHash + // ProtocolVersion is the Stellar Core protocol version associated with the ledger. + ProtocolVersion uint32 + // Sequence is the latest ledger sequence number. + Sequence uint32 + // LedgerCloseTime is the unix timestamp when the latest ledger closed. + LedgerCloseTime int64 + // LedgerHeaderXDR is the base64-encoded LedgerHeader XDR for the latest ledger. + LedgerHeaderXDR XDR + // LedgerMetadataXDR is the base64-encoded LedgerCloseMetaV2 XDR for the latest ledger. + LedgerMetadataXDR XDR +} diff --git a/pkg/types/core/mocks/relayer.go b/pkg/types/core/mocks/relayer.go index c5d3b4e9b..dfa4cf92c 100644 --- a/pkg/types/core/mocks/relayer.go +++ b/pkg/types/core/mocks/relayer.go @@ -723,6 +723,63 @@ func (_c *Relayer_Solana_Call) RunAndReturn(run func() (types.SolanaService, err return _c } +// Stellar provides a mock function with no fields +func (_m *Relayer) Stellar() (types.StellarService, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Stellar") + } + + var r0 types.StellarService + var r1 error + if rf, ok := ret.Get(0).(func() (types.StellarService, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() types.StellarService); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.StellarService) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Relayer_Stellar_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stellar' +type Relayer_Stellar_Call struct { + *mock.Call +} + +// Stellar is a helper method to define mock.On call +func (_e *Relayer_Expecter) Stellar() *Relayer_Stellar_Call { + return &Relayer_Stellar_Call{Call: _e.mock.On("Stellar")} +} + +func (_c *Relayer_Stellar_Call) Run(run func()) *Relayer_Stellar_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Relayer_Stellar_Call) Return(_a0 types.StellarService, _a1 error) *Relayer_Stellar_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Relayer_Stellar_Call) RunAndReturn(run func() (types.StellarService, error)) *Relayer_Stellar_Call { + _c.Call.Return(run) + return _c +} + // Start provides a mock function with given fields: _a0 func (_m *Relayer) Start(_a0 context.Context) error { ret := _m.Called(_a0) diff --git a/pkg/types/core/relayerset.go b/pkg/types/core/relayerset.go index b0fa9c49b..740faef2a 100644 --- a/pkg/types/core/relayerset.go +++ b/pkg/types/core/relayerset.go @@ -37,6 +37,8 @@ type Relayer interface { Solana() (types.SolanaService, error) // Aptos returns AptosService that provides access to Aptos specific functionalities Aptos() (types.AptosService, error) + // Stellar returns StellarService that provides access to Stellar specific functionalities + Stellar() (types.StellarService, error) NewPluginProvider(context.Context, RelayArgs, PluginArgs) (PluginProvider, error) NewContractReader(_ context.Context, contractReaderConfig []byte) (types.ContractReader, error) NewContractWriter(_ context.Context, contractWriterConfig []byte) (types.ContractWriter, error) diff --git a/pkg/types/mocks/stellar_service.go b/pkg/types/mocks/stellar_service.go new file mode 100644 index 000000000..cf39019b3 --- /dev/null +++ b/pkg/types/mocks/stellar_service.go @@ -0,0 +1,150 @@ +// Code generated by mockery v2.53.3. DO NOT EDIT. + +package mocks + +import ( + context "context" + + stellar "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" + mock "github.com/stretchr/testify/mock" +) + +// StellarService is an autogenerated mock type for the StellarService type +type StellarService struct { + mock.Mock +} + +type StellarService_Expecter struct { + mock *mock.Mock +} + +func (_m *StellarService) EXPECT() *StellarService_Expecter { + return &StellarService_Expecter{mock: &_m.Mock} +} + +// GetLatestLedger provides a mock function with given fields: ctx +func (_m *StellarService) GetLatestLedger(ctx context.Context) (stellar.GetLatestLedgerResponse, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLatestLedger") + } + + var r0 stellar.GetLatestLedgerResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (stellar.GetLatestLedgerResponse, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) stellar.GetLatestLedgerResponse); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(stellar.GetLatestLedgerResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StellarService_GetLatestLedger_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestLedger' +type StellarService_GetLatestLedger_Call struct { + *mock.Call +} + +// GetLatestLedger is a helper method to define mock.On call +// - ctx context.Context +func (_e *StellarService_Expecter) GetLatestLedger(ctx interface{}) *StellarService_GetLatestLedger_Call { + return &StellarService_GetLatestLedger_Call{Call: _e.mock.On("GetLatestLedger", ctx)} +} + +func (_c *StellarService_GetLatestLedger_Call) Run(run func(ctx context.Context)) *StellarService_GetLatestLedger_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *StellarService_GetLatestLedger_Call) Return(_a0 stellar.GetLatestLedgerResponse, _a1 error) *StellarService_GetLatestLedger_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StellarService_GetLatestLedger_Call) RunAndReturn(run func(context.Context) (stellar.GetLatestLedgerResponse, error)) *StellarService_GetLatestLedger_Call { + _c.Call.Return(run) + return _c +} + +// GetLedgerEntries provides a mock function with given fields: ctx, req +func (_m *StellarService) GetLedgerEntries(ctx context.Context, req stellar.GetLedgerEntriesRequest) (stellar.GetLedgerEntriesResponse, error) { + ret := _m.Called(ctx, req) + + if len(ret) == 0 { + panic("no return value specified for GetLedgerEntries") + } + + var r0 stellar.GetLedgerEntriesResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, stellar.GetLedgerEntriesRequest) (stellar.GetLedgerEntriesResponse, error)); ok { + return rf(ctx, req) + } + if rf, ok := ret.Get(0).(func(context.Context, stellar.GetLedgerEntriesRequest) stellar.GetLedgerEntriesResponse); ok { + r0 = rf(ctx, req) + } else { + r0 = ret.Get(0).(stellar.GetLedgerEntriesResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, stellar.GetLedgerEntriesRequest) error); ok { + r1 = rf(ctx, req) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// StellarService_GetLedgerEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLedgerEntries' +type StellarService_GetLedgerEntries_Call struct { + *mock.Call +} + +// GetLedgerEntries is a helper method to define mock.On call +// - ctx context.Context +// - req stellar.GetLedgerEntriesRequest +func (_e *StellarService_Expecter) GetLedgerEntries(ctx interface{}, req interface{}) *StellarService_GetLedgerEntries_Call { + return &StellarService_GetLedgerEntries_Call{Call: _e.mock.On("GetLedgerEntries", ctx, req)} +} + +func (_c *StellarService_GetLedgerEntries_Call) Run(run func(ctx context.Context, req stellar.GetLedgerEntriesRequest)) *StellarService_GetLedgerEntries_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(stellar.GetLedgerEntriesRequest)) + }) + return _c +} + +func (_c *StellarService_GetLedgerEntries_Call) Return(_a0 stellar.GetLedgerEntriesResponse, _a1 error) *StellarService_GetLedgerEntries_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *StellarService_GetLedgerEntries_Call) RunAndReturn(run func(context.Context, stellar.GetLedgerEntriesRequest) (stellar.GetLedgerEntriesResponse, error)) *StellarService_GetLedgerEntries_Call { + _c.Call.Return(run) + return _c +} + +// NewStellarService creates a new instance of StellarService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewStellarService(t interface { + mock.TestingT + Cleanup(func()) +}) *StellarService { + mock := &StellarService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/types/relayer.go b/pkg/types/relayer.go index 6d0b071c7..8277a4744 100644 --- a/pkg/types/relayer.go +++ b/pkg/types/relayer.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/chains/aptos" "github.com/smartcontractkit/chainlink-common/pkg/types/chains/evm" "github.com/smartcontractkit/chainlink-common/pkg/types/chains/solana" + "github.com/smartcontractkit/chainlink-common/pkg/types/chains/stellar" "github.com/smartcontractkit/chainlink-common/pkg/types/chains/ton" "github.com/smartcontractkit/chainlink-common/pkg/types/query" "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" @@ -268,6 +269,12 @@ type AptosService interface { SubmitTransaction(ctx context.Context, req aptos.SubmitTransactionRequest) (*aptos.SubmitTransactionReply, error) } +// StellarService exposes the Stellar RPC operations needed by CRE: +// fetch ledger entries and get the latest ledger. +type StellarService interface { + stellar.Client +} + // Relayer extends ChainService with providers for each product. type Relayer interface { ChainService @@ -280,6 +287,8 @@ type Relayer interface { Solana() (SolanaService, error) // Aptos returns AptosService that provides access to Aptos specific functionalities Aptos() (AptosService, error) + // Stellar returns StellarService that provides access to Stellar specific functionalities + Stellar() (StellarService, error) // NewContractWriter returns a new ContractWriter. // The format of config depends on the implementation. NewContractWriter(ctx context.Context, config []byte) (ContractWriter, error) @@ -412,6 +421,10 @@ func (u *UnimplementedRelayer) Aptos() (AptosService, error) { return nil, status.Errorf(codes.Unimplemented, "method Aptos not implemented") } +func (u *UnimplementedRelayer) Stellar() (StellarService, error) { + return nil, status.Errorf(codes.Unimplemented, "method Stellar not implemented") +} + func (u *UnimplementedRelayer) NewContractWriter(ctx context.Context, config []byte) (ContractWriter, error) { return nil, status.Errorf(codes.Unimplemented, "method NewContractWriter not implemented") } @@ -628,3 +641,18 @@ func (ua *UnimplementedAptosService) AccountTransactions(ctx context.Context, re func (ua *UnimplementedAptosService) SubmitTransaction(ctx context.Context, req aptos.SubmitTransactionRequest) (*aptos.SubmitTransactionReply, error) { return nil, status.Errorf(codes.Unimplemented, "method SubmitTransaction not implemented") } + +var _ StellarService = &UnimplementedStellarService{} + +// UnimplementedStellarService implements the StellarService interface with stubbed methods that return codes.Unimplemented errors. +// Embed this in real StellarService implementations so that new methods added to the interface +// don't immediately break downstream packages on dependency bumps. +type UnimplementedStellarService struct{} + +func (u *UnimplementedStellarService) GetLedgerEntries(_ context.Context, _ stellar.GetLedgerEntriesRequest) (stellar.GetLedgerEntriesResponse, error) { + return stellar.GetLedgerEntriesResponse{}, status.Errorf(codes.Unimplemented, "method GetLedgerEntries not implemented") +} + +func (u *UnimplementedStellarService) GetLatestLedger(_ context.Context) (stellar.GetLatestLedgerResponse, error) { + return stellar.GetLatestLedgerResponse{}, status.Errorf(codes.Unimplemented, "method GetLatestLedger not implemented") +}