Skip to content

Composite-owner inputs still double-count when token IDs are filtered out of the metadata #2240

Description

@EvanYan1024

Problem

InputStream.UniquePerInput() (added in #2148) deduplicates the member rows of a composite-owner input by (token ID, enrollment ID), and keeps every input whose token ID is nil.

Token IDs are not always there. Metadata.filterTransfer builds the enrollment-ID-filtered copy of the transfer metadata by cloning only Senders and never TokenID (token/metadata.go), and ttx marshals exactly that filtered copy for each counterparty (token/services/ttx/marshaller.go, via FilterMetadataBy). On those consumers every Input carries Id == nil, so the deduplication no-ops and the member rows are summed again: spending 40 from a wallet whose two members share an enrollment ID is recorded as -80 in that party's transaction records and movements.

This gap was identified during the review of #2148 and deferred as a follow-up.

Proposed fix

No driver/metadata wire change is needed. The filtering is position-preserving: filterTransfer emits one (possibly empty) input-metadata entry per input, and Request.extractTransferInputs walks them by index, so the input's position within its action is available even when the token ID is not.

  • Give Input an Index field, its position within the action, mirroring the role of Output.Index.
  • Key UniquePerInput on (token ID, enrollment ID) when the token ID is present (behavior unchanged for existing callers), falling back to (action index, input index, enrollment ID) when it has been filtered out.

The two key spaces cannot mix: filterIssue keeps Inputs verbatim (token IDs included), so only transfer inputs ever lose their token ID, and the per-type action index is unique among transfers.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions