What version are you using?
go list -m github.com/stellar/go-stellar-sdk
Bug is present in current main branch at ingest/ledger_transaction.go:739.
What did you do?
Called LedgerTransaction.NewMaxFee() on a fee-bump transaction where the outer fee (FeeBumpTransaction.Fee) exceeded 4,294,967,295 stroops (~429.5 XLM):
tx, _ := reader.Read()
fee, ok := tx.NewMaxFee()
// fee is silently truncated for any fee-bump tx with fee > 429.5 XLM
Fee-bump transactions with fees exceeding this threshold are realistic for resource-intensive Soroban smart contract executions or during network congestion. The Stellar XDR spec deliberately defines FeeBumpTransaction.Fee as Int64 to accommodate values above the Uint32 range.
What did you expect to see?
NewMaxFee() should return the full correct int64 value of the fee-bump outer fee, consistent with:
- The XDR spec:
FeeBumpTransaction.Fee is Int64
- The underlying accessor:
Envelope.FeeBumpFee() already returns int64
For example, a fee of 5,000,000,000 stroops (500 XLM) should be returned as 5000000000.
What did you see instead?
NewMaxFee() silently truncates the int64 fee to uint32, discarding the upper 32 bits and returning a completely wrong value with no error.
Vulnerable code — ingest/ledger_transaction.go:739:
func (t *LedgerTransaction) NewMaxFee() (uint32, bool) {
if !t.Envelope.IsFeeBump() {
return 0, false
}
return uint32(t.Envelope.FeeBumpFee()), true // ← BUG: int64 truncated to uint32
}
Example of data corruption: A fee of 5,000,000,000 stroops becomes 705,032,704 (~70.5 XLM instead of ~500 XLM). The math: 5,000,000,000 mod 4,294,967,296 = 705,032,704.
Root cause: MaxFee() on a regular inner transaction correctly returns uint32 because the inner transaction fee uses XDR Uint32. NewMaxFee() was modelled after it without accounting for the fact that the fee-bump outer fee uses XDR Int64. The wrong type and cast were copied without adjustment.
Proposed fix:
func (t *LedgerTransaction) NewMaxFee() (int64, bool) {
if !t.Envelope.IsFeeBump() {
return 0, false
}
return t.Envelope.FeeBumpFee(), true
}
This is a breaking change to the public function signature. All callers must update their type handling from uint32 to int64.
Known affected downstream consumer: stellar/stellar-etl has the same truncation bug independently at internal/transform/transaction.go:294 and internal/transform/schema.go:63 — that fix should be coordinated with this one.
Impact: Any application using LedgerTransaction.NewMaxFee() from the ingest SDK silently receives an incorrect fee value for any fee-bump transaction with a fee exceeding ~429.5 XLM. This affects all downstream analytics, reconciliation, or compliance systems built on this function.
What version are you using?
Bug is present in current
mainbranch atingest/ledger_transaction.go:739.What did you do?
Called
LedgerTransaction.NewMaxFee()on a fee-bump transaction where the outer fee (FeeBumpTransaction.Fee) exceeded 4,294,967,295 stroops (~429.5 XLM):Fee-bump transactions with fees exceeding this threshold are realistic for resource-intensive Soroban smart contract executions or during network congestion. The Stellar XDR spec deliberately defines
FeeBumpTransaction.FeeasInt64to accommodate values above theUint32range.What did you expect to see?
NewMaxFee()should return the full correctint64value of the fee-bump outer fee, consistent with:FeeBumpTransaction.FeeisInt64Envelope.FeeBumpFee()already returnsint64For example, a fee of 5,000,000,000 stroops (500 XLM) should be returned as
5000000000.What did you see instead?
NewMaxFee()silently truncates theint64fee touint32, discarding the upper 32 bits and returning a completely wrong value with no error.Vulnerable code —
ingest/ledger_transaction.go:739:Example of data corruption: A fee of 5,000,000,000 stroops becomes 705,032,704 (~70.5 XLM instead of ~500 XLM). The math:
5,000,000,000 mod 4,294,967,296 = 705,032,704.Root cause:
MaxFee()on a regular inner transaction correctly returnsuint32because the inner transaction fee uses XDRUint32.NewMaxFee()was modelled after it without accounting for the fact that the fee-bump outer fee uses XDRInt64. The wrong type and cast were copied without adjustment.Proposed fix:
This is a breaking change to the public function signature. All callers must update their type handling from
uint32toint64.Known affected downstream consumer:
stellar/stellar-etlhas the same truncation bug independently atinternal/transform/transaction.go:294andinternal/transform/schema.go:63— that fix should be coordinated with this one.Impact: Any application using
LedgerTransaction.NewMaxFee()from the ingest SDK silently receives an incorrect fee value for any fee-bump transaction with a fee exceeding ~429.5 XLM. This affects all downstream analytics, reconciliation, or compliance systems built on this function.