Skip to content

Commit 341f003

Browse files
committed
staticaddr: deposit timeout fsm and timeout actions
1 parent 6035e82 commit 341f003

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

staticaddr/actions.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package staticaddr
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/btcsuite/btcd/wire"
8+
"github.com/lightninglabs/lndclient"
9+
"github.com/lightninglabs/loop/fsm"
10+
"github.com/lightninglabs/loop/staticaddr/script"
11+
)
12+
13+
const (
14+
defaultConfTarget = 3
15+
)
16+
17+
// SweepExpiredDepositAction ...
18+
func (f *FSM) SweepExpiredDepositAction(
19+
eventCtx fsm.EventContext) fsm.EventType {
20+
21+
depoCtx, ok := eventCtx.(*DepositContext)
22+
if !ok {
23+
return f.HandleError(fsm.ErrInvalidContextType)
24+
}
25+
f.Debugf("SweepExpiredDepositAction: %v", depoCtx)
26+
27+
msgTx := wire.NewMsgTx(2)
28+
29+
msgTx.AddTxIn(&wire.TxIn{
30+
PreviousOutPoint: f.deposit.OutPoint,
31+
Sequence: f.addressParameters.Expiry,
32+
SignatureScript: nil,
33+
})
34+
35+
// Estimate the fee rate of an expiry spend transaction.
36+
feeRateEstimator, err := f.cfg.Lnd.WalletKit.EstimateFeeRate(
37+
f.ctx, defaultConfTarget,
38+
)
39+
if err != nil {
40+
return f.HandleError(fmt.Errorf("timeout sweep fee "+
41+
"estimation failed: %v", err))
42+
}
43+
44+
weight := script.ExpirySpendWeight()
45+
46+
fee := feeRateEstimator.FeeForWeight(weight)
47+
48+
// We cap the fee at 20% of the deposit value.
49+
if fee > f.deposit.Value/5 {
50+
return f.HandleError(errors.New("fee is higher than 20% of " +
51+
"deposit value"))
52+
}
53+
54+
output := &wire.TxOut{
55+
Value: int64(f.deposit.Value - fee),
56+
PkScript: f.deposit.TimeOutSweepPkScript,
57+
}
58+
msgTx.AddTxOut(output)
59+
60+
signDesc, err := f.SignDescriptor()
61+
if err != nil {
62+
return f.HandleError(err)
63+
}
64+
65+
txOut := &wire.TxOut{
66+
Value: int64(f.deposit.Value),
67+
PkScript: f.addressParameters.PkScript,
68+
}
69+
70+
prevOut := []*wire.TxOut{txOut}
71+
72+
rawSigs, err := f.cfg.Lnd.Signer.SignOutputRaw(
73+
f.ctx, msgTx, []*lndclient.SignDescriptor{&signDesc}, prevOut,
74+
)
75+
if err != nil {
76+
return f.HandleError(err)
77+
}
78+
79+
sig := rawSigs[0]
80+
msgTx.TxIn[0].Witness, err = f.staticAddress.GenTimeoutWitness(sig)
81+
if err != nil {
82+
return f.HandleError(err)
83+
}
84+
85+
err = f.cfg.Lnd.WalletKit.PublishTransaction(
86+
f.ctx, msgTx, f.deposit.OutPoint.Hash.String()+"-close-sweep",
87+
)
88+
if err != nil {
89+
return f.HandleError(err)
90+
}
91+
92+
f.Debugf("publishing timeout sweep with txid: %v", msgTx.TxHash())
93+
94+
return OnSwept
95+
}
96+
97+
// FinalizeDeposit is the first action that is executed when the instant
98+
// out FSM is started. It will send the instant out request to the server.
99+
func (f *FSM) FinalizeDeposit(eventCtx fsm.EventContext) fsm.EventType {
100+
101+
depoCtx, ok := eventCtx.(*DepositContext)
102+
if !ok {
103+
return f.HandleError(fsm.ErrInvalidContextType)
104+
}
105+
106+
f.Debugf("FinalizeDeposit: %v", depoCtx)
107+
108+
return fsm.NoOp
109+
}

staticaddr/fsm.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package staticaddr
2+
3+
import (
4+
"context"
5+
6+
"github.com/btcsuite/btcd/chaincfg"
7+
"github.com/btcsuite/btcd/txscript"
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+
"github.com/lightningnetwork/lnd/input"
13+
"github.com/lightningnetwork/lnd/keychain"
14+
)
15+
16+
// DepositContext contains the context parameters for a deposit state machine.
17+
type DepositContext struct {
18+
Deposit *Deposit
19+
}
20+
21+
// States.
22+
var (
23+
Deposited = fsm.StateType("Deposited")
24+
SweepExpiredDeposit = fsm.StateType("SweepExpiredDeposit")
25+
26+
DepositFinalized = fsm.StateType("DepositFinalized")
27+
28+
Failed = fsm.StateType("InstantFailedOutFailed")
29+
)
30+
31+
// Events.
32+
var (
33+
OnStart = fsm.EventType("OnStart")
34+
OnExpiry = fsm.EventType("OnExpiry")
35+
OnSwept = fsm.EventType("OnSwept")
36+
)
37+
38+
// Config contains the services required for the instant out FSM.
39+
type Config struct {
40+
// Store is used to store the instant out.
41+
Store SqlStore
42+
43+
Lnd lndclient.LndServices
44+
45+
Manager *ManagerConfig
46+
47+
// Network is the network that is used for the swap.
48+
Network *chaincfg.Params
49+
}
50+
51+
// FSM is the state machine that handles the instant out.
52+
type FSM struct {
53+
*fsm.StateMachine
54+
55+
addressParameters *AddressParameters
56+
57+
staticAddress *script.StaticAddress
58+
59+
deposit *Deposit
60+
61+
ctx context.Context
62+
63+
// cfg contains all the services that the reservation manager needs to
64+
// operate.
65+
cfg *ManagerConfig
66+
}
67+
68+
// NewFSM creates a new state machine that can action on all static address
69+
// feature requests.
70+
func NewFSM(ctx context.Context, addressParameters *AddressParameters,
71+
staticAddress *script.StaticAddress, deposit *Deposit,
72+
cfg *ManagerConfig) (*FSM, error) {
73+
74+
depoFsm := &FSM{
75+
ctx: ctx,
76+
addressParameters: addressParameters,
77+
staticAddress: staticAddress,
78+
deposit: deposit,
79+
cfg: cfg,
80+
}
81+
82+
depoFsm.StateMachine = fsm.NewStateMachine(depoFsm.DepositStates(), 20)
83+
depoFsm.ActionEntryFunc = depoFsm.DepositEntryFunction
84+
85+
return depoFsm, nil
86+
}
87+
88+
// DepositStates returns the states a deposit can be in.
89+
func (f *FSM) DepositStates() fsm.States {
90+
return fsm.States{
91+
fsm.EmptyState: fsm.State{
92+
Transitions: fsm.Transitions{
93+
OnStart: Deposited,
94+
},
95+
Action: fsm.NoOpAction,
96+
},
97+
Deposited: fsm.State{
98+
Transitions: fsm.Transitions{
99+
OnExpiry: SweepExpiredDeposit,
100+
fsm.OnError: Failed,
101+
},
102+
Action: fsm.NoOpAction,
103+
},
104+
SweepExpiredDeposit: fsm.State{
105+
Transitions: fsm.Transitions{
106+
OnSwept: DepositFinalized,
107+
fsm.OnError: Failed,
108+
},
109+
Action: f.SweepExpiredDepositAction,
110+
},
111+
Failed: fsm.State{
112+
Action: fsm.NoOpAction,
113+
},
114+
DepositFinalized: fsm.State{
115+
Action: f.FinalizeDeposit,
116+
},
117+
}
118+
}
119+
120+
// DepositEntryFunction is called after every action and updates the reservation
121+
// in the db.
122+
func (f *FSM) DepositEntryFunction(notification fsm.Notification) {
123+
log.Debugf("DepositEntryFunction: %v", notification)
124+
}
125+
126+
// Infof logs an info message with the deposit outpoint.
127+
func (f *FSM) Infof(format string, args ...interface{}) {
128+
log.Infof(
129+
"Deposit %v: "+format,
130+
append(
131+
[]interface{}{f.deposit.OutPoint},
132+
args...,
133+
)...,
134+
)
135+
}
136+
137+
// Debugf logs a debug message with the deposit outpoint.
138+
func (f *FSM) Debugf(format string, args ...interface{}) {
139+
log.Debugf(
140+
"Deposit %v: "+format,
141+
append(
142+
[]interface{}{f.deposit.OutPoint},
143+
args...,
144+
)...,
145+
)
146+
}
147+
148+
// Errorf logs an error message with the deposit outpoint.
149+
func (f *FSM) Errorf(format string, args ...interface{}) {
150+
log.Errorf(
151+
"Deposit %v: "+format,
152+
append(
153+
[]interface{}{f.deposit.OutPoint},
154+
args...,
155+
)...,
156+
)
157+
}
158+
159+
// SignDescriptor returns the sign descriptor for the static address output.
160+
func (f *FSM) SignDescriptor() (lndclient.SignDescriptor, error) {
161+
return lndclient.SignDescriptor{
162+
WitnessScript: f.staticAddress.TimeoutLeaf.Script,
163+
KeyDesc: keychain.KeyDescriptor{
164+
PubKey: f.addressParameters.ClientPubkey,
165+
},
166+
Output: wire.NewTxOut(
167+
int64(f.deposit.Value), f.addressParameters.PkScript,
168+
),
169+
HashType: txscript.SigHashDefault,
170+
InputIndex: 0,
171+
SignMethod: input.TaprootScriptSpendSignMethod,
172+
}, nil
173+
}

0 commit comments

Comments
 (0)