Skip to content

Commit

Permalink
staticaddr: deposit timeout fsm and timeout actions
Browse files Browse the repository at this point in the history
  • Loading branch information
hieblmi committed Dec 22, 2023
1 parent 6035e82 commit 341f003
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 0 deletions.
109 changes: 109 additions & 0 deletions staticaddr/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package staticaddr

import (
"errors"
"fmt"

"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/fsm"
"github.com/lightninglabs/loop/staticaddr/script"
)

const (
defaultConfTarget = 3
)

// SweepExpiredDepositAction ...
func (f *FSM) SweepExpiredDepositAction(
eventCtx fsm.EventContext) fsm.EventType {

depoCtx, ok := eventCtx.(*DepositContext)
if !ok {
return f.HandleError(fsm.ErrInvalidContextType)
}
f.Debugf("SweepExpiredDepositAction: %v", depoCtx)

msgTx := wire.NewMsgTx(2)

msgTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: f.deposit.OutPoint,
Sequence: f.addressParameters.Expiry,
SignatureScript: nil,
})

// Estimate the fee rate of an expiry spend transaction.
feeRateEstimator, err := f.cfg.Lnd.WalletKit.EstimateFeeRate(
f.ctx, defaultConfTarget,
)
if err != nil {
return f.HandleError(fmt.Errorf("timeout sweep fee "+
"estimation failed: %v", err))
}

weight := script.ExpirySpendWeight()

fee := feeRateEstimator.FeeForWeight(weight)

// We cap the fee at 20% of the deposit value.
if fee > f.deposit.Value/5 {
return f.HandleError(errors.New("fee is higher than 20% of " +
"deposit value"))
}

output := &wire.TxOut{
Value: int64(f.deposit.Value - fee),
PkScript: f.deposit.TimeOutSweepPkScript,
}
msgTx.AddTxOut(output)

signDesc, err := f.SignDescriptor()
if err != nil {
return f.HandleError(err)
}

txOut := &wire.TxOut{
Value: int64(f.deposit.Value),
PkScript: f.addressParameters.PkScript,
}

prevOut := []*wire.TxOut{txOut}

rawSigs, err := f.cfg.Lnd.Signer.SignOutputRaw(
f.ctx, msgTx, []*lndclient.SignDescriptor{&signDesc}, prevOut,
)
if err != nil {
return f.HandleError(err)
}

sig := rawSigs[0]
msgTx.TxIn[0].Witness, err = f.staticAddress.GenTimeoutWitness(sig)
if err != nil {
return f.HandleError(err)
}

err = f.cfg.Lnd.WalletKit.PublishTransaction(
f.ctx, msgTx, f.deposit.OutPoint.Hash.String()+"-close-sweep",
)
if err != nil {
return f.HandleError(err)
}

f.Debugf("publishing timeout sweep with txid: %v", msgTx.TxHash())

return OnSwept
}

// FinalizeDeposit is the first action that is executed when the instant
// out FSM is started. It will send the instant out request to the server.
func (f *FSM) FinalizeDeposit(eventCtx fsm.EventContext) fsm.EventType {

depoCtx, ok := eventCtx.(*DepositContext)
if !ok {
return f.HandleError(fsm.ErrInvalidContextType)
}

f.Debugf("FinalizeDeposit: %v", depoCtx)

return fsm.NoOp
}
173 changes: 173 additions & 0 deletions staticaddr/fsm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package staticaddr

import (
"context"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/fsm"
"github.com/lightninglabs/loop/staticaddr/script"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)

// DepositContext contains the context parameters for a deposit state machine.
type DepositContext struct {
Deposit *Deposit
}

// States.
var (
Deposited = fsm.StateType("Deposited")
SweepExpiredDeposit = fsm.StateType("SweepExpiredDeposit")

DepositFinalized = fsm.StateType("DepositFinalized")

Failed = fsm.StateType("InstantFailedOutFailed")
)

// Events.
var (
OnStart = fsm.EventType("OnStart")
OnExpiry = fsm.EventType("OnExpiry")
OnSwept = fsm.EventType("OnSwept")
)

// Config contains the services required for the instant out FSM.
type Config struct {
// Store is used to store the instant out.
Store SqlStore

Lnd lndclient.LndServices

Manager *ManagerConfig

// Network is the network that is used for the swap.
Network *chaincfg.Params
}

// FSM is the state machine that handles the instant out.
type FSM struct {
*fsm.StateMachine

addressParameters *AddressParameters

staticAddress *script.StaticAddress

deposit *Deposit

ctx context.Context

// cfg contains all the services that the reservation manager needs to
// operate.
cfg *ManagerConfig
}

// NewFSM creates a new state machine that can action on all static address
// feature requests.
func NewFSM(ctx context.Context, addressParameters *AddressParameters,
staticAddress *script.StaticAddress, deposit *Deposit,
cfg *ManagerConfig) (*FSM, error) {

depoFsm := &FSM{
ctx: ctx,
addressParameters: addressParameters,
staticAddress: staticAddress,
deposit: deposit,
cfg: cfg,
}

depoFsm.StateMachine = fsm.NewStateMachine(depoFsm.DepositStates(), 20)
depoFsm.ActionEntryFunc = depoFsm.DepositEntryFunction

return depoFsm, nil
}

// DepositStates returns the states a deposit can be in.
func (f *FSM) DepositStates() fsm.States {
return fsm.States{
fsm.EmptyState: fsm.State{
Transitions: fsm.Transitions{
OnStart: Deposited,
},
Action: fsm.NoOpAction,
},
Deposited: fsm.State{
Transitions: fsm.Transitions{
OnExpiry: SweepExpiredDeposit,
fsm.OnError: Failed,
},
Action: fsm.NoOpAction,
},
SweepExpiredDeposit: fsm.State{
Transitions: fsm.Transitions{
OnSwept: DepositFinalized,
fsm.OnError: Failed,
},
Action: f.SweepExpiredDepositAction,
},
Failed: fsm.State{
Action: fsm.NoOpAction,
},
DepositFinalized: fsm.State{
Action: f.FinalizeDeposit,
},
}
}

// DepositEntryFunction is called after every action and updates the reservation
// in the db.
func (f *FSM) DepositEntryFunction(notification fsm.Notification) {
log.Debugf("DepositEntryFunction: %v", notification)
}

// Infof logs an info message with the deposit outpoint.
func (f *FSM) Infof(format string, args ...interface{}) {
log.Infof(
"Deposit %v: "+format,
append(
[]interface{}{f.deposit.OutPoint},
args...,
)...,
)
}

// Debugf logs a debug message with the deposit outpoint.
func (f *FSM) Debugf(format string, args ...interface{}) {
log.Debugf(
"Deposit %v: "+format,
append(
[]interface{}{f.deposit.OutPoint},
args...,
)...,
)
}

// Errorf logs an error message with the deposit outpoint.
func (f *FSM) Errorf(format string, args ...interface{}) {
log.Errorf(
"Deposit %v: "+format,
append(
[]interface{}{f.deposit.OutPoint},
args...,
)...,
)
}

// SignDescriptor returns the sign descriptor for the static address output.
func (f *FSM) SignDescriptor() (lndclient.SignDescriptor, error) {
return lndclient.SignDescriptor{
WitnessScript: f.staticAddress.TimeoutLeaf.Script,
KeyDesc: keychain.KeyDescriptor{
PubKey: f.addressParameters.ClientPubkey,
},
Output: wire.NewTxOut(
int64(f.deposit.Value), f.addressParameters.PkScript,
),
HashType: txscript.SigHashDefault,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}, nil
}

0 comments on commit 341f003

Please sign in to comment.