Skip to content

Define a retention policy and TTL strategy so payment records are not archived out of the audit log #465

Description

@Cedarich
  • Complexity: Hard
  • Labels: Soroban, contracts, storage, reliability, Hard
  • Overview: The contract describes itself as "a reliable on-chain log of invoice payments" and states that "records survive ledger archival", but its TTL policy cannot deliver that. BUMP_TTL is 518,400 ledgers, roughly 30 days at five-second close times, and persistent entries are extended only when they are read or written. A payment record that nobody touches for about a month is archived by the network. Since the point of an audit log is that old records sit untouched, the records most important to preserve are precisely the ones guaranteed to expire.
  • Details:
    • set_payment, append_payment_history, and the allowlist writes all extend to BUMP_TTL on access, and get_payment / get_history_record bump on read. Nothing extends a record that is not being accessed.
    • Reconciliation reads an invoice around the time it settles and then generally never again, so a payment record's last access is usually within minutes of its creation. Thirty days later it is eligible for archival.
    • Archival is indistinguishable from corruption in the current read paths. get_payment_history_page counts a missing slot in gaps_skipped, and that field is documented as indicating a "corrupted or partially-rebuilt index". An operator seeing gaps would reasonably run rebuild_history_index, which cannot restore archived entries and will not help.
    • get_payment on an archived record returns PaymentNotFound, which the backend and the TypeScript error manifest both treat as "no payment was ever recorded" — the opposite of the truth. Reconciliation that trusts this could re-anchor an invoice that was already settled.
    • PaymentCount and PaymentHistoryCount live in instance storage and are not decremented by archival, so the counters will drift above the number of readable records and history_index_status will report an inconsistency that no rebuild can fix.
    • There is no bulk TTL-extension entrypoint, so an operator who wants to keep history alive has no mechanism other than reading every record individually before each expiry window.
    • Soroban supports restoring archived entries, but the contract exposes no restore-aware path and the ops tooling has no procedure for it.
  • Scope:
    • Decide and document an explicit retention policy for payment records and the history index, separating what must be permanently retrievable on-chain from what may be archived and reconstructed from events off-chain.
    • Add an admin-gated bulk TTL-extension entrypoint that walks a bounded range of the history index and extends record TTLs, so retention can be maintained without reading every record individually.
    • Choose TTL constants that match the retention policy instead of the current thirty-day bump, and document the ledger-cost implications.
    • Distinguish archived from absent in the read paths, so PaymentNotFound and a history gap no longer conflate "never recorded" with "archived and restorable".
    • Separate archival-induced gaps from genuine index corruption in PaymentHistoryPage and history_index_status, so rebuild_history_index is not the recommended response to expiry.
    • Document the restore procedure and add an ops script for it alongside the existing pause and allowlist scripts.
    • Make the backend and TypeScript client handle an archived record explicitly rather than treating it as an unrecorded payment.
  • Technical scope:
    • soroban/contracts/invoice-payment/src/storage.rs
    • soroban/contracts/invoice-payment/src/lib.rs
    • soroban/contracts/invoice-payment/src/errors.rs
    • soroban/contracts/invoice-payment/src/test.rs
    • soroban/client/src/soroban-invoice-client.ts
    • soroban/client/src/error-manifest.ts
    • backend/src/stellar/soroban.service.ts
  • Acceptance criteria:
    • The retention policy is documented, and the TTL constants and bump strategy implement it.
    • An admin can extend TTLs across a bounded range of payment records in a single call, and repeated calls cover the full history without exceeding the resource budget.
    • An archived record is distinguishable from one that was never recorded, in both the contract read paths and the TypeScript client.
    • History gaps caused by archival are distinguishable from gaps caused by index corruption.
    • history_index_status does not report unfixable inconsistency purely because records were archived.
    • Tests advance the ledger past the TTL window and assert the archival, distinguish, and extend behaviours.
    • The restore procedure is documented and covered by an ops script.
    • The backend does not re-anchor an invoice whose on-chain record was archived rather than missing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

GrantFox OSSIssue tracked in GrantFox OSSHardHigh-complexity taskMaybe RewardedIssue may be eligible for a GrantFox rewardSorobanThird CampaignCampaign: Third CampaigncontractsSmart contract implementationreliabilityReliability and resiliencystorageContract or app storage design

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions