Summary
On an auditor node under c=800 stress (~520 TPS sustained, 19k unique wallets, x509 identities, zkatdlog driver), LocalMembership.refreshAndGet accumulates 82-88% of all mutex wait time (Go mutex profile). Looking at the call chain, the root cause appears to be that tokens.Service.Parse calls auth.IsMine() on every transaction output regardless of node role, and on an auditor the result is always false but the LocalMembership lookup is still performed.
Question for maintainers: is this intentional? Should the auditor path skip IsMine(), or is the Mine flag on TokenToAppend semantically required for auditor records?
Call chain
P2P.handleMessage
→ AuditView.Call
→ AuditApproveView.Call
→ tokens.Service.CacheRequest
→ extractActions // tokens.go:364
→ Parse // tokens.go:397
→ auth.IsMine() // tokens.go:438 (per output)
→ role.Lookup
→ LocalMembership.refreshAndGet
At tokens.go:438 Parse computes mine for every output:
ownerWalletID, ids, mine := auth.IsMine(ctx, &output.Token)
At line 450:
if !mine && !auditorFlag && !issuerFlag {
continue
}
For an auditor auditorFlag is always true, so mine does not affect keep/discard. The only later use of mine is the TokenToAppend.Flags.Mine field (line 475), which on an auditor is always false.
Evidence
Setup: 1 auditor + 1 institution + 19,996 x509 wallets, c=800, 60s. zkatdlog driver, identity_type=Owner cache fully warmed at start.
mutex profile breakdown (cumulative):
| Path |
Share |
LocalMembership.refreshAndGet |
88.3% |
↳ via GetIdentityInfo |
64.2% |
↳ via GetIdentifier |
24.1% |
auditor.Service.Append → auditdb.ReleaseLocks |
10.5% |
| other |
~1% |
Per-60s-window counters (with negative-caching shim added locally — without the shim every call takes write Lock):
- ~150,000 IsMine calls per 60s
- ~6,000 unique owner labels seen → all written to negative cache
- 0 positive resolutions on the auditor (
localIdentitiesByName / localIdentitiesByIdentity never matched a user-wallet label)
- 100% of slow-path entries either find the label in the negative cache during double-check, or do a PG
storedIdentityConfigurations read followed by writing a negative entry
So every IsMine call from the audit hot path is essentially "look up a user-wallet label that this auditor will never have locally → confirm absent → record absence". The lookup result is unused on the auditor (line 450 already kept it via auditorFlag).
audit_view p50 duration is ~1.5s under c=800, with audit_view inflight peaking at 16,943 goroutines (vs audit-pool size 120) — large queue piled up on the LocalMembership write Lock during slow-path entries.
Possible fixes (asking for guidance)
(A) Skip IsMine when auditorFlag is set, in Parse:
var ownerWalletID string
var ids []string
mine := false
if !auditorFlag {
ownerWalletID, ids, mine = auth.IsMine(ctx, &output.Token)
}
Smallest possible patch. Predicated on Mine: false being acceptable on an auditor's TokenToAppend records. Does anything read Flags.Mine on auditor-owned records?
(B) Auditor short-circuit in Authorization.IsMine:
Return false immediately when the local node is purely an auditor (no owner wallets and not an issuer). Less invasive at the call site but changes IsMine semantics for one node role.
(C) Different audit path that doesn't go through Parse:
More invasive — give the auditor view a dedicated parser that doesn't compute ownership flags.
Environment
- fabric-token-sdk: v0.10.0
- Driver: zkatdlog (nogh v1)
- Identity type: x509
- Go: 1.25
- Workload: ring transfers across 19,996 wallets, c=800 concurrent, 60s window
Happy to share full mutex profiles, OTel histogram dumps, or test patches if any of (A)/(B)/(C) sounds promising.
Summary
On an auditor node under c=800 stress (~520 TPS sustained, 19k unique wallets, x509 identities, zkatdlog driver),
LocalMembership.refreshAndGetaccumulates 82-88% of all mutex wait time (Go mutex profile). Looking at the call chain, the root cause appears to be thattokens.Service.Parsecallsauth.IsMine()on every transaction output regardless of node role, and on an auditor the result is alwaysfalsebut the LocalMembership lookup is still performed.Question for maintainers: is this intentional? Should the auditor path skip
IsMine(), or is theMineflag onTokenToAppendsemantically required for auditor records?Call chain
At
tokens.go:438Parse computesminefor every output:At line 450:
For an auditor
auditorFlagis always true, sominedoes not affect keep/discard. The only later use ofmineis theTokenToAppend.Flags.Minefield (line 475), which on an auditor is always false.Evidence
Setup: 1 auditor + 1 institution + 19,996 x509 wallets, c=800, 60s. zkatdlog driver, identity_type=Owner cache fully warmed at start.
mutex profile breakdown (cumulative):
LocalMembership.refreshAndGetGetIdentityInfoGetIdentifierauditor.Service.Append → auditdb.ReleaseLocksPer-60s-window counters (with negative-caching shim added locally — without the shim every call takes write Lock):
localIdentitiesByName/localIdentitiesByIdentitynever matched a user-wallet label)storedIdentityConfigurationsread followed by writing a negative entrySo every IsMine call from the audit hot path is essentially "look up a user-wallet label that this auditor will never have locally → confirm absent → record absence". The lookup result is unused on the auditor (line 450 already kept it via
auditorFlag).audit_view p50 duration is ~1.5s under c=800, with audit_view inflight peaking at 16,943 goroutines (vs audit-pool size 120) — large queue piled up on the LocalMembership write Lock during slow-path entries.
Possible fixes (asking for guidance)
(A) Skip IsMine when auditorFlag is set, in Parse:
Smallest possible patch. Predicated on
Mine: falsebeing acceptable on an auditor's TokenToAppend records. Does anything readFlags.Mineon auditor-owned records?(B) Auditor short-circuit in
Authorization.IsMine:Return
falseimmediately when the local node is purely an auditor (no owner wallets and not an issuer). Less invasive at the call site but changes IsMine semantics for one node role.(C) Different audit path that doesn't go through Parse:
More invasive — give the auditor view a dedicated parser that doesn't compute ownership flags.
Environment
Happy to share full mutex profiles, OTel histogram dumps, or test patches if any of (A)/(B)/(C) sounds promising.