Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 27 additions & 24 deletions chain_capabilities/stellar/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,33 +238,36 @@ func (s *Stellar) fetchLatestLedgerMetadata(ctx context.Context, latestLedger ui
}
out.Hash = hash

// extract encoded data for cleaner sdk response (ok when fetching one block)
if ledger.LedgerHeaderXDR != "" {
var hist xdr.LedgerHeaderHistoryEntry
if err = xdr.SafeUnmarshalBase64(ledger.LedgerHeaderXDR, &hist); err != nil {
return nil, fmt.Errorf("failed to decode ledger header xdr: %w", err)
}
headerBin, err := hist.Header.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal ledger header: %w", err)
}
out.LedgerHeaderXdr = headerBin
out.ProtocolVersion = uint32(hist.Header.LedgerVersion)
if ledger.LedgerHeaderXDR == "" {
return nil, fmt.Errorf("ledger header xdr is required for sequence %d", latestLedger)
}
var hist xdr.LedgerHeaderHistoryEntry
if err = xdr.SafeUnmarshalBase64(ledger.LedgerHeaderXDR, &hist); err != nil {
return nil, fmt.Errorf("failed to decode ledger header xdr: %w", err)
}
headerBin, err := hist.Header.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal ledger header: %w", err)
}
out.LedgerHeaderXdr = headerBin
out.ProtocolVersion = uint32(hist.Header.LedgerVersion)

if ledger.LedgerMetadataXDR != "" {
var meta xdr.LedgerCloseMeta
if err = xdr.SafeUnmarshalBase64(ledger.LedgerMetadataXDR, &meta); err != nil {
return nil, fmt.Errorf("failed to decode ledger metadata xdr: %w", err)
}
if v2, ok := meta.GetV2(); ok {
metaBin, err := v2.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal ledger close meta v2: %w", err)
}
out.LedgerMetadataXdr = metaBin
}
if ledger.LedgerMetadataXDR == "" {
return nil, fmt.Errorf("ledger metadata xdr is required for sequence %d", latestLedger)
}
var meta xdr.LedgerCloseMeta
if err = xdr.SafeUnmarshalBase64(ledger.LedgerMetadataXDR, &meta); err != nil {
return nil, fmt.Errorf("failed to decode ledger metadata xdr: %w", err)
}
v2, ok := meta.GetV2()
if !ok {
return nil, fmt.Errorf("ledger metadata xdr version %d is unsupported, expected V2", meta.V)
}
metaBin, err := v2.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal ledger close meta v2: %w", err)
}
out.LedgerMetadataXdr = metaBin

return out, nil
}
Expand Down
124 changes: 96 additions & 28 deletions chain_capabilities/stellar/actions/actions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,36 @@ func validReadContractRequest() *stellarcap.ReadContractRequest {
}
}

func validLatestLedgerInfo(t *testing.T, sequence uint32) (stellartypes.LedgerInfo, []byte, []byte) {
t.Helper()

hist := xdr.LedgerHeaderHistoryEntry{
Header: xdr.LedgerHeader{LedgerVersion: 22, LedgerSeq: xdr.Uint32(sequence)},
}
headerB64, err := xdr.MarshalBase64(hist)
require.NoError(t, err)
headerBin, err := hist.Header.MarshalBinary()
require.NoError(t, err)

txSet, err := xdr.NewGeneralizedTransactionSet(1, xdr.TransactionSetV1{})
require.NoError(t, err)
v2 := xdr.LedgerCloseMetaV2{TxSet: txSet}
closeMeta, err := xdr.NewLedgerCloseMeta(2, v2)
require.NoError(t, err)
metaB64, err := xdr.MarshalBase64(closeMeta)
require.NoError(t, err)
metaBin, err := v2.MarshalBinary()
require.NoError(t, err)

return stellartypes.LedgerInfo{
Sequence: sequence,
Hash: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
LedgerCloseTime: 456,
LedgerHeaderXDR: headerB64,
LedgerMetadataXDR: metaB64,
}, headerBin, metaBin
}

func TestNewStellar(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -122,28 +152,9 @@ func TestGetLatestLedger(t *testing.T) {
helper := newMockedStellar(t)
helper.stellar.handler = testConsensusHandler{handle: runLockableToBlockHandle(&ctypes.ChainHeight{Latest: 123})}

// The RPC returns a LedgerHeaderHistoryEntry and a LedgerCloseMeta union; the
// capability response carries the inner LedgerHeader and the V2 close-meta arm.
hist := xdr.LedgerHeaderHistoryEntry{
Header: xdr.LedgerHeader{LedgerVersion: 22, LedgerSeq: 123},
}
headerB64, err := xdr.MarshalBase64(hist)
require.NoError(t, err)
wantHeaderBin, err := hist.Header.MarshalBinary()
require.NoError(t, err)

txSet, err := xdr.NewGeneralizedTransactionSet(1, xdr.TransactionSetV1{})
require.NoError(t, err)
v2 := xdr.LedgerCloseMetaV2{TxSet: txSet}
closeMeta, err := xdr.NewLedgerCloseMeta(2, v2)
require.NoError(t, err)
metaB64, err := xdr.MarshalBase64(closeMeta)
require.NoError(t, err)
wantMetaBin, err := v2.MarshalBinary()
require.NoError(t, err)
ledgerInfo, wantHeaderBin, wantMetaBin := validLatestLedgerInfo(t, 123)

const hashHex = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
wantHash, err := hex.DecodeString(hashHex)
wantHash, err := hex.DecodeString(ledgerInfo.Hash)
require.NoError(t, err)

helper.stellarService.EXPECT().
Expand All @@ -152,13 +163,7 @@ func TestGetLatestLedger(t *testing.T) {
Pagination: &stellartypes.LedgerPaginationOptions{Limit: 1},
}).
Return(stellartypes.GetLedgersResponse{
Ledgers: []stellartypes.LedgerInfo{{
Sequence: 123,
Hash: hashHex,
LedgerCloseTime: 456,
LedgerHeaderXDR: headerB64,
LedgerMetadataXDR: metaB64,
}},
Ledgers: []stellartypes.LedgerInfo{ledgerInfo},
}, nil).
Once()

Expand All @@ -181,6 +186,69 @@ func TestGetLatestLedger(t *testing.T) {
_, err := helper.stellar.GetLatestLedger(t.Context(), capabilities.RequestMetadata{}, &stellarcap.GetLatestLedgerRequest{})
require.Error(t, err)
})

t.Run("missing header xdr fails", func(t *testing.T) {
t.Parallel()
helper := newMockedStellar(t)
helper.stellar.handler = testConsensusHandler{handle: runLockableToBlockHandle(&ctypes.ChainHeight{Latest: 123})}
ledgerInfo, _, _ := validLatestLedgerInfo(t, 123)
ledgerInfo.LedgerHeaderXDR = ""

helper.stellarService.EXPECT().
GetLedgers(mock.Anything, stellartypes.GetLedgersRequest{
StartLedger: 123,
Pagination: &stellartypes.LedgerPaginationOptions{Limit: 1},
}).
Return(stellartypes.GetLedgersResponse{Ledgers: []stellartypes.LedgerInfo{ledgerInfo}}, nil).
Once()

_, err := helper.stellar.GetLatestLedger(t.Context(), capabilities.RequestMetadata{}, &stellarcap.GetLatestLedgerRequest{})
require.Error(t, err)
require.Contains(t, err.Error(), "ledger header xdr is required")
})

t.Run("missing metadata xdr fails", func(t *testing.T) {
t.Parallel()
helper := newMockedStellar(t)
helper.stellar.handler = testConsensusHandler{handle: runLockableToBlockHandle(&ctypes.ChainHeight{Latest: 123})}
ledgerInfo, _, _ := validLatestLedgerInfo(t, 123)
ledgerInfo.LedgerMetadataXDR = ""

helper.stellarService.EXPECT().
GetLedgers(mock.Anything, stellartypes.GetLedgersRequest{
StartLedger: 123,
Pagination: &stellartypes.LedgerPaginationOptions{Limit: 1},
}).
Return(stellartypes.GetLedgersResponse{Ledgers: []stellartypes.LedgerInfo{ledgerInfo}}, nil).
Once()

_, err := helper.stellar.GetLatestLedger(t.Context(), capabilities.RequestMetadata{}, &stellarcap.GetLatestLedgerRequest{})
require.Error(t, err)
require.Contains(t, err.Error(), "ledger metadata xdr is required")
})

t.Run("non-v2 metadata fails", func(t *testing.T) {
t.Parallel()
helper := newMockedStellar(t)
helper.stellar.handler = testConsensusHandler{handle: runLockableToBlockHandle(&ctypes.ChainHeight{Latest: 123})}
ledgerInfo, _, _ := validLatestLedgerInfo(t, 123)
closeMeta, err := xdr.NewLedgerCloseMeta(0, xdr.LedgerCloseMetaV0{})
require.NoError(t, err)
ledgerInfo.LedgerMetadataXDR, err = xdr.MarshalBase64(closeMeta)
require.NoError(t, err)

helper.stellarService.EXPECT().
GetLedgers(mock.Anything, stellartypes.GetLedgersRequest{
StartLedger: 123,
Pagination: &stellartypes.LedgerPaginationOptions{Limit: 1},
}).
Return(stellartypes.GetLedgersResponse{Ledgers: []stellartypes.LedgerInfo{ledgerInfo}}, nil).
Once()

_, err = helper.stellar.GetLatestLedger(t.Context(), capabilities.RequestMetadata{}, &stellarcap.GetLatestLedgerRequest{})
require.Error(t, err)
require.Contains(t, err.Error(), "ledger metadata xdr version 0 is unsupported")
})
}

func TestStellar_Info(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions chain_capabilities/stellar/actions/write_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,24 @@ func (wr *writeReport) replyFromTransaction(
if txResp.LedgerSequence > 0 {
reply.LedgerSequence = new(txResp.LedgerSequence)
}

logAttrs := []any{
"txHash", txHash,
"txStatus", txStatus,
"receiverStatus", receiverStatus,
"errorMessage", message,
}
if reply.TransactionFee != nil {
logAttrs = append(logAttrs, "transactionFeeStroops", *reply.TransactionFee)
}
if reply.LedgerSequence != nil {
logAttrs = append(logAttrs, "ledgerSequence", *reply.LedgerSequence)
}
if reply.BlockTimestamp != nil {
logAttrs = append(logAttrs, "blockTimestamp", *reply.BlockTimestamp)
}
wr.lggr.Infow("Successfully fetched transaction", logAttrs...)

return reply, nil
}

Expand Down
4 changes: 2 additions & 2 deletions chain_capabilities/stellar/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ go 1.26.4
require (
github.com/smartcontractkit/capabilities/libs v0.0.0-20260714133332-db2a5f11cd64
github.com/smartcontractkit/chainlink-common v0.11.2-0.20260717225314-5fc9656c1b02
github.com/stellar/go-stellar-sdk v0.5.0
github.com/stellar/go-stellar-sdk v0.7.1
)

require (
github.com/smartcontractkit/chainlink-framework/metrics v0.0.0-20260401162955-be2bc6b5264b // indirect
github.com/smartcontractkit/chainlink-protos/metering/go v0.0.0-20260710151514-27b5a126dabe // indirect
github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf // indirect
github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364 // indirect
)

require (
Expand Down
8 changes: 4 additions & 4 deletions chain_capabilities/stellar/go.sum

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

Loading