fix(token): count composite-owner outputs once per enrollment ID in amount sums - #2148
Conversation
92f93c6 to
11bd6ca
Compare
|
Thanks a lot for this PR. Nice fix. One thing I wanted to ask about: does the same apply to the input side? extractTransferInputs looks like it expands members the same way (for _, sender := range input.Senders, same token ID), and AuditRecord() fills each row with the full quantity — so received is fixed but sent would still count once per member. Spending 40 from a two-member wallet gave me -80 rather than -40, so I may be missing something about that path. If it's the same shape, would an InputStream.UniquePerInput() keyed on (Id, EnrollmentID) be the natural fit, given Input has no index? Fine as a follow-up if you'd prefer. What do you think? |
|
Thanks Akram, you are right. I traced the input path and confirmed that it has the same representation as the output side:
I’ll include this in the current PR since it is the same accounting issue rather than leave movements partially fixed. I’ll also update the composite-spend test to use two input member rows, add the Thanks a lot for the concrete -80 reproduction — it made the input-side gap easy to confirm. |
|
@EvanYan1024 Thanks a lot, I have the following concerns, I think they may warrant a follow-up rather than changes in this PR. What do you think? 1. Different enrollment IDs. Both keys include 2. 3. Auditor views. |
|
Thanks — I've done (3) here and would leave (1) as is and (2) as a follow-up. Reasoning below.
It is also a no-op where it is actually used: in Movements the deduplication runs after ByEnrollmentID(eID), so every row in the stream already carries the same enrollment ID. The eID in the key only guards a caller who reaches for UniquePerInput on an unfiltered stream.
Giving Input an index is the right fix, and there is no cheap substitute: ActionIndex cannot serve as the key because several distinct tokens share one action index, so keying on it would over-deduplicate. The index has to be carried through TransferInputMetadata, which is a driver metadata change and larger than this PR. I'll open a follow-up issue for it unless you'd rather it land here.
I ran the token module tests with -race and the pinned linter over both modules, but not the integration suite locally — leaving that to CI. |
|
Thanks a lot for the great work you did on this PR. I really appreciate it! Best Regards, ProblemTokens owned by a composite owner (multisig) list each member's enrollment ID FixDeduplicate before summing: each
Out of scopeComposite owners whose members have distinct enrollment IDs are unchanged: |
02a4134 to
b65e73f
Compare
|
I would like to rebase and merge the changes. Could you please rebase and then squash all the commits to one? Regards, |
b65e73f to
5699fa2
Compare
|
@AkramBitar Done — rebased onto Ready to merge whenever you are. Thanks for the thorough review |
Tokens owned by a composite owner (multisig) list each member's enrollment
ID separately. Amount aggregations walked those entries individually, so a
single token was counted once per member - inflating auditor balances,
movements, and the sums that auditor limit checks are evaluated against.
Deduplicate before summing: each (output index, enrollment ID) / (token ID,
enrollment ID) pair is counted once.
- token/stream.go - new dedup filters on the input/output streams.
- token/services/storage/ttxdb/store.go - applied when building transaction
records and movements.
- integration/token/{fungible,interop}/views/auditor.go - applied at the
payment, cumulative, and holding limit checks.
- docs/services/storage/ttxdb.md - documents the aggregation rule.
Composite owners whose members have distinct enrollment IDs are unchanged:
such an input is still rejected by TransactionRecords, and Movements books
the full amount once per co-owner. Tracked separately.
Fixes LFDT-Panurus#2147
Signed-off-by: Evan <evanyan@sign.global>
5699fa2 to
3193823
Compare
Fixes #2147
What
OutputStream.UniquePerOutput, keeping for each(Index, EnrollmentID)pair only the first output, and apply it in the ttxdbTransactionRecordsandMovementssums (shared by the owner and auditor stores).InputStream.UniquePerInput, keeping for each(token ID value, EnrollmentID)pair only the first input — rows with no token ID are all kept — and apply it in theMovementssent sum. The input side has the same shape (raised in review below):extractTransferInputsemits one row per member with the same token ID andAuditRecordfills every row with the token's full quantity.Why
A composite owner (multisig, boolpolicy) is expanded into one audit row per member on both sides of a transaction, each row carrying the full amount. Members sharing an enrollment ID therefore multiply the amount by the member count in every eid-keyed sum: in our test environments a 100 deposit into a wallet whose owner held two keys of the same enrollment booked as 200 received, and review reproduced the symmetric sent side — a 40 spend from a two-member wallet booked as -80.
Deduplicating at extraction time instead would drop data that identity consumers need —
RevocationHandles()must surface a revoked second key andByRecipient()must see every member — so the collapse happens only where amounts aggregate.Testing
token/request_composite_output_test.go: composite owners with same-enrollment and cross-enrollment members, issue and transfer paths; asserts identity consumers still see every member row.token/services/storage/ttxdb/store_test.go:TransactionRecords/Movementsbook a composite-owner output once per enrollment ID; the composite-spend fixture expands the spent input into two member rows sharing one token ID, pinning the movement to -6 instead of -46.token/stream_test.go: table-driven contract tests forUniquePerOutputandUniquePerInput— the same(Index, EnrollmentID)/(token ID, EnrollmentID)pair collapses to the first row (for inputs via distinct pointers to the same token ID value); the same index or token ID with different enrollment IDs, and different indexes or token IDs with the same enrollment ID, all survive; inputs with no token ID are all kept.Docs
docs/services/storage/ttxdb.mdgains a "Composite Owners" note: one audit row per member for identity/revocation visibility, amount aggregations count each(output index, enrollment ID)/(token ID, enrollment ID)pair once.