From 43015827799a49d3349733abda60bad2556a3079 Mon Sep 17 00:00:00 2001 From: ilija42 Date: Thu, 13 Aug 2026 13:50:57 +0200 Subject: [PATCH 1/3] Be more strict with stellar ledger decoding and bump stellar sdk --- chain_capabilities/stellar/actions/actions.go | 51 +++---- .../stellar/actions/actions_test.go | 124 ++++++++++++++---- chain_capabilities/stellar/go.mod | 4 +- chain_capabilities/stellar/go.sum | 4 + 4 files changed, 129 insertions(+), 54 deletions(-) diff --git a/chain_capabilities/stellar/actions/actions.go b/chain_capabilities/stellar/actions/actions.go index 22338c40d..f99e063d1 100644 --- a/chain_capabilities/stellar/actions/actions.go +++ b/chain_capabilities/stellar/actions/actions.go @@ -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 } diff --git a/chain_capabilities/stellar/actions/actions_test.go b/chain_capabilities/stellar/actions/actions_test.go index b02d699ef..b8fa18d0b 100644 --- a/chain_capabilities/stellar/actions/actions_test.go +++ b/chain_capabilities/stellar/actions/actions_test.go @@ -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() @@ -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(). @@ -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() @@ -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) { diff --git a/chain_capabilities/stellar/go.mod b/chain_capabilities/stellar/go.mod index 6163ab99c..3b64b08ba 100644 --- a/chain_capabilities/stellar/go.mod +++ b/chain_capabilities/stellar/go.mod @@ -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 ( diff --git a/chain_capabilities/stellar/go.sum b/chain_capabilities/stellar/go.sum index cf9ff3ab2..df4274b55 100644 --- a/chain_capabilities/stellar/go.sum +++ b/chain_capabilities/stellar/go.sum @@ -244,8 +244,12 @@ github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 h1:pEcgcjT github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342/go.mod h1:5JPtsRwjugpyfsdEALC4RopfvohqK/G+3DHaR8uv+Bc= github.com/stellar/go-stellar-sdk v0.5.0 h1:xpOO+ZTyvGz54wTm7pwl2Gf1e6lZl0ExrJ/tKb+Roj4= github.com/stellar/go-stellar-sdk v0.5.0/go.mod h1:tLKAQPxa2I5UvGMabBbUXcY3fmgYnfDudrMeK7CDX4w= +github.com/stellar/go-stellar-sdk v0.7.1 h1:28z6JuMtXoE5W535ekcUOUPAgfrxof3yc6gM700W2I4= +github.com/stellar/go-stellar-sdk v0.7.1/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw= github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf h1:GY1RVbX3Hg7poPXEf6yojjP0hyypvgUgZmCqQU9D0xg= github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= +github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364 h1:gOKrfuWdZ92LFlv0TAwgZ7OsWKeBsOMDlGLyFgduI1w= +github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= From d9e596f0992028e58edaacd2189371b98fced6fd Mon Sep 17 00:00:00 2001 From: ilija42 Date: Thu, 13 Aug 2026 13:51:07 +0200 Subject: [PATCH 2/3] Add info log for tx fetching stellar --- .../stellar/actions/write_report.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/chain_capabilities/stellar/actions/write_report.go b/chain_capabilities/stellar/actions/write_report.go index 11c2986f0..8f8c87209 100644 --- a/chain_capabilities/stellar/actions/write_report.go +++ b/chain_capabilities/stellar/actions/write_report.go @@ -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 } From 082e4367ca5ea648ed6f6ce447c177c2a8227fff Mon Sep 17 00:00:00 2001 From: ilija42 Date: Thu, 13 Aug 2026 14:15:56 +0200 Subject: [PATCH 3/3] tidy --- chain_capabilities/stellar/go.sum | 4 ---- 1 file changed, 4 deletions(-) diff --git a/chain_capabilities/stellar/go.sum b/chain_capabilities/stellar/go.sum index df4274b55..b95c5a4c1 100644 --- a/chain_capabilities/stellar/go.sum +++ b/chain_capabilities/stellar/go.sum @@ -242,12 +242,8 @@ github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12i github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA= github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342 h1:pEcgcjTGA83MzpqbTbyIg9AJrOs62s77SooDdJGIg9w= github.com/smartcontractkit/libocr v0.0.0-20260810200708-618b5bf7f342/go.mod h1:5JPtsRwjugpyfsdEALC4RopfvohqK/G+3DHaR8uv+Bc= -github.com/stellar/go-stellar-sdk v0.5.0 h1:xpOO+ZTyvGz54wTm7pwl2Gf1e6lZl0ExrJ/tKb+Roj4= -github.com/stellar/go-stellar-sdk v0.5.0/go.mod h1:tLKAQPxa2I5UvGMabBbUXcY3fmgYnfDudrMeK7CDX4w= github.com/stellar/go-stellar-sdk v0.7.1 h1:28z6JuMtXoE5W535ekcUOUPAgfrxof3yc6gM700W2I4= github.com/stellar/go-stellar-sdk v0.7.1/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw= -github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf h1:GY1RVbX3Hg7poPXEf6yojjP0hyypvgUgZmCqQU9D0xg= -github.com/stellar/go-xdr v0.0.0-20260312225820-cc2b0611aabf/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364 h1:gOKrfuWdZ92LFlv0TAwgZ7OsWKeBsOMDlGLyFgduI1w= github.com/stellar/go-xdr v0.0.0-20260529210834-0bf8f4956364/go.mod h1:If+U9Z1W5xU97VrOgJandQT+2dN7/iOpkCrxBJEyF80= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=