Repository navigation
txnbuild: build SetOptions XDR from the current fields on each call - #6021
Conversation
SetOptions kept its XDR operation in an unexported struct field. BuildXDR wrote each optional field into it only when the public field was non-nil, and nothing reset it. When a caller reused a SetOptions and cleared a field such as Signer or MasterWeight, the next build still contained the old value. The emitted operation could add a signer or change the master weight that the public fields no longer showed. BuildXDR now builds a new xdr.SetOptionsOp on each call, as the other operations do. This also stops BuildXDR from writing to the receiver, so concurrent builds of one SetOptions no longer race. FromXDR had the same problem in the other direction: it set only the fields present in the XDR and kept the others. It now resets the receiver first.
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The changes are covered by regression tests and no unresolved issues remain.
Review effort: Lite
Findings: None
What changed in this PR
Updates SetOptions XDR handling to prevent stale optional fields when instances are reused.
Changes:
- Builds fresh XDR state on each call.
- Resets receivers before parsing XDR.
- Adds regression tests and changelog documentation.
| File | Description |
|---|---|
txnbuild/set_options.go |
Uses local XDR state and resets decoded values. |
txnbuild/set_options_test.go |
Adds reuse regression tests and updates helpers. |
txnbuild/CHANGELOG.md |
Documents the bug fix. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
FromXDR into a populated SetOptions used to append the XDR flags to the existing SetFlags and ClearFlags. The reuse test now puts flags in the source operation, so a fix that clears only the pointer fields fails. SetOptions had no marshaling round-trip test, unlike the other operations, because the removed xdrOp field made the struct comparison fail after BuildXDR. Add one for plain and muxed source accounts. The changelog entry now names the flag append.
…account's destination unchanged The old comment read as if the struct could not unset the field. The rule belongs to the network.
tamirms
left a comment
There was a problem hiding this comment.
Not for this PR: CreateClaimableBalance.FromXDR has the same merge problem. It appends to cb.Destinations (create_claimable_balance.go:153) without resetting, so decoding into a populated value keeps the old claimants, and rebuilding lets them claim. A *cb = CreateClaimableBalance{} after the type check, as done here, would fix it. RevokeSponsorship.FromXDR also leaves the previous type's field set, though BuildXDR ignores it.
Pending becomes 0.8.0 for the Protocol 30 release (#6028). Four of its entries already shipped in v0.7.3 and move to a new 0.7.3 section: LedgerCloseMetaView.LedgerHeader (#5982), rpcclient Client.URL() and both processors/token_transfer to_muxed_id fixes. Adds the txnbuild SetOptions fix (#6021), which reached protocol-next with #6027. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MBWddBqppX2FsWGbuaJ82g
What
SetOptions.BuildXDRnow builds a newxdr.SetOptionsOpon each call. Before, it wrote into an unexported struct field (xdrOp) that wasn't being properly reset.SetOptions.FromXDRnow resets the receiver before it reads the operation.Why
A reused
SetOptionskept fields that the caller had cleared:FromXDRinto a populated value had the same defect:A nice side effect to this change is that
BuildXDRno longer writes to the receiver, so concurrent builds of oneSetOptionsdo not race.Compatibility
xdrOpwas unexported, so the public API does not change. A caller that builds a newSetOptionsfor each operation gets the same XDR as before.