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
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package config

import "testing"

// Probe: how does the strict decoder treat a BARE INTEGER earliest_ledger
// (the doc types the key "uint32 | genesis | now")?
func TestProbe_BareIntegerEarliestLedger(t *testing.T) {
_, err := ParseConfig([]byte("[retention]\nearliest_ledger = 20002\n"))
t.Logf("bare integer: err=%v", err)

cfg, err2 := ParseConfig([]byte("[retention]\nearliest_ledger = \"20002\"\n"))
t.Logf("quoted string: val=%q err=%v", cfg.Retention.EarliestLedger, err2)
}
105 changes: 100 additions & 5 deletions cmd/stellar-rpc/internal/fullhistory/ingest/ingest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -936,21 +936,28 @@ func TestWriteColdChunk_ByteIdentity_SharedWalk(t *testing.T) {
coldDirsAt(coldDir, chunkID), nil, Config{Ledgers: true, Txhash: true, Events: true},
))

// Reference #1 — txhash: sorted, truncated ExtractTxHashes over the sentinels.
// Reference #1 — txhash: sorted, truncated entries for every resolvable
// hash (outer, plus a fee-bump's inner) over the sentinels.
var wantEntries []txhash.ColdEntry
for _, seq := range sentinels {
hashes, err := sdkingest.ExtractTxHashes(xdr.LedgerCloseMetaView(raws[seq]))
txEvents, err := sdkingest.ExtractLedgerEvents(xdr.LedgerCloseMetaView(raws[seq]))
require.NoError(t, err)
for _, h := range hashes {
for i := range txEvents {
var ke txhash.ColdEntry
copy(ke.Key[:], h[:txhash.ColdKeySize])
copy(ke.Key[:], txEvents[i].Hash[:txhash.ColdKeySize])
ke.Seq = seq
wantEntries = append(wantEntries, ke)
if txEvents[i].FeeBump {
var ike txhash.ColdEntry
copy(ike.Key[:], txEvents[i].InnerHash[:txhash.ColdKeySize])
ike.Seq = seq
wantEntries = append(wantEntries, ike)
}
}
}
slices.SortFunc(wantEntries, func(a, b txhash.ColdEntry) int { return bytes.Compare(a.Key[:], b.Key[:]) })
gotEntries := fhtest.ReadColdBin(t, txhashBinPath(filepath.Join(coldDir, dataTypeTxhash)))
require.Equal(t, wantEntries, gotEntries, "cold .bin must match the old ExtractTxHashes path byte-for-byte")
require.Equal(t, wantEntries, gotEntries, "cold .bin must hold every resolvable hash, sorted and truncated")

// Reference #2 — events: PayloadsFromLedgerEvents with chunk-relative IDs
// assigned in ingest (ascending-seq) order, mapped to their term keys.
Expand Down Expand Up @@ -1624,3 +1631,91 @@ func TestWriteColdChunk_EmptyStream(t *testing.T) {

// The earlier-artifact-stays-on-disk invariant when a LATER writer's finalize
// fails is covered by TestColdChunk_Finalize_FirstErrorStopsRemaining above.

// buildLCMWithFeeBump builds a single-transaction V2 LCM whose transaction is a
// fee-bump with a real txFEE_BUMP_INNER_SUCCESS result, returning the outer and
// inner hashes.
func buildLCMWithFeeBump(t *testing.T, seq uint32) (xdr.LedgerCloseMeta, [32]byte, [32]byte) {
t.Helper()
inner := xdr.Transaction{
SourceAccount: xdr.MustMuxedAddress(keypair.MustRandom().Address()),
Ext: xdr.TransactionExt{V: 1, SorobanData: &xdr.SorobanTransactionData{}},
}
envelope := xdr.TransactionEnvelope{
Type: xdr.EnvelopeTypeEnvelopeTypeTxFeeBump,
FeeBump: &xdr.FeeBumpTransactionEnvelope{Tx: xdr.FeeBumpTransaction{
Fee: 999,
FeeSource: xdr.MustMuxedAddress(keypair.MustRandom().Address()),
InnerTx: xdr.FeeBumpTransactionInnerTx{
Type: xdr.EnvelopeTypeEnvelopeTypeTx,
V1: &xdr.TransactionV1Envelope{Tx: inner},
},
}},
}
outerHash, err := network.HashTransactionInEnvelope(envelope, testPassphrase)
require.NoError(t, err)
innerHash, err := network.HashTransactionInEnvelope(xdr.TransactionEnvelope{
Type: xdr.EnvelopeTypeEnvelopeTypeTx, V1: envelope.FeeBump.Tx.InnerTx.V1,
}, testPassphrase)
require.NoError(t, err)

ops := []xdr.OperationResult{}
result := xdr.TransactionResult{FeeCharged: 200, Result: xdr.TransactionResultResult{
Code: xdr.TransactionResultCodeTxFeeBumpInnerSuccess,
InnerResultPair: &xdr.InnerTransactionResultPair{
TransactionHash: innerHash,
Result: xdr.InnerTransactionResult{Result: xdr.InnerTransactionResultResult{
Code: xdr.TransactionResultCodeTxSuccess, Results: &ops,
}},
},
}}
comp := []xdr.TxSetComponent{{
Type: xdr.TxSetComponentTypeTxsetCompTxsMaybeDiscountedFee,
TxsMaybeDiscountedFee: &xdr.TxSetComponentTxsMaybeDiscountedFee{
Txs: []xdr.TransactionEnvelope{envelope},
},
}}
lcm := xdr.LedgerCloseMeta{V: 2, V2: &xdr.LedgerCloseMetaV2{
LedgerHeader: xdr.LedgerHeaderHistoryEntry{Header: xdr.LedgerHeader{
ScpValue: xdr.StellarValue{CloseTime: xdr.TimePoint(0)}, LedgerSeq: xdr.Uint32(seq),
}},
TxSet: xdr.GeneralizedTransactionSet{V: 1, V1TxSet: &xdr.TransactionSetV1{
Phases: []xdr.TransactionPhase{{V: 0, V0Components: &comp}},
}},
TxProcessing: []xdr.TransactionResultMetaV1{{
TxApplyProcessing: xdr.TransactionMeta{V: 3, V3: &xdr.TransactionMetaV3{}},
Result: xdr.TransactionResultPair{TransactionHash: outerHash, Result: result},
}},
}}
return lcm, outerHash, innerHash
}

// TestTxhashColdWriter_FeeBumpBothHashes: a fee-bump ledger contributes two
// .bin entries — the outer and the inner hash — both mapping to the same seq,
// so the cold index resolves the transaction by either.
func TestTxhashColdWriter_FeeBumpBothHashes(t *testing.T) {
chunkID := chunk.ID(0)
seq := chunkID.FirstLedger()
coldDir := t.TempDir()

ing, err := newTxhashCold(txhashBinPath(coldDir), chunkID, nil)
require.NoError(t, err)
defer func() { require.NoError(t, ing.close()) }()

lcm, outerHash, innerHash := buildLCMWithFeeBump(t, seq)
raw, err := lcm.MarshalBinary()
require.NoError(t, err)
txEvents, _ := extractFor(t, raw)
require.NoError(t, ing.write(seq, txEvents))
require.NoError(t, ing.finalize(context.Background()))

entries := fhtest.ReadColdBin(t, txhashBinPath(coldDir))
require.Len(t, entries, 2)
keys := make([][]byte, 0, len(entries))
for i := range entries {
require.Equal(t, seq, entries[i].Seq)
keys = append(keys, entries[i].Key[:])
}
assert.Contains(t, keys, outerHash[:txhash.ColdKeySize])
assert.Contains(t, keys, innerHash[:txhash.ColdKeySize])
}
18 changes: 12 additions & 6 deletions cmd/stellar-rpc/internal/fullhistory/ingest/txhash.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,31 @@ func newTxhashCold(binPath string, chunkID chunk.ID, sink MetricSink) (*txhashCo
}, nil
}

// write accumulates one ledger's tx hashes. They come from coldChunk's shared
// ExtractLedgerEvents walk: each element's Hash is the ledger's tx hash in
// apply order — byte-identical, and in the same order, as the ExtractTxHashes
// call this used to run (both read txProcessingHash off the same TxProcessing
// iteration). Each is truncated to ColdKeySize and appended STRAIGHT into the
// write accumulates one ledger's tx hashes — one entry per hash, two for a
// fee-bump (outer + inner). They come from coldChunk's shared
// ExtractLedgerEvents walk, in apply order. Each is truncated to ColdKeySize
// and appended STRAIGHT into the
// accumulator — no intermediate per-ledger entry slice; over a ~3M-tx chunk
// that intermediate would be hundreds of MB of transient garbage. The
// extraction itself is metered once, ledger-scoped, as the ColdExtract signal;
// this cheap truncate-append folds into the per-writer ColdIngest total (its
// per-chunk cost is the finalize sort + .bin write).
func (t *txhashCold) write(seq uint32, txEvents []sdkingest.LedgerTransactionEvents) error {
start := time.Now()
before := len(t.entries)
for i := range txEvents {
var ke txhash.ColdEntry
copy(ke.Key[:], txEvents[i].Hash[:txhash.ColdKeySize])
ke.Seq = seq
t.entries = append(t.entries, ke)
if txEvents[i].FeeBump {
var ike txhash.ColdEntry
copy(ike.Key[:], txEvents[i].InnerHash[:txhash.ColdKeySize])
ike.Seq = seq
t.entries = append(t.entries, ike)
}
}
t.metrics.observe(time.Since(start), len(txEvents), nil)
t.metrics.observe(time.Since(start), len(t.entries)-before, nil)
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,12 @@ func (d *DB) IngestLedger(seq uint32, lcm xdr.LedgerCloseMetaView) (LedgerReport
rep.Failed = PhaseExtract
return rep, fmt.Errorf("extract ledger events seq %d: %w", seq, err)
}
txEntries := make([]txhash.Entry, len(txEvents))
txEntries := make([]txhash.Entry, 0, len(txEvents))
for i := range txEvents {
txEntries[i] = txhash.Entry{Hash: txEvents[i].Hash, LedgerSeq: seq}
txEntries = append(txEntries, txhash.Entry{Hash: txEvents[i].Hash, LedgerSeq: seq})
if txEvents[i].FeeBump {
txEntries = append(txEntries, txhash.Entry{Hash: txEvents[i].InnerHash, LedgerSeq: seq})
}
}

closedAt, err := lcm.LedgerCloseTime()
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/stellar/go-stellar-sdk v0.6.1-0.20260618191317-308407eca8c6
github.com/stellar/go-stellar-sdk v0.6.1-0.20260716145807-2bfffb159f36
github.com/stellar/streamhash v0.0.0-20260713164615-c72a4e6f578d
github.com/stretchr/testify v1.11.1
)
Expand Down
10 changes: 2 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -439,16 +439,10 @@ github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stellar/go-stellar-sdk v0.6.1-0.20260618191317-308407eca8c6 h1:UiPdd65wjndoURfq4+55UH1VCgCyCCdX0bMqr3LIo5k=
github.com/stellar/go-stellar-sdk v0.6.1-0.20260618191317-308407eca8c6/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw=
github.com/stellar/go-stellar-sdk v0.6.1-0.20260716145807-2bfffb159f36 h1:D+df6/VaD+vPAHdgR94H3vFcxaEPjKGUSfjtI7Lh9dQ=
github.com/stellar/go-stellar-sdk v0.6.1-0.20260716145807-2bfffb159f36/go.mod h1:IkcqcrE9UQi7n/1y+MxKB+7qzdjG1T2kGOD7Ss8dqjw=
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/stellar/streamhash v0.0.0-20260622155330-9e08e05c5fa6 h1:57+plrmWTOlCKeTRiYJnfJqU47F0qboBAMFsL9eRjoo=
github.com/stellar/streamhash v0.0.0-20260622155330-9e08e05c5fa6/go.mod h1:fxKHewECHOFV08vy4rR/J40H7KVZcnNS96q6OdjR+TQ=
github.com/stellar/streamhash v0.0.0-20260713121614-da9a38b1018e h1:zJrOvEk7g1rPOlU3o1tkEoucLIa0qCOI45j50FzIJEc=
github.com/stellar/streamhash v0.0.0-20260713121614-da9a38b1018e/go.mod h1:fxKHewECHOFV08vy4rR/J40H7KVZcnNS96q6OdjR+TQ=
github.com/stellar/streamhash v0.0.0-20260713140807-9f6eeb7acecf h1:0eG06xnYp14POIYGyFQiDHXZzlUdLLpoqhPTQgjAi1g=
github.com/stellar/streamhash v0.0.0-20260713140807-9f6eeb7acecf/go.mod h1:fxKHewECHOFV08vy4rR/J40H7KVZcnNS96q6OdjR+TQ=
github.com/stellar/streamhash v0.0.0-20260713164615-c72a4e6f578d h1:KOjgc+0Q2hsqnOa7Zn6sPiGMNDB/wyVC5WpriJV54s4=
github.com/stellar/streamhash v0.0.0-20260713164615-c72a4e6f578d/go.mod h1:fxKHewECHOFV08vy4rR/J40H7KVZcnNS96q6OdjR+TQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
Loading