Skip to content

Commit 35add2f

Browse files
authored
Merge pull request #1792 from Roasbeef/supply-verify-logging
universe/supplyverifier: add logging and readme
2 parents 35fa961 + 525d0e7 commit 35add2f

File tree

8 files changed

+475
-7
lines changed

8 files changed

+475
-7
lines changed

supplysync_rpc.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ func (r *RpcSupplySync) FetchSupplyCommit(ctx context.Context,
128128
return zero, fmt.Errorf("unable to unwrap group key: %w", err)
129129
}
130130

131+
srvrLog.Infof("[RpcSupplySync.FetchSupplyCommit]: fetching supply "+
132+
"commitment from remote server "+
133+
"(server_addr=%s, asset=%s, spent_outpoint=%v)",
134+
r.serverAddr.HostStr(), assetSpec.String(),
135+
spentCommitOutpoint.IsSome())
136+
131137
req := &unirpc.FetchSupplyCommitRequest{
132138
GroupKey: &unirpc.FetchSupplyCommitRequest_GroupKeyBytes{
133139
GroupKeyBytes: groupKey.SerializeCompressed(),
@@ -140,6 +146,8 @@ func (r *RpcSupplySync) FetchSupplyCommit(ctx context.Context,
140146
// If a spent commit outpoint is provided, use that to locate the next
141147
// supply commitment.
142148
spentCommitOutpoint.WhenSome(func(outpoint wire.OutPoint) {
149+
srvrLog.Debugf("Using spent commitment outpoint as locator: %s",
150+
outpoint.String())
143151
// nolint: lll
144152
req.Locator = &unirpc.FetchSupplyCommitRequest_SpentCommitOutpoint{
145153
SpentCommitOutpoint: &taprpc.OutPoint{
@@ -222,7 +230,7 @@ func (r *RpcSupplySync) FetchSupplyCommit(ctx context.Context,
222230
"root: %w", err)
223231
}
224232

225-
return supplycommit.FetchSupplyCommitResult{
233+
result := supplycommit.FetchSupplyCommitResult{
226234
RootCommitment: *rootCommitment,
227235
SupplyLeaves: *supplyLeaves,
228236
ChainProof: chainProof,
@@ -233,7 +241,15 @@ func (r *RpcSupplySync) FetchSupplyCommit(ctx context.Context,
233241
IgnoreSubtreeRoot: ignoreSubtreeRoot,
234242

235243
SpentCommitmentOutpoint: respSpentCommitOutpoint,
236-
}, nil
244+
}
245+
246+
srvrLog.Infof("[RpcSupplySync.FetchSupplyCommit]: succeeded in "+
247+
"fetching supply commitment "+
248+
"(server_addr=%s, asset=%s, supply_tree_root_hash=%x)",
249+
r.serverAddr.HostStr(), assetSpec.String(),
250+
rootCommitment.SupplyRoot.NodeHash())
251+
252+
return result, nil
237253
}
238254

239255
// Close closes the RPC connection to the universe server.

universe/supplyverifier/README.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Universe Supply Verifier State Machine
2+
3+
This package implements a state machine responsible for verifying and tracking
4+
on-chain supply commitments for Taproot Assets created by other nodes in the
5+
network. It acts as the counterpart to the supply commitment state machine,
6+
ensuring the integrity and consistency of supply data across the distributed
7+
Universe network.
8+
9+
## Rationale and Purpose
10+
11+
While the supply commitment state machine creates and publishes supply commitments
12+
for assets where a node owns the delegation key, the supply verifier ensures that
13+
commitments created by other nodes are properly validated and tracked. This
14+
creates a trustless, verifiable system for tracking asset supply across the
15+
entire network.
16+
17+
The verifier's primary objectives are:
18+
19+
1. **Monitor Pre-commitments and Supply Commitments:** Track unspent outputs that
20+
represent either initial pre-commitment outputs from asset issuance or existing
21+
supply commitment outputs, watching for when they are spent.
22+
23+
2. **Detect Supply Updates:** When a watched output is spent, recognize this as a
24+
signal that a new supply commitment has been created and needs to be retrieved.
25+
26+
3. **Retrieve and Verify:** Pull the new supply commitment from Universe servers,
27+
including all associated supply leaves (mints, burns, ignores) and cryptographic
28+
proofs.
29+
30+
4. **Validate Chain Integrity:** Ensure each new commitment properly spends the
31+
previous commitment (or pre-commitments for initial commitments), maintaining
32+
an unbroken chain of commitments anchored to the blockchain.
33+
34+
5. **Persist Verified State:** Store verified commitments and their associated
35+
supply trees locally, enabling future verification of incremental updates and
36+
serving as a foundation for the next verification cycle.
37+
38+
## Scope
39+
40+
The verifier manages the complete lifecycle of supply commitment verification for
41+
assets where the local node does not control the delegation key. Each asset group
42+
has its own dedicated state machine instance that handles:
43+
44+
* Monitoring blockchain outputs for spend notifications
45+
* Coordinating with Universe servers to retrieve new commitments
46+
* Verifying the cryptographic integrity of supply commitments and leaves
47+
* Validating the chain of commitments back to asset issuance
48+
* Managing the transition between monitoring and syncing states
49+
* Persisting verified commitment data and supply trees
50+
51+
The verifier does *not* handle:
52+
53+
* Creating new supply commitments (handled by the supply commitment state machine)
54+
* Managing assets where the local node owns the delegation key
55+
* Direct interaction with asset issuance or transfer operations
56+
* Serving supply proofs to external clients (handled by Universe server components)
57+
58+
## Architecture Overview
59+
60+
The supply verifier consists of several cooperating components:
61+
62+
### Core Components
63+
64+
**Verifier (`verifier.go`):** The core verification engine that validates supply
65+
commitments against cryptographic proofs, ensures proper spending of previous
66+
commitments, and verifies individual supply leaves (mints, burns, ignores).
67+
68+
**State Machine (`states.go`, `transitions.go`):** Implements the verification
69+
lifecycle through a series of well-defined states, managing the flow from output
70+
monitoring through commitment retrieval to verification and persistence.
71+
72+
**Manager (`manager.go`):** Orchestrates multiple state machine instances, one per
73+
asset group, handling initialization, lifecycle management, and coordination with
74+
Universe sync events.
75+
76+
**Syncer (`syncer.go`):** Manages communication with remote Universe servers to
77+
retrieve supply commitments, handling parallel fetches and error recovery across
78+
multiple servers.
79+
80+
### Supporting Components
81+
82+
**Environment (`env.go`):** Encapsulates all dependencies required by the state
83+
machine, including chain access, supply commitment storage, tree management, and
84+
Universe server communication.
85+
86+
**Events (`events.go`):** Defines the event types that drive state transitions,
87+
including initialization, output spend notifications, and sync triggers.
88+
89+
## State Machine Overview
90+
91+
The verifier state machine manages the verification lifecycle through three primary
92+
states, transitioning based on blockchain events and sync operations. Unlike the
93+
commitment state machine which operates on a timer, the verifier is entirely
94+
event-driven, responding to on-chain activity.
95+
96+
### States and Transitions
97+
98+
```mermaid
99+
stateDiagram-v2
100+
direction TB
101+
102+
[*] --> InitState: Start
103+
104+
InitState --> WatchOutputsState: InitEvent
105+
106+
WatchOutputsState --> SyncVerifyState: SpendEvent<br/>(output spent)
107+
108+
SyncVerifyState --> WatchOutputsState: SyncVerifyEvent<br/>(commitment verified)
109+
110+
SyncVerifyState --> SyncVerifyState: SyncVerifyEvent<br/>(already synced)
111+
112+
note right of InitState
113+
Fetches pre-commitments
114+
and latest commitment
115+
from local DB
116+
end note
117+
118+
note right of WatchOutputsState
119+
Registers spend notifications
120+
for pre-commitments and
121+
current supply commitment
122+
end note
123+
124+
note right of SyncVerifyState
125+
Retrieves new commitment
126+
from Universe servers,
127+
verifies, and stores
128+
end note
129+
```
130+
131+
### State Descriptions
132+
133+
**InitState:** The entry point for each state machine instance. Queries the local
134+
database for any unspent pre-commitment outputs (from asset issuance transactions)
135+
and the latest verified supply commitment for the asset group. This state ensures
136+
the verifier has the necessary starting context before beginning active monitoring.
137+
If neither pre-commitments nor an existing commitment are found, the state machine
138+
cannot proceed, as there would be nothing to watch.
139+
140+
**WatchOutputsState:** The primary monitoring state where the verifier spends most
141+
of its time. Registers with the chain notification system to watch for spends of
142+
specific outputs: all known pre-commitment outputs and the current supply commitment
143+
output (if one exists). When any watched output is spent, this indicates a new
144+
supply commitment transaction has been broadcast. The state machine captures the
145+
spend details, including the spending transaction and block height, then transitions
146+
to retrieve and verify the new commitment.
147+
148+
**SyncVerifyState:** The active verification state triggered by output spend
149+
detection. First checks if the commitment has already been processed (avoiding
150+
duplicate work), then retrieves the commitment from configured Universe servers.
151+
The retrieval includes the full commitment transaction, all supply leaves (mints,
152+
burns, ignores), and the cryptographic proofs linking them. Once retrieved, the
153+
verifier validates the entire structure: checking that previous outputs were properly
154+
spent, verifying leaf signatures and proofs, and ensuring the merkle tree roots
155+
match. Successfully verified commitments are stored in the database along with their
156+
supply trees, becoming the new "latest commitment" for future monitoring cycles.
157+
158+
### Timing and Synchronization
159+
160+
The verifier implements intelligent timing to handle the inherent race condition
161+
between commitment creation and Universe server availability. When a spend is
162+
detected, the verifier waits a configurable delay (default 5 seconds) before
163+
attempting to sync, giving the commitment creator time to publish to Universe
164+
servers. This delay is optimized based on how long the output was watched,
165+
reducing unnecessary waiting when possible.
166+
167+
## Database Persistence
168+
169+
The verifier relies on several database interfaces for persistence and state
170+
management:
171+
172+
### SupplyCommitView
173+
174+
Provides access to supply commitments and pre-commitments:
175+
- `FetchLatestCommitment`: Retrieves the most recent verified commitment
176+
- `FetchCommitmentByOutpoint`: Checks for existing commitments at specific outpoints
177+
- `FetchCommitmentBySpentOutpoint`: Finds commitments that spend specific outputs
178+
- `UnspentPrecommits`: Lists all unspent pre-commitment outputs for monitoring
179+
- `InsertSupplyCommit`: Stores newly verified commitments
180+
181+
### SupplyTreeView
182+
183+
Manages the merkle tree structures that represent supply state:
184+
- `FetchSupplyTrees`: Retrieves the current root tree and subtrees
185+
- Tree updates are performed during commitment verification to maintain consistency
186+
187+
### Persistence Strategy
188+
189+
The verifier maintains minimal persistent state, as most information can be
190+
reconstructed from the blockchain and stored commitments. The key persisted
191+
elements are:
192+
193+
1. **Verified Commitments:** Each successfully verified commitment is stored with
194+
its transaction details, merkle root, and relationship to previous commitments.
195+
196+
2. **Supply Trees:** The merkle tree structures representing the supply state at
197+
each commitment point, enabling efficient incremental verification.
198+
199+
3. **Pre-commitment Tracking:** Records of which pre-commitment outputs have been
200+
spent, preventing double-spending in the commitment chain.
201+
202+
Unlike the commitment creator, the verifier does not need to persist state machine
203+
state itself, as it can always reconstruct its position from the latest verified
204+
commitment.
205+
206+
## Integration and Dependencies
207+
208+
The supply verifier integrates with several system components:
209+
210+
### Chain Bridge
211+
212+
Provides blockchain access for:
213+
- Header verification for proof-of-work validation
214+
- Transaction confirmation monitoring
215+
- Block height tracking for spend notifications
216+
217+
### Universe Federation
218+
219+
Coordinates with Universe servers to:
220+
- Retrieve supply commitments from multiple sources
221+
- Handle server failures gracefully with parallel fetching
222+
- Maintain consistency across the distributed network
223+
224+
### Asset Management
225+
226+
Interfaces with asset tracking systems to:
227+
- Determine which assets require verification (those without local delegation keys)
228+
- Access asset metadata including delegation keys for signature verification
229+
- Validate asset group membership for supply leaves
230+
231+
### Event System
232+
233+
Responds to system events including:
234+
- Asset issuance notifications to begin tracking new assets
235+
- Universe sync events that may indicate new commitments
236+
- Chain notifications for output spend detection
237+
238+
## Error Handling and Recovery
239+
240+
The verifier implements robust error handling to ensure verification integrity:
241+
242+
**Verification Failures:** If a commitment fails verification, it is rejected and
243+
not stored. The verifier continues watching the previously verified commitment,
244+
ensuring only valid commitments enter the local database.
245+
246+
**Network Failures:** When Universe servers are unreachable, the verifier retries
247+
with exponential backoff. Multiple servers are queried in parallel, requiring only
248+
one successful response to proceed.
249+
250+
**State Recovery:** After system restarts, each state machine reconstructs its
251+
state from the latest verified commitment in the database, automatically resuming
252+
monitoring without loss of data.
253+
254+
**Chain Reorganizations:** The verifier handles blockchain reorganizations by
255+
re-verifying commitments when their confirming blocks are reorganized away,
256+
maintaining consistency with the canonical chain.
257+
258+
## Security Considerations
259+
260+
The supply verifier enforces several security properties:
261+
262+
1. **Cryptographic Verification:** All supply leaves must have valid signatures
263+
from the delegation key holder, preventing unauthorized supply manipulation.
264+
265+
2. **Chain Integrity:** Each commitment must properly spend previous commitments
266+
or pre-commitments, creating an immutable audit trail.
267+
268+
3. **Proof Validation:** All merkle proofs are verified against their claimed roots,
269+
ensuring data integrity.
270+
271+
4. **Byzantine Fault Tolerance:** By querying multiple Universe servers and requiring
272+
only one valid response, the system tolerates malicious or faulty servers.
273+
274+
5. **No Trust Required:** The verifier independently validates all data against
275+
on-chain anchors, requiring no trust in Universe servers or other nodes.

universe/supplyverifier/env.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/btcsuite/btcd/wire"
9+
"github.com/btcsuite/btclog/v2"
910
"github.com/lightninglabs/lndclient"
1011
"github.com/lightninglabs/taproot-assets/asset"
1112
"github.com/lightninglabs/taproot-assets/fn"
@@ -96,6 +97,9 @@ type Environment struct {
9697
// that we're maintaining a supply commit for.
9798
AssetSpec asset.Specifier
9899

100+
// AssetLog is the asset-specific logger for this state machine.
101+
AssetLog btclog.Logger
102+
99103
// Chain is our access to the current main chain.
100104
Chain tapgarden.ChainBridge
101105

@@ -138,3 +142,14 @@ type Environment struct {
138142
func (e *Environment) Name() string {
139143
return fmt.Sprintf("supply_verifier(%s)", e.AssetSpec.String())
140144
}
145+
146+
// Logger returns the logger for the environment. If a logger is configured in
147+
// the environment configuration, it returns that logger. Otherwise, it returns
148+
// the package-level logger with an asset-specific prefix.
149+
func (e *Environment) Logger() btclog.Logger {
150+
if e.AssetLog != nil {
151+
return e.AssetLog
152+
}
153+
154+
return NewAssetLogger(e.AssetSpec.String())
155+
}

universe/supplyverifier/log.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package supplyverifier
22

33
import (
4+
"fmt"
5+
46
"github.com/btcsuite/btclog/v2"
57
)
68

@@ -24,3 +26,10 @@ func DisableLog() {
2426
func UseLogger(logger btclog.Logger) {
2527
log = logger
2628
}
29+
30+
// NewAssetLogger creates a new prefixed logger for a specific asset. This
31+
// logger will automatically include the asset specifier in all log messages,
32+
// using the format "SupplyVerifier(asset): message".
33+
func NewAssetLogger(assetSpec string) btclog.Logger {
34+
return log.WithPrefix(fmt.Sprintf("SupplyVerifier(%v): ", assetSpec))
35+
}

0 commit comments

Comments
 (0)