Skip to content

Commit cb2486f

Browse files
committed
WIP: enhance supply verifier state machine
1 parent 9c4ab52 commit cb2486f

File tree

6 files changed

+605
-57
lines changed

6 files changed

+605
-57
lines changed

universe/supplyverifier/README.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Supply Verifier
2+
3+
The Supply Verifier package provides components to ensure the integrity of an
4+
asset's supply. It includes a supply syncer for an asset issuer to publish
5+
cryptographic commitments of the asset's total supply, and a verifier for
6+
other network participants to validate these commitments. This document outlines
7+
the core workflows for publishing, syncing, and verifying supply commitments.
8+
9+
## Core Concepts
10+
11+
* **Supply Commitment:** A cryptographic proof representing the total supply of
12+
an asset at a specific point in time. This commitment is published by the
13+
asset issuer and anchored into an on-chain Bitcoin transaction.
14+
* **Issuer:** The node responsible for creating and issuing an asset. It is the
15+
only entity that can publish supply commitments for that asset.
16+
* **Universe:** A designated node (or set of nodes) that acts as a repository
17+
for supply commitments, making them available to the rest of the network.
18+
* **User / Verifier:** Any non-issuer node that wants to independently verify
19+
the supply of an asset.
20+
21+
## Workflows
22+
23+
There are two primary workflows: publishing a new commitment and syncing
24+
existing commitments.
25+
26+
### 1\. Publishing a Supply Commitment
27+
28+
This flow is managed by the supply commit state machine on the Issuer's
29+
node. The `supplyverifier` package provides the supply syncer component, which is
30+
used by the state machine to push the commitment proof to the default universe
31+
server as well as the canonical list of universe servers specified in the
32+
asset's metadata.
33+
34+
1. The supply commit state machine waits for the on-chain commitment
35+
transaction to be broadcast and receive sufficient confirmations.
36+
2. Before the supply commitment creation process is considered final, the state
37+
machine uses the supply syncer to push the new supply commitment proof to the
38+
Universe for storage and distribution.
39+
40+
#### Call Flow:
41+
42+
* **Issuer Side (Push to Universe):**
43+
* The process starts in the `supplycommit.Manager`, which manages and
44+
multiplexes across all asset-group-specific supply commit state machines.
45+
* It calls `supplySyncer.PushSupplyCommitment` to transmit the on-chain data
46+
and supply tree proofs for the supply commitment to the designated
47+
universe server(s).
48+
* **Universe Side (Receiving the Commitment):**
49+
* The request is received by `rpcserver.InsertSupplyCommitment`.
50+
* The server then passes the commitment to
51+
`supplyverify.Manager.InsertSupplyCommit`.
52+
* Finally, the commitment is stored locally via
53+
`supplyCommitView.InsertSupplyCommit`.
54+
55+
```text
56+
Issuer Node Universe Node
57+
| |
58+
[supplycommit.Manager] |
59+
| |
60+
v |
61+
[supplySyncer.PushSupplyCommitment] -------> [rpcserver.InsertSupplyCommitment]
62+
|
63+
v
64+
[supplyverify.Manager.InsertSupplyCommit]
65+
|
66+
v
67+
[supplyCommitView.InsertSupplyCommit]
68+
```
69+
70+
### 2\. Syncing Supply Commitments
71+
72+
This flow is initiated by a User (a non-issuer node) who wants to verify
73+
the supply of a particular asset.
74+
75+
1. The User enables issuance syncing for the target asset.
76+
2. The issuance syncer fetches the asset's on-chain data and publishes an
77+
event to the supply verifier.
78+
3. The supply verifier state machine begins the process of syncing supply
79+
commitments from the Universe.
80+
81+
#### Call Flow:
82+
83+
* **User Side (Fetch from Universe):**
84+
* The sync process is kicked off by the `supplyverify.Manager.SyncSupplyCommit`
85+
method. This can be triggered in two ways: either when the supply verifier
86+
receives a notification from the issuance syncer, or when a known supply
87+
commitment transaction output is spent. The user's node monitors all
88+
on-chain supply commitment and pre-commitment outputs; the expenditure of
89+
these outputs serves as a signal that a new synchronization is necessary
90+
for the corresponding asset group.
91+
* It first calls `supplyCommitView.FetchSupplyCommit` to find the last
92+
commitment it has stored locally.
93+
* It then calls `supplySyncer.FetchSupplyCommit`, which makes an RPC call via
94+
`rpc.FetchSupplyCommit` to the Universe.
95+
* **Universe Side (Serving the Commitment):**
96+
* The request is received by `rpcserver.FetchSupplyCommit`.
97+
* The server retrieves the requested commitment(s) via
98+
`supplyverify.Manager.FetchSupplyCommit`.
99+
* The data is read from local storage using
100+
`supplyCommitView.FetchSupplyCommit` and sent back to the User.
101+
102+
```text
103+
User/Verifier Node Universe Node
104+
| |
105+
[supplyverify.Manager.SyncSupplyCommit] |
106+
| |
107+
v |
108+
[supplyCommitView.FetchSupplyCommit] | (Finds last known local commit)
109+
| |
110+
v |
111+
[supplySyncer.FetchSupplyCommit] |
112+
| |
113+
v |
114+
[rpc.FetchSupplyCommit] -------------------> [rpcserver.FetchSupplyCommit]
115+
|
116+
v
117+
[supplyverify.Manager.FetchSupplyCommit]
118+
|
119+
v
120+
[supplyCommitView.FetchSupplyCommit]

universe/supplyverifier/env.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,17 @@ type SupplyCommitView interface {
3232
UnspentPrecommits(ctx context.Context,
3333
assetSpec asset.Specifier) lfn.Result[supplycommit.PreCommits]
3434

35-
// SupplyCommit returns the latest supply commitment for a given asset
36-
// spec.
37-
SupplyCommit(ctx context.Context,
38-
assetSpec asset.Specifier) supplycommit.RootCommitResp
35+
// FetchStartingCommitment fetches the very first supply commitment of
36+
// an asset group. If no commitment is found, it returns
37+
// ErrCommitmentNotFound.
38+
FetchStartingCommitment(ctx context.Context,
39+
assetSpec asset.Specifier) (*supplycommit.RootCommitment, error)
40+
41+
// FetchLatestCommitment fetches the latest supply commitment of an
42+
// asset group. If no commitment is found, it returns
43+
// ErrCommitmentNotFound.
44+
FetchLatestCommitment(ctx context.Context,
45+
assetSpec asset.Specifier) (*supplycommit.RootCommitment, error)
3946

4047
// FetchCommitmentByOutpoint fetches a supply commitment by its outpoint
4148
// and group key. If no commitment is found, it returns
@@ -52,12 +59,6 @@ type SupplyCommitView interface {
5259
spentOutpoint wire.OutPoint) (*supplycommit.RootCommitment,
5360
error)
5461

55-
// FetchStartingCommitment fetches the very first supply commitment of
56-
// an asset group. If no commitment is found, it returns
57-
// ErrCommitmentNotFound.
58-
FetchStartingCommitment(ctx context.Context,
59-
assetSpec asset.Specifier) (*supplycommit.RootCommitment, error)
60-
6162
// InsertSupplyCommit inserts a supply commitment into the database.
6263
InsertSupplyCommit(ctx context.Context,
6364
assetSpec asset.Specifier, commit supplycommit.RootCommitment,
@@ -99,6 +100,17 @@ type Environment struct {
99100
// pre-commitments.
100101
SupplyCommitView SupplyCommitView
101102

103+
// SupplyTreeView is used to fetch supply leaves by height.
104+
SupplyTreeView SupplyTreeView
105+
106+
// AssetLookup is used to look up asset information such as asset groups
107+
// and asset metadata.
108+
AssetLookup supplycommit.AssetLookup
109+
110+
// SupplySyncer is used to retrieve supply commitments from a universe
111+
// server.
112+
SupplySyncer SupplySyncer
113+
102114
// ErrChan is the channel that is used to send errors to the caller.
103115
ErrChan chan<- error
104116

universe/supplyverifier/events.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package supplyverifier
2+
3+
import (
4+
"github.com/btcsuite/btcd/wire"
5+
"github.com/lightninglabs/taproot-assets/fn"
6+
"github.com/lightninglabs/taproot-assets/universe/supplycommit"
7+
"github.com/lightningnetwork/lnd/chainntnfs"
8+
"github.com/lightningnetwork/lnd/protofsm"
9+
)
10+
11+
// Event is a special interface used to create the equivalent of a sum-type, but
12+
// using a "sealed" interface.
13+
type Event interface {
14+
eventSealed()
15+
}
16+
17+
// Events is a special type constraint that enumerates all the possible protocol
18+
// events.
19+
type Events interface {
20+
}
21+
22+
// FsmEvent is a type alias for the event type of the supply verifier state
23+
// machine.
24+
type FsmEvent = protofsm.EmittedEvent[Event]
25+
26+
// InitEvent is the first event that is sent to the state machine.
27+
type InitEvent struct{}
28+
29+
// eventSealed is a special method that is used to seal the interface.
30+
func (i *InitEvent) eventSealed() {}
31+
32+
// SyncVerifyEvent is sent to SyncVerifyState to prompt it to sync-verify
33+
// starting from the given outpoint, or from scratch if no outpoint is given.
34+
type SyncVerifyEvent struct {
35+
// SpentCommitOutpoint is an optional outpoint that was spent which
36+
// triggered the need to start syncing from the beginning. If this is
37+
// None, then we will sync from the first supply commitment.
38+
SpentCommitOutpoint fn.Option[wire.OutPoint]
39+
}
40+
41+
// eventSealed is a special method that is used to seal the interface.
42+
func (e *SyncVerifyEvent) eventSealed() {}
43+
44+
// WatchOutputsEvent is an event that carries the set of outputs to watch.
45+
type WatchOutputsEvent struct {
46+
// PreCommits is the set of all pre-commitments that should be watched
47+
// for a spend.
48+
PreCommits supplycommit.PreCommits
49+
50+
// SupplyCommit is the latest known supply commitment that should be
51+
// watched for a spend.
52+
SupplyCommit *supplycommit.RootCommitment
53+
}
54+
55+
// eventSealed is a special method that is used to seal the interface.
56+
func (e *WatchOutputsEvent) eventSealed() {}
57+
58+
// SpendEvent is sent in response to an intent to be notified of a spend of an
59+
// outpoint.
60+
type SpendEvent struct {
61+
// SpendDetail is the details of the spend that was observed on-chain.
62+
SpendDetail *chainntnfs.SpendDetail
63+
64+
// PreCommitments is the set of all pre-commitments that were being
65+
// watched for a spend.
66+
PreCommitments []supplycommit.PreCommitment
67+
68+
// SpentPreCommitment is the pre-commitment that was spent. This will
69+
// be non-nil only if the spent output was a pre-commitment.
70+
SpentPreCommitment *supplycommit.PreCommitment
71+
72+
// SpentSupplyCommitment is the supply commitment that was spent. This
73+
// will be non-nil only if the spent output was a supply commitment.
74+
SpentSupplyCommitment *supplycommit.RootCommitment
75+
}
76+
77+
// eventSealed is a special method that is used to seal the interface.
78+
func (s *SpendEvent) eventSealed() {}

universe/supplyverifier/manager.go

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,6 @@ type DaemonAdapters interface {
3838
Stop() error
3939
}
4040

41-
// StateMachineStore is an interface that allows the state machine to persist
42-
// its state across restarts. This is used to track the state of the state
43-
// machine for supply verification.
44-
type StateMachineStore interface {
45-
// CommitState is used to commit the state of the state machine to disk.
46-
CommitState(context.Context, asset.Specifier, State) error
47-
48-
// FetchState attempts to fetch the state of the state machine for the
49-
// target asset specifier. If the state machine doesn't exist, then a
50-
// default state will be returned.
51-
FetchState(context.Context, asset.Specifier) (State, error)
52-
}
53-
5441
// IssuanceSubscriptions allows verifier state machines to subscribe to
5542
// asset group issuance events.
5643
type IssuanceSubscriptions interface {
@@ -90,11 +77,6 @@ type ManagerCfg struct {
9077
// interact with external daemons whilst processing internal events.
9178
DaemonAdapters DaemonAdapters
9279

93-
// StateLog is the main state log that is used to track the state of the
94-
// state machine. This is used to persist the state of the state machine
95-
// across restarts.
96-
StateLog StateMachineStore
97-
9880
// ErrChan is the channel that is used to send errors to the caller.
9981
ErrChan chan<- error
10082
}
@@ -238,23 +220,18 @@ func (m *Manager) startAssetSM(ctx context.Context,
238220
AssetSpec: assetSpec,
239221
Chain: m.cfg.Chain,
240222
SupplyCommitView: m.cfg.SupplyCommitView,
223+
SupplyTreeView: m.cfg.SupplyTreeView,
224+
SupplySyncer: m.cfg.SupplySyncer,
241225
ErrChan: m.cfg.ErrChan,
242226
QuitChan: m.Quit,
243227
}
244228

245-
// Before we start the state machine, we'll need to fetch the current
246-
// state from disk, to see if we need to emit any new events.
247-
initialState, err := m.cfg.StateLog.FetchState(ctx, assetSpec)
248-
if err != nil {
249-
return nil, fmt.Errorf("unable to fetch current state: %w", err)
250-
}
251-
252229
// Create a new error reporter for the state machine.
253230
errorReporter := NewErrorReporter(assetSpec)
254231

255232
fsmCfg := protofsm.StateMachineCfg[Event, *Environment]{
256233
ErrorReporter: &errorReporter,
257-
InitialState: initialState,
234+
InitialState: &InitState{},
258235
Env: env,
259236
Daemon: m.cfg.DaemonAdapters,
260237
}

0 commit comments

Comments
 (0)