The Auditor Service (token/services/auditor) provides specialized tools and interfaces for nodes acting in an oversight or compliance role.
It allows authorized auditors to inspect token transactions.
The Auditor Service is responsible for:
zkatdlog).The Auditor Service is typically invoked during the CollectEndorsements phase of a token transaction.
sequenceDiagram
autonumber
participant Initiator
participant TTX as TTX Service
participant Auditor as Auditor Service
participant Network as Network Service
box darkgreen Panurus Stack
participant TTX
participant Auditor
participant Network
end
Note over Initiator: Transaction Assembly
Initiator->>+TTX: CollectEndorsements
TTX->>+Auditor: AuditApproveView
Note over Auditor: 1. De-obfuscate Request
Auditor->>+Auditor: Verify Validity
Auditor->>+Auditor: Inspect Actions & Metadata
Note over Auditor: 2. Sign & Store
Auditor->>+Auditor: Generate Auditor Signature
Auditor->>+Auditor: Update AuditDB
Auditor-->>-TTX: Auditor Signature
TTX->>+Network: Request Approval
Network-->>-TTX: Request Approved
Auditors use specialized wallets (Auditor Wallets) managed by the Identity Service. These wallets contain the cryptographic keys necessary to “open” the commitments and proofs found in privacy-preserving token transactions.
The service provides the AuditApproveView, which auditors use to respond to incoming audit requests. This view automates the verification and signing process, ensuring that the auditor only approves transactions that are fully compliant with the system’s public parameters.
When multiple auditor replicas share the same AuditDB (PostgreSQL), concurrent processing of the same enrollment IDs (EIDs) must be serialized. The auditor locker (token/services/storage/auditdb/locker) coordinates exclusive access to EIDs during audit record writes.
| Package | Role |
|---|---|
auditdb/locker/memory |
In-process semaphores for single-replica deployments |
auditdb/locker/postgres |
Lease-table locking backed by PostgreSQL |
auditdb/locker |
Factory, configuration, and DI wiring |
The locker is injected into auditdb.StoreService at startup. Before appending audit records, the store calls AssertLocksHeld to verify leases are still valid.
Configure under token.tms.<name>.auditor.locker (see Configuration):
token:
tms:
mytms:
auditor:
locker:
backend: postgres # use "memory" (default) for single replica
postgres:
ttl: 30s
acquireBackoff: 100ms
acquireDeadline: 1m
heartbeat: 10s
owner: # required; defaults to the FSC node ID
Backend selection:
| Backend | Use case | Database |
|---|---|---|
memory |
Single replica | Any |
postgres |
Multi-replica | PostgreSQL only |
The Postgres backend creates an eid_leases table (prefixed per TMS persistence settings) and uses lease rows with heartbeat renewal.
Lease ownership: each row is keyed by EID and carries the holding replica (owner) plus the request it was taken for (anchor). An acquisition may only take over an existing row when the lease has expired, or when the row is the same replica’s lease for the same anchor — a re-acquisition, which just refreshes the deadline. Two different anchors therefore never hold the same EID at once, including two concurrent audits on a single replica; the second one is contended and retried until acquireDeadline.
Every lease row carries an owner column, and each replica scopes all of its lease
queries by it — the acquire upsert, the release, the heartbeat renewal, and
AssertLocksHeld. The owner must therefore be non-empty and unique per replica.
Resolution order:
token.tms.<name>.auditor.locker.postgres.owner, when set.fsc.id, via the config provider’s ID()).If both are empty or blank, the locker fails to start with
auditor locker owner is required, and the audit store for that TMS is never
created. This is deliberate: a shared empty owner would make every owner-scoped
lease predicate match on rows belonging to other replicas, so a replica could
release or renew leases it does not hold and AssertLocksHeld could be satisfied
by another replica’s row — silently removing mutual exclusion across the whole
cluster. Failing at startup surfaces the misconfiguration instead.
A common way to hit this is a templated or cloned node configuration where fsc.id
was left unset. Note that no owner is synthesized as a fallback: an owner that
changed on every restart would leave a restarted replica unable to renew or release
the leases it still holds in the table.
The memory backend has no owner concept and is unaffected.