Skip to content

Commit a6da4b2

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

File tree

6 files changed

+1060
-30
lines changed

6 files changed

+1060
-30
lines changed

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

Lines changed: 69 additions & 28 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,10 @@ 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"
1415
"github.com/lightninglabs/loop"
16+
"github.com/lightninglabs/loop/staticaddr"
1517
"github.com/lightninglabs/loop/staticaddr/script"
1618
"github.com/lightninglabs/loop/swap"
1719
staticaddressrpc "github.com/lightninglabs/loop/swapserverrpc"
@@ -20,6 +22,10 @@ import (
2022
"github.com/lightningnetwork/lnd/lnwallet"
2123
)
2224

25+
var (
26+
log btclog.Logger
27+
)
28+
2329
// ManagerConfig holds the configuration for the address manager.
2430
type ManagerConfig struct {
2531
// AddressClient is the client that communicates with the loop server
@@ -31,7 +37,7 @@ type ManagerConfig struct {
3137

3238
// Store is the database store that is used to store static address
3339
// related records.
34-
Store AddressStore
40+
Store Store
3541

3642
// WalletKit is the wallet client that is used to derive new keys from
3743
// lnd's wallet.
@@ -46,31 +52,21 @@ type ManagerConfig struct {
4652
type Manager struct {
4753
cfg *ManagerConfig
4854

49-
initChan chan struct{}
50-
5155
sync.Mutex
5256
}
5357

54-
// NewAddressManager creates a new address manager.
55-
func NewAddressManager(cfg *ManagerConfig) *Manager {
58+
// NewManager creates a new address manager.
59+
func NewManager(cfg *ManagerConfig) *Manager {
60+
log = staticaddr.GetLogger()
5661
return &Manager{
57-
cfg: cfg,
58-
initChan: make(chan struct{}),
62+
cfg: cfg,
5963
}
6064
}
6165

6266
// Run runs the address manager.
6367
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-
7168
<-ctx.Done()
72-
73-
return nil
69+
return ctx.Err()
7470
}
7571

7672
// NewAddress starts a new address creation flow.
@@ -113,7 +109,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
113109

114110
// Send our clientPubKey to the server and wait for the server to
115111
// respond with he serverPubKey and the static address CSV expiry.
116-
protocolVersion := CurrentRPCProtocolVersion()
112+
protocolVersion := staticaddr.CurrentRPCProtocolVersion()
117113
resp, err := m.cfg.AddressClient.ServerNewAddress(
118114
ctx, &staticaddressrpc.ServerNewAddressRequest{
119115
ProtocolVersion: protocolVersion,
@@ -146,7 +142,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
146142

147143
// Create the static address from the parameters the server provided and
148144
// store all parameters in the database.
149-
addrParams := &AddressParameters{
145+
addrParams := &Parameters{
150146
ClientPubkey: clientPubKey.PubKey,
151147
ServerPubkey: serverPubKey,
152148
PkScript: pkScript,
@@ -155,7 +151,9 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
155151
Family: clientPubKey.Family,
156152
Index: clientPubKey.Index,
157153
},
158-
ProtocolVersion: AddressProtocolVersion(protocolVersion),
154+
ProtocolVersion: staticaddr.AddressProtocolVersion(
155+
protocolVersion,
156+
),
159157
}
160158
err = m.cfg.Store.CreateStaticAddress(ctx, addrParams)
161159
if err != nil {
@@ -172,7 +170,7 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot,
172170
return nil, err
173171
}
174172

175-
log.Infof("imported static address taproot script to lnd wallet: %v",
173+
log.Infof("Imported static address taproot script to lnd wallet: %v",
176174
addr)
177175

178176
return m.getTaprootAddress(
@@ -197,12 +195,6 @@ func (m *Manager) getTaprootAddress(clientPubkey,
197195
)
198196
}
199197

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-
206198
// ListUnspentRaw returns a list of utxos at the static address.
207199
func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
208200
maxConfs int32) (*btcutil.AddressTaproot, []*lnwallet.Utxo, error) {
@@ -213,7 +205,7 @@ func (m *Manager) ListUnspentRaw(ctx context.Context, minConfs,
213205
return nil, nil, err
214206

215207
case len(addresses) == 0:
216-
return nil, nil, fmt.Errorf("no address found")
208+
return nil, nil, nil
217209

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

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

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)