ingest/contractevents: tolerate CAP-86 sparse maps in V4 SAC event data - #223
ingest/contractevents: tolerate CAP-86 sparse maps in V4 SAC event data#223tamirms wants to merge 2 commits into
Conversation
parseSacEventMap required the data map to hold exactly two entries and to carry a to_muxed_id. A transfer with no muxed destination may express that by omitting the field, as a CAP-86 sparse map (protocol 28) would, or by binding the key to Void, and neither shape parsed. Require only amount, accept both encodings of a None to_muxed_id, and ignore unrecognized keys. This is forward-compat hardening rather than a fix for anything in ledger history: the Stellar Asset Contract emits a map only when a muxed destination exists, and always with both keys. It matters because every caller discards an event that fails to parse instead of surfacing the error, so a shape this parser rejects silently costs that event its account_credited and account_debited effects, its balance_changes operation details, and its participants. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Closing as unnecessary. The premise behind this change was that CAP-0086 sparse maps could reach SAC event data. They cannot, on two independent grounds:
So no event in ledger history hits the shapes this PR newly accepts, and none ever will without a separate CAP that changes ledger meta and forces a coordinated Horizon update anyway. Two things surfaced here that may deserve their own issues, independent of CAP-0086:
The companion SDK PR (stellar/go-stellar-sdk#5983) was reduced for the same reason and now carries only a genuine bug fix in |
|
Reopened. This PR stands on the premise that CAP-0086 applies to contract storage as well as events, in which case a V4 SAC event data map can arrive sparsely encoded and this parser's exact-arity gate plus mandatory Disregard the previous comment. |
Companion to go-stellar-sdk#5983, which has the full write-up of the encoding change.
What
CAP-0086 (protocol 28) adds host functions that build Soroban UDT struct maps sparsely: a field holding
Noneis omitted from the map rather than stored bound toVoid. Both encodings will exist in ledger history forever, so a decoder must treat key absent and key bound toVoidas the same thing and must not assume a fixed map arity.parseSacEventMapassumed both:A transfer with no muxed destination cannot satisfy either, in either encoding.
Changes
amount.to_muxed_idbound toVoidas "no muxed destination" (it decodes toMemoNone, which both consumers already handle explicitly).makeV4MapDatanow builds a map without ato_muxed_idkey for aMemoNonememo, and a newmakeV4MapDataWithMuxedIDcovers the explicit-Voidshape. Tests cover both encodings ofNoneplus the unknown-key case.Why it matters, and what it does not change
This is forward-compat hardening, not a fix for anything in ledger history. Verified against rs-soroban-env: the Stellar Asset Contract emits a map only when a muxed destination exists, and always with both keys — so no event on chain today hits the rejected shapes, and no reingestion is needed. The accepted set is unchanged for all existing data.
It is worth doing anyway because of how callers treat a parse failure. Every one of them discards the event rather than surfacing the error:
effects_processor.go:1547—if err != nil { continue }operations_processor.go:793,:1096—if ... err == nilSo a shape this parser rejects does not raise an ingestion error; it silently costs that event its
account_credited/account_debitedeffects, itsbalance_changesoperation details, and its participants — leaving the transaction unindexed for the accounts involved. Failing open is the correct bias here, which is also why unrecognized keys are now ignored rather than rejected.Note for review
Two test expectations flip, both intentionally:
V4 map data insufficient elements→ renamedV4 map data with no entries; the error is nowamount field not found in maprather thanexpected exactly 2 elements in map data.V4 map data - missing muxed id→ renamedV4 map data with an unrecognized key; it now parses successfully instead of erroring. (Its fixture carries a typo'dtooo_muxed_idkey, so it was really testing unknown-key handling all along.)Deliberately self-contained
The SDK PR adds
xdr.ScMap.GetSymbolField, which encodes this rule once. This PR does not use it, so that it compiles against the currently pinnedgo-stellar-sdk. Worth a follow-up to adopt it after the dependency bump — the deeper duplication is thatinternal/ingest/contracteventsis a near-clone of the SDK'sprocessors/token_transferV4 parsing, and this same one-line rule had to be applied to both.The SDK PR also carries the
ingest/sacdecoders that backasset_stats_processor.goandcontract_asset_stats.go; those reach Horizon transparently on the next SDK bump, with one behavior note about contract asset stats documented there.Testing
internal/ingest/contracteventsandinternal/ingest/processorspass;gofmtandgo vetclean. The DB-backed tests ininternal/ingestwere not run locally (no Postgres available).🤖 Generated with Claude Code