Skip to content

Commit 056591e

Browse files
committed
supplyverifier: rewrite supply verifier state machine
Significant rewrite of state machine events, states, and transitions. Replaces the placeholder state machine.
1 parent 9cc7019 commit 056591e

File tree

6 files changed

+573
-34
lines changed

6 files changed

+573
-34
lines changed

tapcfg/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger,
556556
Lnd: lndServices,
557557
SupplyCommitView: supplyCommitStore,
558558
SupplyTreeView: supplyTreeStore,
559+
SupplySyncer: supplySyncer,
559560
GroupFetcher: assetMintingStore,
560561
IssuanceSubscriptions: universeSyncer,
561562
DaemonAdapters: lndFsmDaemonAdapters,

universe/supplyverifier/env.go

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package supplyverifier
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/btcsuite/btcd/wire"
9+
"github.com/lightninglabs/lndclient"
810
"github.com/lightninglabs/taproot-assets/asset"
911
"github.com/lightninglabs/taproot-assets/fn"
1012
"github.com/lightninglabs/taproot-assets/mssmt"
@@ -33,10 +35,17 @@ type SupplyCommitView interface {
3335
assetSpec asset.Specifier,
3436
localIssuerOnly bool) lfn.Result[supplycommit.PreCommits]
3537

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

4150
// FetchCommitmentByOutpoint fetches a supply commitment by its outpoint
4251
// and group key. If no commitment is found, it returns
@@ -53,12 +62,6 @@ type SupplyCommitView interface {
5362
spentOutpoint wire.OutPoint) (*supplycommit.RootCommitment,
5463
error)
5564

56-
// FetchStartingCommitment fetches the very first supply commitment of
57-
// an asset group. If no commitment is found, it returns
58-
// ErrCommitmentNotFound.
59-
FetchStartingCommitment(ctx context.Context,
60-
assetSpec asset.Specifier) (*supplycommit.RootCommitment, error)
61-
6265
// InsertSupplyCommit inserts a supply commitment into the database.
6366
InsertSupplyCommit(ctx context.Context,
6467
assetSpec asset.Specifier, commit supplycommit.RootCommitment,
@@ -100,6 +103,29 @@ type Environment struct {
100103
// pre-commitments.
101104
SupplyCommitView SupplyCommitView
102105

106+
// SupplyTreeView is used to fetch supply leaves by height.
107+
SupplyTreeView SupplyTreeView
108+
109+
// AssetLookup is used to look up asset information such as asset groups
110+
// and asset metadata.
111+
AssetLookup supplycommit.AssetLookup
112+
113+
// Lnd is a collection of useful LND clients.
114+
Lnd *lndclient.LndServices
115+
116+
// GroupFetcher is used to fetch asset groups.
117+
GroupFetcher tapgarden.GroupFetcher
118+
119+
// SupplySyncer is used to retrieve supply commitments from a universe
120+
// server.
121+
SupplySyncer SupplySyncer
122+
123+
// SpendSyncDelay is the wait time after detecting a spend before
124+
// starting the sync of the corresponding supply commitment. The delay
125+
// allows the peer node to submit the new commitment to the universe
126+
// server and for it to be available for retrieval.
127+
SpendSyncDelay time.Duration
128+
103129
// ErrChan is the channel that is used to send errors to the caller.
104130
ErrChan chan<- error
105131

universe/supplyverifier/events.go

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

universe/supplyverifier/manager.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ import (
2222
const (
2323
// DefaultTimeout is the context guard default timeout.
2424
DefaultTimeout = 30 * time.Second
25+
26+
// DefaultSpendSyncDelay is the default delay to wait after a spend
27+
// notification is received before starting the sync of the
28+
// corresponding supply commitment. The delay allows the peer node to
29+
// submit the new commitment to the universe server and for it to be
30+
// available for retrieval
31+
DefaultSpendSyncDelay = 5 * time.Second
2532
)
2633

2734
// DaemonAdapters is a wrapper around the protofsm.DaemonAdapters interface
@@ -372,11 +379,20 @@ func (m *Manager) Stop() error {
372379
func (m *Manager) startAssetSM(ctx context.Context,
373380
assetSpec asset.Specifier) (*StateMachine, error) {
374381

382+
log.Infof("Starting supply verifier state machine (asset=%s)",
383+
assetSpec.String())
384+
375385
// If the state machine is not found, create a new one.
376386
env := &Environment{
377387
AssetSpec: assetSpec,
378388
Chain: m.cfg.Chain,
379389
SupplyCommitView: m.cfg.SupplyCommitView,
390+
SupplyTreeView: m.cfg.SupplyTreeView,
391+
AssetLookup: m.cfg.AssetLookup,
392+
Lnd: m.cfg.Lnd,
393+
GroupFetcher: m.cfg.GroupFetcher,
394+
SupplySyncer: m.cfg.SupplySyncer,
395+
SpendSyncDelay: DefaultSpendSyncDelay,
380396
ErrChan: m.cfg.ErrChan,
381397
QuitChan: m.Quit,
382398
}
@@ -386,9 +402,9 @@ func (m *Manager) startAssetSM(ctx context.Context,
386402

387403
fsmCfg := protofsm.StateMachineCfg[Event, *Environment]{
388404
ErrorReporter: &errorReporter,
389-
// TODO(ffranr): Set InitialState here.
390-
Env: env,
391-
Daemon: m.cfg.DaemonAdapters,
405+
InitialState: &InitState{},
406+
Env: env,
407+
Daemon: m.cfg.DaemonAdapters,
392408
}
393409
newSm := protofsm.NewStateMachine[Event, *Environment](fsmCfg)
394410

@@ -436,6 +452,9 @@ func (m *Manager) fetchStateMachine(assetSpec asset.Specifier) (*StateMachine,
436452
// the cache with a new running instance.
437453
}
438454

455+
log.Debugf("Creating new supply verifier state machine for "+
456+
"group: %x", groupKey.SerializeCompressed())
457+
439458
ctx, cancel := m.WithCtxQuitNoTimeout()
440459
defer cancel()
441460

universe/supplyverifier/states.go

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,6 @@ var (
1212
ErrInvalidStateTransition = fmt.Errorf("invalid state transition")
1313
)
1414

15-
// Event is a special interface used to create the equivalent of a sum-type, but
16-
// using a "sealed" interface.
17-
type Event interface {
18-
eventSealed()
19-
}
20-
21-
// Events is a special type constraint that enumerates all the possible protocol
22-
// events.
23-
type Events interface {
24-
}
25-
2615
// StateTransition is the StateTransition type specific to the supply verifier
2716
// state machine.
2817
type StateTransition = protofsm.StateTransition[Event, *Environment]
@@ -36,6 +25,59 @@ type State interface {
3625
String() string
3726
}
3827

28+
// InitState is the starting state of the machine. In this state we decide
29+
// whether to start syncing immediately or wait for spends before syncing.
30+
type InitState struct {
31+
}
32+
33+
// stateSealed is a special method that is used to seal the interface.
34+
func (s *InitState) stateSealed() {}
35+
36+
// IsTerminal returns true if the target state is a terminal state.
37+
func (s *InitState) IsTerminal() bool {
38+
return false
39+
}
40+
41+
// String returns the name of the state.
42+
func (s *InitState) String() string {
43+
return "InitState"
44+
}
45+
46+
// SyncVerifyState is the state where we sync proofs related to a
47+
// supply commitment transaction.
48+
type SyncVerifyState struct{}
49+
50+
// stateSealed is a special method that is used to seal the interface.
51+
func (s *SyncVerifyState) stateSealed() {}
52+
53+
// IsTerminal returns true if the target state is a terminal state.
54+
func (s *SyncVerifyState) IsTerminal() bool {
55+
return false
56+
}
57+
58+
// String returns the name of the state.
59+
func (s *SyncVerifyState) String() string {
60+
return "SyncVerifyState"
61+
}
62+
63+
// WatchOutputsState waits for one of the watched outputs to be spent.
64+
// If an output is already spent, we transition immediately.
65+
// This state avoids wasted sync polling of universe servers.
66+
type WatchOutputsState struct{}
67+
68+
// stateSealed is a special method that is used to seal the interface.
69+
func (s *WatchOutputsState) stateSealed() {}
70+
71+
// IsTerminal returns true if the target state is a terminal state.
72+
func (s *WatchOutputsState) IsTerminal() bool {
73+
return false
74+
}
75+
76+
// String returns the name of the state.
77+
func (s *WatchOutputsState) String() string {
78+
return "WatchOutputsState"
79+
}
80+
3981
// StateMachine is a state machine that handles verifying the on-chain supply
4082
// commitment for a given asset.
4183
type StateMachine = protofsm.StateMachine[Event, *Environment]
@@ -47,16 +89,6 @@ type Config = protofsm.StateMachineCfg[Event, *Environment]
4789
// FsmState is a type alias for the state of the supply verifier state machine.
4890
type FsmState = protofsm.State[Event, *Environment]
4991

50-
// FsmEvent is a type alias for the event type of the supply verifier state
51-
// machine.
52-
type FsmEvent = protofsm.EmittedEvent[Event]
53-
5492
// StateSub is a type alias for the state subscriber of the supply verifier
5593
// state machine.
5694
type StateSub = protofsm.StateSubscriber[Event, *Environment]
57-
58-
// InitEvent is the first event that is sent to the state machine.
59-
type InitEvent struct{}
60-
61-
// eventSealed is a special method that is used to seal the interface.
62-
func (i *InitEvent) eventSealed() {}

0 commit comments

Comments
 (0)