Skip to content

Commit 4b012b6

Browse files
committed
staticaddr: deposit manager and fsm
1 parent 5b18aab commit 4b012b6

File tree

6 files changed

+1062
-33
lines changed

6 files changed

+1062
-33
lines changed

staticaddr/manager.go renamed to staticaddr/address/manager.go

Lines changed: 71 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package staticaddr
1+
package address
22

33
import (
44
"bytes"
@@ -10,8 +10,9 @@ import (
1010
"github.com/btcsuite/btcd/btcec/v2/schnorr"
1111
"github.com/btcsuite/btcd/btcutil"
1212
"github.com/btcsuite/btcd/chaincfg"
13+
"github.com/btcsuite/btclog"
1314
"github.com/lightninglabs/lndclient"
14-
"github.com/lightninglabs/loop"
15+
"github.com/lightninglabs/loop/staticaddr"
1516
"github.com/lightninglabs/loop/staticaddr/script"
1617
"github.com/lightninglabs/loop/swap"
1718
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
@@ -20,18 +21,22 @@ import (
2021
"github.com/lightningnetwork/lnd/lnwallet"
2122
)
2223

24+
var (
25+
log btclog.Logger
26+
)
27+
2328
// ManagerConfig holds the configuration for the address manager.
2429
type ManagerConfig struct {
2530
// AddressClient is the client that communicates with the loop server
2631
// to manage static addresses.
2732
AddressClient staticaddressrpc.StaticAddressServerClient
2833

29-
// SwapClient provides loop rpc functionality.
30-
SwapClient *loop.Client
34+
// FetchL402 is the function used to fetch the l402 token.
35+
FetchL402 func(context.Context) error
3136

3237
// Store is the database store that is used to store static address
3338
// related records.
34-
Store AddressStore
39+
Store Store
3540

3641
// WalletKit is the wallet client that is used to derive new keys from
3742
// lnd's wallet.
@@ -46,30 +51,20 @@ type ManagerConfig struct {
4651
type Manager struct {
4752
cfg *ManagerConfig
4853

49-
initChan chan struct{}
50-
5154
sync.Mutex
5255
}
5356

54-
// NewAddressManager creates a new address manager.
55-
func NewAddressManager(cfg *ManagerConfig) *Manager {
57+
// NewManager creates a new address manager.
58+
func NewManager(cfg *ManagerConfig) *Manager {
59+
log = staticaddr.GetLogger()
5660
return &Manager{
57-
cfg: cfg,
58-
initChan: make(chan struct{}),
61+
cfg: cfg,
5962
}
6063
}
6164

6265
// Run runs the address manager.
6366
func (m *Manager) Run(ctx context.Context) error {
64-
log.Debugf("Starting address manager.")
65-
defer log.Debugf("Address manager stopped.")
66-
67-
// Communicate to the caller that the address manager has completed its
68-
// initialization.
69-
close(m.initChan)
70-
7167
<-ctx.Done()
72-
7368
return nil
7469
}
7570

@@ -99,7 +94,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
9994

10095
// We are fetching a new L402 token from the server. There is one static
10196
// address per L402 token allowed.
102-
err = m.cfg.SwapClient.Server.FetchL402(ctx)
97+
err = m.cfg.FetchL402(ctx)
10398
if err != nil {
10499
return nil, err
105100
}
@@ -113,7 +108,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
113108

114109
// Send our clientPubKey to the server and wait for the server to
115110
// respond with he serverPubKey and the static address CSV expiry.
116-
protocolVersion := CurrentRPCProtocolVersion()
111+
protocolVersion := staticaddr.CurrentRPCProtocolVersion()
117112
resp, err := m.cfg.AddressClient.ServerNewAddress(
118113
ctx, &staticaddressrpc.ServerNewAddressRequest{
119114
ProtocolVersion: protocolVersion,
@@ -146,7 +141,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
146141

147142
// Create the static address from the parameters the server provided and
148143
// store all parameters in the database.
149-
addrParams := &AddressParameters{
144+
addrParams := &Parameters{
150145
ClientPubkey: clientPubKey.PubKey,
151146
ServerPubkey: serverPubKey,
152147
PkScript: pkScript,
@@ -155,7 +150,9 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
155150
Family: clientPubKey.Family,
156151
Index: clientPubKey.Index,
157152
},
158-
ProtocolVersion: AddressProtocolVersion(protocolVersion),
153+
ProtocolVersion: staticaddr.AddressProtocolVersion(
154+
protocolVersion,
155+
),
159156
}
160157
err = m.cfg.Store.CreateStaticAddress(ctx, addrParams)
161158
if err != nil {
@@ -172,7 +169,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
172169
return nil, err
173170
}
174171

175-
log.Infof("imported static address taproot script to lnd wallet: %v",
172+
log.Infof("Imported static address taproot script to lnd wallet: %v",
176173
addr)
177174

178175
return m.getTaprootAddress(
@@ -197,12 +194,6 @@ func (m *Manager) getTaprootAddress(clientPubkey,
197194
)
198195
}
199196

200-
// WaitInitComplete waits until the address manager has completed its setup.
201-
func (m *Manager) WaitInitComplete() {
202-
defer log.Debugf("Address manager initiation complete.")
203-
<-m.initChan
204-
}
205-
206197
// ListUnspentRaw returns a list of utxos at the static address.
207198
func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
208199
maxConfs int32) (*btcutil.AddressTaproot, []*lnwallet.Utxo, error) {
@@ -213,7 +204,7 @@ func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
213204
return nil, nil, err
214205

215206
case len(addresses) == 0:
216-
return nil, nil, fmt.Errorf("no address found")
207+
return nil, nil, nil
217208

218209
case len(addresses) > 1:
219210
return nil, nil, fmt.Errorf("more than one address found")
@@ -249,3 +240,52 @@ func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
249240

250241
return taprootAddress, filteredUtxos, nil
251242
}
243+
244+
// GetStaticAddressParameters returns the parameters of the static address.
245+
func (m *Manager) GetStaticAddressParameters(ctx context.Context) (*Parameters,
246+
error) {
247+
248+
params, err := m.cfg.Store.GetAllStaticAddresses(ctx)
249+
if err != nil {
250+
return nil, err
251+
}
252+
253+
if len(params) == 0 {
254+
return nil, fmt.Errorf("no static address parameters found")
255+
}
256+
257+
return params[0], nil
258+
}
259+
260+
// GetStaticAddress returns a taproot address for the given client and server
261+
// public keys and expiry.
262+
func (m *Manager) GetStaticAddress(ctx context.Context) (*script.StaticAddress,
263+
error) {
264+
265+
params, err := m.GetStaticAddressParameters(ctx)
266+
if err != nil {
267+
return nil, err
268+
}
269+
270+
address, err := script.NewStaticAddress(
271+
input.MuSig2Version100RC2, int64(params.Expiry),
272+
params.ClientPubkey, params.ServerPubkey,
273+
)
274+
if err != nil {
275+
return nil, err
276+
}
277+
278+
return address, nil
279+
}
280+
281+
// ListUnspent returns a list of utxos at the static address.
282+
func (m *Manager) ListUnspent(ctx context.Context, minConfs,
283+
maxConfs int32) ([]*lnwallet.Utxo, error) {
284+
285+
_, utxos, err := m.ListUnspentRaw(ctx, minConfs, maxConfs)
286+
if err != nil {
287+
return nil, err
288+
}
289+
290+
return utxos, nil
291+
}

staticaddr/deposit/actions.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package deposit
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/btcsuite/btcd/wire"
9+
"github.com/lightninglabs/lndclient"
10+
"github.com/lightninglabs/loop/fsm"
11+
"github.com/lightninglabs/loop/staticaddr/script"
12+
)
13+
14+
const (
15+
defaultConfTarget = 3
16+
)
17+
18+
// PublishDepositExpirySweepAction creates and publishes the timeout transaction
19+
// that spends the deposit from the static address timeout leaf to the
20+
// predefined timeout sweep pkscript.
21+
func (f *FSM) PublishDepositExpirySweepAction(_ fsm.EventContext) fsm.EventType {
22+
msgTx := wire.NewMsgTx(2)
23+
24+
params, err := f.cfg.AddressManager.GetStaticAddressParameters(f.ctx)
25+
if err != nil {
26+
return fsm.OnError
27+
}
28+
29+
// Add the deposit outpoint as input to the transaction.
30+
msgTx.AddTxIn(&wire.TxIn{
31+
PreviousOutPoint: f.deposit.OutPoint,
32+
Sequence: params.Expiry,
33+
SignatureScript: nil,
34+
})
35+
36+
// Estimate the fee rate of an expiry spend transaction.
37+
feeRateEstimator, err := f.cfg.WalletKit.EstimateFeeRate(
38+
f.ctx, defaultConfTarget,
39+
)
40+
if err != nil {
41+
return f.HandleError(fmt.Errorf("timeout sweep fee "+
42+
"estimation failed: %v", err))
43+
}
44+
45+
weight := script.ExpirySpendWeight()
46+
47+
fee := feeRateEstimator.FeeForWeight(weight)
48+
49+
// We cap the fee at 20% of the deposit value.
50+
if fee > f.deposit.Value/5 {
51+
return f.HandleError(errors.New("fee is greater than 20% of " +
52+
"the deposit value"))
53+
}
54+
55+
output := &wire.TxOut{
56+
Value: int64(f.deposit.Value - fee),
57+
PkScript: f.deposit.TimeOutSweepPkScript,
58+
}
59+
msgTx.AddTxOut(output)
60+
61+
txOut := &wire.TxOut{
62+
Value: int64(f.deposit.Value),
63+
PkScript: params.PkScript,
64+
}
65+
66+
prevOut := []*wire.TxOut{txOut}
67+
68+
signDesc, err := f.SignDescriptor()
69+
if err != nil {
70+
return f.HandleError(err)
71+
}
72+
73+
rawSigs, err := f.cfg.Signer.SignOutputRaw(
74+
f.ctx, msgTx, []*lndclient.SignDescriptor{signDesc}, prevOut,
75+
)
76+
if err != nil {
77+
return f.HandleError(err)
78+
}
79+
80+
address, err := f.cfg.AddressManager.GetStaticAddress(f.ctx)
81+
if err != nil {
82+
return f.HandleError(err)
83+
}
84+
85+
sig := rawSigs[0]
86+
msgTx.TxIn[0].Witness, err = address.GenTimeoutWitness(sig)
87+
if err != nil {
88+
return f.HandleError(err)
89+
}
90+
91+
txLabel := fmt.Sprintf("timeout sweep for deposit %v",
92+
f.deposit.OutPoint)
93+
94+
err = f.cfg.WalletKit.PublishTransaction(f.ctx, msgTx, txLabel)
95+
if err != nil {
96+
if !strings.Contains(err.Error(), "output already spent") {
97+
log.Errorf("%v: %v", txLabel, err)
98+
f.LastActionError = err
99+
return fsm.OnError
100+
}
101+
} else {
102+
f.Debugf("published timeout sweep with txid: %v",
103+
msgTx.TxHash())
104+
}
105+
106+
return OnExpiryPublished
107+
}
108+
109+
// WaitForExpirySweepAction waits for a sufficient number of confirmations
110+
// before a timeout sweep is considered successful.
111+
func (f *FSM) WaitForExpirySweepAction(_ fsm.EventContext) fsm.EventType {
112+
spendChan, errSpendChan, err := f.cfg.ChainNotifier.RegisterConfirmationsNtfn( //nolint:lll
113+
f.ctx, nil, f.deposit.TimeOutSweepPkScript, defaultConfTarget,
114+
int32(f.deposit.ConfirmationHeight),
115+
)
116+
if err != nil {
117+
return f.HandleError(err)
118+
}
119+
120+
select {
121+
case err := <-errSpendChan:
122+
log.Debugf("error while sweeping expired deposit: %v", err)
123+
return fsm.OnError
124+
125+
case confirmedTx := <-spendChan:
126+
f.deposit.ExpirySweepTxid = confirmedTx.Tx.TxHash()
127+
return OnExpirySwept
128+
129+
case <-f.ctx.Done():
130+
return fsm.OnError
131+
}
132+
}
133+
134+
// SweptExpiredDepositAction is the final action of the FSM. It signals to the
135+
// manager that the deposit has been swept and the FSM can be removed. It also
136+
// ends the state machine main loop by cancelling its context.
137+
func (f *FSM) SweptExpiredDepositAction(_ fsm.EventContext) fsm.EventType {
138+
select {
139+
case <-f.ctx.Done():
140+
return fsm.OnError
141+
142+
default:
143+
f.finalizedDepositChan <- f.deposit.OutPoint
144+
f.ctx.Done()
145+
}
146+
147+
return fsm.NoOp
148+
}

0 commit comments

Comments
 (0)