Skip to content

Commit 13c79da

Browse files
authored
Improve CollectEndorsementsView's handling of signers #1226 (#1895)
Signed-off-by: Angelo De Caro <adc@zurich.ibm.com>
1 parent 8b3c726 commit 13c79da

49 files changed

Lines changed: 1855 additions & 196 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/drivers/metrics.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ Caller → Metrics Wrapper → Concrete Driver Service
4242
This keeps business logic free of monitoring concerns and guarantees that any new driver
4343
automatically gets the same metrics by wrapping its services at construction time.
4444

45+
### Pitfall: `LabelNames` must include `network`, `channel`, `namespace`
46+
47+
`NewTMSProvider` (`token/core/common/metrics/provider.go`) wraps the underlying `Provider` so that
48+
*every* metric it creates is bound to fixed `network`/`channel`/`namespace` label values via
49+
`.With(...)` before the metric is returned — the caller never supplies these three values itself.
50+
51+
Because of this, any `CounterOpts`/`GaugeOpts`/`HistogramOpts` passed to a TMS-scoped provider's
52+
`NewCounter`/`NewGauge`/`NewHistogram` **must declare `"network", "channel", "namespace"` as
53+
`LabelNames`**, even though nothing in the wrapper code ever passes values for them explicitly.
54+
Forgetting them creates a Prometheus vector with 0 label names while `NewTMSProvider` immediately
55+
calls `.With(...)` with 3 values, which panics at runtime ("inconsistent label cardinality") the
56+
first time the metric is used — not at registration time, so it can slip past a quick smoke test.
57+
This exact mistake shipped in `token/services/identity/metrics.go` and crashed the DVP/DLog
58+
integration suite inside `SignerRouter.Register`; see that file for the corrected `LabelNames`.
59+
4560
## Wrapped Services
4661

4762
Five driver services are wrapped:

docs/imgs/storage_db.puml

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,24 @@ package "Transaction & Audit Store (TTXDB / AuditDB)" {
88
entity "Requests" as requests {
99
* tx_id : TEXT <<PK, NOT NULL>>
1010
--
11-
request : BYTEA
12-
status : INT
13-
status_message : TEXT
14-
application_metadata : JSONB
15-
public_metadata : JSONB
16-
pp_hash : BYTEA
11+
request : BYTEA <<NOT NULL>>
12+
status : INT <<NOT NULL>>
13+
status_message : TEXT <<NOT NULL>>
14+
application_metadata : JSONB <<NOT NULL>>
15+
public_metadata : JSONB <<NOT NULL>>
16+
pp_hash : BYTEA <<NOT NULL>>
17+
recovery_claimed_by : TEXT
18+
recovery_claim_expires_at : TIMESTAMP
19+
stored_at : TIMESTAMP <<NOT NULL>>
1720
}
1821

1922
entity "Transactions" as txs {
2023
* id : CHAR(36) <<PK>>
2124
--
2225
tx_id : TEXT <<FK, NOT NULL>>
23-
action_type : INT
24-
sender_eid : TEXT
25-
recipient_eid : TEXT
26+
action_type : INT <<NOT NULL>>
27+
sender_eid : TEXT <<NOT NULL>>
28+
recipient_eid : TEXT <<NOT NULL>>
2629
token_type : TEXT <<NOT NULL>>
2730
amount : NUMERIC(78,0) <<NOT NULL>>
2831
stored_at : TIMESTAMP <<NOT NULL>>
@@ -32,7 +35,7 @@ package "Transaction & Audit Store (TTXDB / AuditDB)" {
3235
* id : CHAR(36) <<PK>>
3336
--
3437
tx_id : TEXT <<FK, NOT NULL>>
35-
enrollment_id : TEXT
38+
enrollment_id : TEXT <<NOT NULL>>
3639
token_type : TEXT <<NOT NULL>>
3740
amount : NUMERIC(78,0) <<NOT NULL>>
3841
stored_at : TIMESTAMP <<NOT NULL>>
@@ -41,10 +44,10 @@ package "Transaction & Audit Store (TTXDB / AuditDB)" {
4144
entity "Endorsements" as tx_ends {
4245
* id : CHAR(36) <<PK>>
4346
--
44-
tx_id : TEXT
45-
endorser : BYTEA
46-
sigma : BYTEA
47-
stored_at : TIMESTAMP
47+
tx_id : TEXT <<NOT NULL>>
48+
endorser : BYTEA <<NOT NULL>>
49+
sigma : BYTEA <<NOT NULL>>
50+
stored_at : TIMESTAMP <<NOT NULL>>
4851
}
4952

5053
txs }o--|| requests : "tx_id"
@@ -56,11 +59,19 @@ package "Endorser Store (EndorserDB)" {
5659
entity "Validations" as req_vals {
5760
* tx_id : TEXT <<PK, NOT NULL>>
5861
--
59-
request : BYTEA
60-
metadata : BYTEA
61-
pp_hash : BYTEA
62+
request : BYTEA <<NOT NULL>>
63+
metadata : BYTEA <<NOT NULL>>
64+
pp_hash : BYTEA <<NOT NULL>>
65+
status : INT <<NOT NULL>>
66+
status_message : TEXT <<NOT NULL>>
6267
stored_at : TIMESTAMP <<NOT NULL>>
6368
}
69+
70+
note right of req_vals
71+
Self-contained: no FK to Requests.
72+
Endorser nodes track validation
73+
status directly on this table.
74+
end note
6475
}
6576

6677
package "Token Store (TokenDB)" {
@@ -112,11 +123,11 @@ package "Token Store (TokenDB)" {
112123
}
113124

114125
entity "TokenLocks" as tkn_locks {
115-
* tx_id : TEXT <<PK, NOT NULL>>
116-
* idx : INT <<PK, NOT NULL>>
126+
* tx_id : TEXT <<PK, NOT NULL, FK>>
127+
* idx : INT <<PK, NOT NULL, FK>>
117128
--
118-
consumer_tx_id : TEXT
119-
created_at : TIMESTAMP
129+
consumer_tx_id : TEXT <<NOT NULL>>
130+
created_at : TIMESTAMP <<NOT NULL>>
120131
}
121132

122133
entity "TokenSKICleanups" as tkn_ski_cleanups {
@@ -129,7 +140,7 @@ package "Token Store (TokenDB)" {
129140

130141
tkn_own }o--|| tokens : "(tx_id, idx)"
131142
tkn_crts }o--|| tokens : "(tx_id, idx)"
132-
tkn_locks |o--o| tokens : "(tx_id, idx)"
143+
tkn_locks }o--|| tokens : "(tx_id, idx)"
133144
tkn_ski_cleanups }o--|| tokens : "(tx_id, idx)"
134145
}
135146

@@ -140,8 +151,9 @@ package "Wallet & Identity Store (WalletDB / IdentityDB)" {
140151
* role_id : INT <<PK, NOT NULL>>
141152
--
142153
meta : BYTEA
143-
enrollment_id : TEXT
154+
enrollment_id : TEXT <<NOT NULL>>
144155
created_at : TIMESTAMP
156+
conf_id : TEXT <<FK, NOT NULL>>
145157
}
146158

147159
entity "IdentityConfigurations" as id_cfgs {
@@ -151,36 +163,38 @@ package "Wallet & Identity Store (WalletDB / IdentityDB)" {
151163
--
152164
conf : BYTEA
153165
raw : BYTEA
166+
conf_id : TEXT <<UNIQUE, NOT NULL>>
154167
}
155168

156169
entity "IdentityInfo" as id_info {
157170
* identity_hash : TEXT <<PK, NOT NULL>>
158171
--
159-
identity : BYTEA
160-
identity_audit_info : BYTEA
172+
identity : BYTEA <<NOT NULL>>
173+
identity_audit_info : BYTEA <<NOT NULL>>
161174
token_metadata : BYTEA
162175
token_metadata_audit_info : BYTEA
163176
}
164177

165178
entity "IdentitySigners" as id_signers {
166179
* identity_hash : TEXT <<PK, NOT NULL>>
167180
--
168-
identity : BYTEA
181+
identity : BYTEA <<NOT NULL>>
169182
info : BYTEA
170183
}
171184

172-
wallets }o--|| id_info : "identity_hash"
173-
wallets }o--|| id_signers : "identity_hash"
185+
wallets }o..o{ id_info : "identity_hash (no FK constraint)"
186+
wallets }o..o{ id_signers : "identity_hash (no FK constraint)"
187+
wallets }o--|| id_cfgs : "conf_id"
174188
}
175189

176190
entity "KeyStore" as key_store {
177191
* key : TEXT <<PK, NOT NULL>>
178192
--
179-
val : BYTEA
193+
val : BYTEA <<NOT NULL>>
180194
}
181195

182196
' Relationships between packages
183-
tkn_own }o--|| wallets : "wallet_id"
184-
tokens }o--|| wallets : "owner_wallet_id"
197+
tkn_own }o..o{ wallets : "wallet_id (no FK constraint)"
198+
tokens }o..o{ wallets : "owner_wallet_id (no FK constraint)"
185199

186-
@enduml
200+
@enduml

docs/services/identity.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,27 @@ func (d *Base) NewWalletService(...) (*wallet.Service, error) {
141141
}
142142
```
143143

144+
### SignerRouter (conf_id-pinned fast path)
145+
146+
`GetSigner`'s default resolution path is a **fallback deserializer**: a linear scan across every `KeyManager` registered under the identity's type, each probed with a cryptographic sign+verify to find the one that actually matches. `SignerRouter` (`token/services/identity/signer_router.go`) is an optional fast path that skips this scan-and-probe entirely: it resolves the `conf_id` an identity was bound under (via a `ConfIDResolver`) and dispatches straight to the single `KeyManager` registered for that `conf_id`.
147+
148+
* **Wiring**: a driver builds a `SignerRouter` with `identity.NewSignerRouter(m *Metrics)`, registers `KeyManager`s against their `conf_id` with `Register`, sets a `ConfIDResolver` with `SetConfIDResolver`, and attaches it to the `Provider` with `Provider.SetSignerRouter`. See `token/core/fabtoken/v1/driver/ws.go` and the zkatdlog equivalent.
149+
* **Fallback semantics**: `Resolve` returns `ok=false` (never an error) whenever routing cannot be attempted (no resolver set, no `conf_id` mapping, no `KeyManager` registered for it) or the routed `KeyManager` itself fails — callers always fall back to the probing deserializer in that case, never treating it as a hard failure.
150+
* **Probe-free deserialization**: when the registered `KeyManager` also implements `idriver.ProbeFreeSignerDeserializer`, `Resolve` calls `DeserializeSignerNoProbe` directly, skipping the cryptographic probe that the fallback path relies on to catch a mismatched `KeyManager`. This is only safe because the `conf_id` already pins the identity to exactly one `KeyManager`.
151+
152+
#### Metrics
153+
154+
`identity.Metrics` (`token/services/identity/metrics.go`) instruments both `Provider.GetSigner` and `SignerRouter`, sharing one `Metrics` instance built with `identity.NewMetrics(provider)` (a `nil` provider yields a `disabled.Provider`-backed noop):
155+
156+
| Metric | Type | Labels | Purpose |
157+
|:-------|:-----|:-------|:--------|
158+
| `identity_signer_resolutions_total` | Counter | `network`, `channel`, `namespace`, `outcome` = `cache` \| `routed` \| `fallback` | How each `GetSigner` call was ultimately resolved. |
159+
| `identity_get_signer_duration_seconds` | Histogram | `network`, `channel`, `namespace`, `path` = `cache` \| `routed` \| `fallback` | `GetSigner` wall-clock time by resolution path; compares the latency saved by skipping the probe. |
160+
| `identity_signer_router_registrations_total` | Counter | `network`, `channel`, `namespace` | `conf_id``KeyManager` bindings registered with the `SignerRouter`. A near-zero count in production means routing is never populated and every call falls back. |
161+
| `identity_signer_router_no_probe_errors_total` | Counter | `network`, `channel`, `namespace` | Failures of the probe-free deserialization path — since that path skips the cryptographic check, a non-zero count is worth investigating as a `conf_id` routing bug. |
162+
163+
> **Note:** `provider` here is a `NewTMSProvider`-wrapped `Provider` (see [Driver Metrics](../drivers/metrics.md#pitfall-labelnames-must-include-network-channel-namespace)), which binds `network`/`channel`/`namespace` on every metric via `.With(...)` before returning it. Every `CounterOpts`/`HistogramOpts` above must therefore declare those three as `LabelNames` in addition to its own label(s), or the metric panics with "inconsistent label cardinality" on first use. This is exactly the bug that crashed the DVP/DLog integration suite in `SignerRouter.Register` before it was fixed.
164+
144165
## Identity Types
145166

146167
The Identity Service leverages a wrapper called **TypedIdentity** to support various identity schemes uniformly.

docs/services/storage.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,15 @@ The [`ttxdb/auditdb`](./storage/ttxdb.md) manages the token request lifecycle an
2828
The following tables track the lifecycle of token requests from assembly to finality.
2929
`AuditDB` uses the same schema but is isolated for compliance reporting.
3030

31-
* **Requests**: Tracks high-level token request state. Contains marshaled requests, current status (Pending, Confirmed, Deleted, Orphan), and application/public metadata.
31+
* **Requests**: Tracks high-level token request state. Contains marshaled requests, current status (Pending, Confirmed, Deleted, Orphan), application/public metadata, and the `recovery_claimed_by`/`recovery_claim_expires_at` lease columns used by the Transaction Recovery Service to atomically claim batches of pending transactions.
3232
* **Transactions**: Records individual actions (Issue, Transfer, Redeem) within a request, including sender/recipient IDs and amounts (stored as `NUMERIC(78, 0)`).
3333
* **Movements**: Aggregates net value changes per enrollment ID (amounts stored as `NUMERIC(78, 0)`). Used to efficiently calculate balances and history.
3434
* **Endorsements**: Collects digital signatures from participants and auditors required for transaction finality.
3535

3636
### Endorser Store (EndorserDB)
37-
The [`endorserdb`](./storage/endorserdb.md) manages validation records created during the token request endorsement process. It shares the physical database with TTXDB but provides a separate interface for validation-specific operations.
37+
The [`endorserdb`](./storage/endorserdb.md) manages validation records created during the token request endorsement process. It shares the physical database with TTXDB but owns its own, self-contained table — it does not write to the Requests table.
3838

39-
* **Validations**: Stores cryptographic validation metadata produced during the request verification phase. The Validations table is self-contained and stores the token request data directly (along with pp_hash) for efficient retrieval. When a validation record is created (typically by endorser nodes), it atomically creates entries in both the Requests table (for foreign key integrity and status tracking) and the Validations table (with embedded token request for direct access), ensuring that non-owner nodes can properly track and recover transactions.
40-
* **Requests** (shared): The endorserdb uses the Requests table to track validation status. While the table is shared with TTXDB, the endorserdb interface provides methods specifically for managing validation-related status updates.
39+
* **Validations**: Stores cryptographic validation metadata produced during the request verification phase. The table is self-contained: it stores the token request data, metadata, and pp_hash directly (along with its own `status`/`status_message` columns), so a validation record can be created and its status tracked without any foreign key into the Requests table. This lets non-owner (endorser) nodes track and recover transactions independently of the TTXDB/AuditDB Requests row for the same `tx_id`.
4140

4241
### Token Store (TokenDB)
4342
This store serves as the authoritative registry for all tokens (UTXOs) known to the node.
@@ -166,10 +165,9 @@ The `ttxdb` serves as the central repository for the lifecycle of token requests
166165

167166
### Endorser Store (EndorserDB)
168167
The `endorserdb` manages validation records for token requests during the endorsement process. It is used by the **Endorsement Service** to:
169-
* **Validations**: Store validation metadata and token requests validated by endorser nodes.
170-
* **Requests** (shared): Track the status of validated token requests.
168+
* **Validations**: Store validation metadata, token requests, and their own status validated by endorser nodes, in a single self-contained table.
171169

172-
The endorserdb shares the same physical database as ttxdb but provides a separate, focused interface for validation-specific operations, improving modularity and separation of concerns.
170+
The endorserdb shares the same physical database as ttxdb but owns its own table and interface for validation-specific operations, improving modularity and separation of concerns.
173171

174172
### Token Store (TokenDB)
175173
The `tokendb` is the registry for the current state of all tokens (UTXOs) known to the node. It is used by the **Selector Service** and **Vault Service** to:

docs/services/storage/endorserdb.md

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The endorser database stores validation records that track token requests valida
99
- Token request data
1010
- Validation metadata
1111
- Public parameters hash
12+
- Status and status message
1213
- Timestamp
1314

1415
## Architecture
@@ -116,30 +117,23 @@ status, message, err := store.GetStatus(ctx, txID)
116117

117118
## Database Schema
118119

119-
The endorserdb uses two tables:
120+
The endorserdb uses a single, self-contained table:
120121

121122
### VALIDATIONS Table
122123
Stores validation records created during endorsement:
123124
- `tx_id`: Transaction identifier (primary key)
124-
- `request`: Token request data
125-
- `metadata`: Validation metadata (JSON)
126-
- `pp_hash`: Public parameters hash
127-
- `stored_at`: Timestamp
125+
- `request`: Token request data (NOT NULL)
126+
- `metadata`: Validation metadata (NOT NULL)
127+
- `pp_hash`: Public parameters hash (NOT NULL)
128+
- `status`: Validation status (NOT NULL)
129+
- `status_message`: Status message (NOT NULL)
130+
- `stored_at`: Timestamp (NOT NULL)
128131

129-
### REQUESTS Table
130-
Stores token request status (shared with ttxdb):
131-
- `tx_id`: Transaction identifier (primary key)
132-
- `request`: Token request data
133-
- `status`: Transaction status
134-
- `status_message`: Status message
135-
- `application_metadata`: Application metadata (JSON)
136-
- `public_metadata`: Public metadata (JSON)
137-
- `pp_hash`: Public parameters hash
138-
- `stored_at`: Timestamp
132+
There is no foreign key to the Requests table — a validation record can be created and have its status tracked entirely independently of any TTXDB/AuditDB Requests row for the same `tx_id`.
139133

140134
## Relationship with TTXDB
141135

142-
The endorserdb was created by extracting validation-related functionality from the Token Transaction Database (ttxdb). While both services share the same physical database and some tables (like REQUESTS), they provide separate interfaces:
136+
The endorserdb was created by extracting validation-related functionality from the Token Transaction Database (ttxdb). Both services share the same physical database, but each owns its own table (ttxdb owns Requests/Transactions/Movements/Endorsements, endorserdb owns Validations) and interface:
143137

144138
- **ttxdb**: Manages token transactions, movements, and token requests
145139
- **endorserdb**: Manages validation records and their status

0 commit comments

Comments
 (0)