sweep: move sweep tx generation into sweep package
Sweep txes are currently generated in multiple locations. Moving the sweep tx generation into a shared package will allow us to reuse that logic.
This commit is contained in:
parent
9fcc7ee390
commit
4dab405623
10
log.go
10
log.go
@ -1,11 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"io"
|
||||
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/btcsuite/btcd/connmgr"
|
||||
@ -24,6 +22,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/routing"
|
||||
"github.com/lightningnetwork/lnd/signal"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
)
|
||||
|
||||
// Loggers per subsystem. A single backend logger is created and all subsystem
|
||||
@ -65,6 +64,7 @@ var (
|
||||
atplLog = build.NewSubLogger("ATPL", backendLog.Logger)
|
||||
cnctLog = build.NewSubLogger("CNCT", backendLog.Logger)
|
||||
sphxLog = build.NewSubLogger("SPHX", backendLog.Logger)
|
||||
swprLog = build.NewSubLogger("SWPR", backendLog.Logger)
|
||||
)
|
||||
|
||||
// Initialize package-global logger variables.
|
||||
@ -81,6 +81,7 @@ func init() {
|
||||
contractcourt.UseLogger(cnctLog)
|
||||
sphinx.UseLogger(sphxLog)
|
||||
signal.UseLogger(ltndLog)
|
||||
sweep.UseLogger(swprLog)
|
||||
}
|
||||
|
||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||
@ -103,6 +104,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
||||
"ATPL": atplLog,
|
||||
"CNCT": cnctLog,
|
||||
"SPHX": sphxLog,
|
||||
"SWPR": swprLog,
|
||||
}
|
||||
|
||||
// initLogRotator initializes the logging rotator to write logs to logFile and
|
||||
|
26
server.go
26
server.go
@ -6,6 +6,7 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
"image/color"
|
||||
"math/big"
|
||||
"net"
|
||||
@ -58,6 +59,10 @@ const (
|
||||
// durations exceeding this value will be eligible to have their
|
||||
// backoffs reduced.
|
||||
defaultStableConnDuration = 10 * time.Minute
|
||||
|
||||
// sweepTxConfirmationTarget assigns a confirmation target for sweep
|
||||
// txes on which the fee calculation will be based.
|
||||
sweepTxConfirmationTarget = 6
|
||||
)
|
||||
|
||||
var (
|
||||
@ -581,19 +586,24 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sweeper := sweep.New(&sweep.UtxoSweeperConfig{
|
||||
Estimator: cc.feeEstimator,
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
return newSweepPkScript(cc.wallet)
|
||||
},
|
||||
Signer: cc.wallet.Cfg.Signer,
|
||||
ConfTarget: sweepTxConfirmationTarget,
|
||||
})
|
||||
|
||||
s.utxoNursery = newUtxoNursery(&NurseryConfig{
|
||||
ChainIO: cc.chainIO,
|
||||
ConfDepth: 1,
|
||||
FetchClosedChannels: chanDB.FetchClosedChannels,
|
||||
FetchClosedChannel: chanDB.FetchClosedChannel,
|
||||
Estimator: cc.feeEstimator,
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
return newSweepPkScript(cc.wallet)
|
||||
},
|
||||
Notifier: cc.chainNotifier,
|
||||
PublishTransaction: cc.wallet.PublishTransaction,
|
||||
Signer: cc.wallet.Cfg.Signer,
|
||||
Store: utxnStore,
|
||||
Notifier: cc.chainNotifier,
|
||||
PublishTransaction: cc.wallet.PublishTransaction,
|
||||
Store: utxnStore,
|
||||
Sweeper: sweeper,
|
||||
})
|
||||
|
||||
// Construct a closure that wraps the htlcswitch's CloseLink method.
|
||||
|
45
sweep/log.go
Normal file
45
sweep/log.go
Normal file
@ -0,0 +1,45 @@
|
||||
package sweep
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lightningnetwork/lnd/build"
|
||||
)
|
||||
|
||||
// log is a logger that is initialized with no output filters. This
|
||||
// means the package will not perform any logging by default until the caller
|
||||
// requests it.
|
||||
var log btclog.Logger
|
||||
|
||||
// The default amount of logging is none.
|
||||
func init() {
|
||||
UseLogger(build.NewSubLogger("SWPR", nil))
|
||||
}
|
||||
|
||||
// DisableLog disables all library log output. Logging output is disabled
|
||||
// by default until UseLogger is called.
|
||||
func DisableLog() {
|
||||
UseLogger(btclog.Disabled)
|
||||
}
|
||||
|
||||
// UseLogger uses a specified Logger to output package logging info.
|
||||
// This should be used in preference to SetLogWriter if the caller is also
|
||||
// using btclog.
|
||||
func UseLogger(logger btclog.Logger) {
|
||||
log = logger
|
||||
}
|
||||
|
||||
// logClosure is used to provide a closure over expensive logging operations so
|
||||
// don't have to be performed when the logging level doesn't warrant it.
|
||||
type logClosure func() string
|
||||
|
||||
// String invokes the underlying function and returns the result.
|
||||
func (c logClosure) String() string {
|
||||
return c()
|
||||
}
|
||||
|
||||
// newLogClosure returns a new closure over a function that returns a string
|
||||
// which itself provides a Stringer interface so that it can be used with the
|
||||
// logging system.
|
||||
func newLogClosure(c func() string) logClosure {
|
||||
return logClosure(c)
|
||||
}
|
249
sweep/sweeper.go
Normal file
249
sweep/sweeper.go
Normal file
@ -0,0 +1,249 @@
|
||||
package sweep
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// UtxoSweeper provides the functionality to generate sweep txes. The plan is to
|
||||
// extend UtxoSweeper in the future to also manage the actual sweeping process
|
||||
// by itself.
|
||||
type UtxoSweeper struct {
|
||||
cfg *UtxoSweeperConfig
|
||||
}
|
||||
|
||||
// UtxoSweeperConfig contains dependencies of UtxoSweeper.
|
||||
type UtxoSweeperConfig struct {
|
||||
// GenSweepScript generates a P2WKH script belonging to the wallet where
|
||||
// funds can be swept.
|
||||
GenSweepScript func() ([]byte, error)
|
||||
|
||||
// Estimator is used when crafting sweep transactions to estimate the
|
||||
// necessary fee relative to the expected size of the sweep transaction.
|
||||
Estimator lnwallet.FeeEstimator
|
||||
|
||||
// Signer is used by the sweeper to generate valid witnesses at the
|
||||
// time the incubated outputs need to be spent.
|
||||
Signer lnwallet.Signer
|
||||
|
||||
// ConfTarget specifies a target for the number of blocks until an
|
||||
// initial confirmation.
|
||||
ConfTarget uint32
|
||||
}
|
||||
|
||||
// New returns a new UtxoSweeper instance.
|
||||
func New(cfg *UtxoSweeperConfig) *UtxoSweeper {
|
||||
return &UtxoSweeper{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateSweepTx accepts a list of outputs and signs and generates a txn that
|
||||
// spends from them. This method also makes an accurate fee estimate before
|
||||
// generating the required witnesses.
|
||||
//
|
||||
// The value of currentBlockHeight argument will be set as the tx locktime. This
|
||||
// function assumes that all CLTV inputs will be unlocked after
|
||||
// currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry
|
||||
// values of the inputs:
|
||||
//
|
||||
// - Make handling re-orgs easier.
|
||||
// - Thwart future possible fee sniping attempts.
|
||||
// - Make us blend in with the bitcoind wallet.
|
||||
func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput,
|
||||
currentBlockHeight uint32) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a transaction which sweeps all the newly mature outputs into
|
||||
// an output controlled by the wallet.
|
||||
|
||||
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
||||
// be more efficient on-chain.
|
||||
|
||||
// Assemble the inputs into a slice csv spendable outputs, and also a
|
||||
// set of regular spendable outputs. The set of regular outputs are CLTV
|
||||
// locked outputs that have had their timelocks expire.
|
||||
var (
|
||||
csvOutputs []CsvSpendableOutput
|
||||
cltvOutputs []SpendableOutput
|
||||
weightEstimate lnwallet.TxWeightEstimator
|
||||
)
|
||||
|
||||
// Allocate enough room for both types of outputs.
|
||||
csvOutputs = make([]CsvSpendableOutput, 0, len(inputs))
|
||||
cltvOutputs = make([]SpendableOutput, 0, len(inputs))
|
||||
|
||||
// Our sweep transaction will pay to a single segwit p2wkh address,
|
||||
// ensure it contributes to our weight estimate.
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
|
||||
// For each output, use its witness type to determine the estimate
|
||||
// weight of its witness, and add it to the proper set of spendable
|
||||
// outputs.
|
||||
for i := range inputs {
|
||||
input := inputs[i]
|
||||
|
||||
switch input.WitnessType() {
|
||||
|
||||
// Outputs on a past commitment transaction that pay directly
|
||||
// to us.
|
||||
case lnwallet.CommitmentTimeLock:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// Outgoing second layer HTLC's that have confirmed within the
|
||||
// chain, and the output they produced is now mature enough to
|
||||
// sweep.
|
||||
case lnwallet.HtlcOfferedTimeoutSecondLevel:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// Incoming second layer HTLC's that have confirmed within the
|
||||
// chain, and the output they produced is now mature enough to
|
||||
// sweep.
|
||||
case lnwallet.HtlcAcceptedSuccessSecondLevel:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// An HTLC on the commitment transaction of the remote party,
|
||||
// that has had its absolute timelock expire.
|
||||
case lnwallet.HtlcOfferedRemoteTimeout:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.AcceptedHtlcTimeoutWitnessSize,
|
||||
)
|
||||
cltvOutputs = append(cltvOutputs, input)
|
||||
|
||||
default:
|
||||
log.Warnf("kindergarten output in nursery store "+
|
||||
"contains unexpected witness type: %v",
|
||||
input.WitnessType())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+
|
||||
"inputs", len(csvOutputs), len(cltvOutputs))
|
||||
|
||||
txWeight := int64(weightEstimate.Weight())
|
||||
return s.populateSweepTx(txWeight, currentBlockHeight, csvOutputs, cltvOutputs)
|
||||
}
|
||||
|
||||
// populateSweepTx populate the final sweeping transaction with all witnesses
|
||||
// in place for all inputs using the provided txn fee. The created transaction
|
||||
// has a single output sending all the funds back to the source wallet, after
|
||||
// accounting for the fee estimate.
|
||||
func (s *UtxoSweeper) populateSweepTx(txWeight int64, currentBlockHeight uint32,
|
||||
csvInputs []CsvSpendableOutput,
|
||||
cltvInputs []SpendableOutput) (*wire.MsgTx, error) {
|
||||
|
||||
// Generate the receiving script to which the funds will be swept.
|
||||
pkScript, err := s.cfg.GenSweepScript()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sum up the total value contained in the inputs.
|
||||
var totalSum btcutil.Amount
|
||||
for _, o := range csvInputs {
|
||||
totalSum += o.Amount()
|
||||
}
|
||||
for _, o := range cltvInputs {
|
||||
totalSum += o.Amount()
|
||||
}
|
||||
|
||||
// Using the txn weight estimate, compute the required txn fee.
|
||||
feePerKw, err := s.cfg.Estimator.EstimateFeePerKW(s.cfg.ConfTarget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Using %v sat/kw for sweep tx", int64(feePerKw))
|
||||
|
||||
txFee := feePerKw.FeeForWeight(txWeight)
|
||||
|
||||
// Sweep as much possible, after subtracting txn fees.
|
||||
sweepAmt := int64(totalSum - txFee)
|
||||
|
||||
// Create the sweep transaction that we will be building. We use
|
||||
// version 2 as it is required for CSV. The txn will sweep the amount
|
||||
// after fees to the pkscript generated above.
|
||||
sweepTx := wire.NewMsgTx(2)
|
||||
sweepTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: pkScript,
|
||||
Value: sweepAmt,
|
||||
})
|
||||
|
||||
// We'll also ensure that the transaction has the required lock time if
|
||||
// we're sweeping any cltvInputs.
|
||||
if len(cltvInputs) > 0 {
|
||||
sweepTx.LockTime = currentBlockHeight
|
||||
}
|
||||
|
||||
// Add all inputs to the sweep transaction. Ensure that for each
|
||||
// csvInput, we set the sequence number properly.
|
||||
for _, input := range csvInputs {
|
||||
sweepTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: *input.OutPoint(),
|
||||
Sequence: input.BlocksToMaturity(),
|
||||
})
|
||||
}
|
||||
for _, input := range cltvInputs {
|
||||
sweepTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: *input.OutPoint(),
|
||||
})
|
||||
}
|
||||
|
||||
// Before signing the transaction, check to ensure that it meets some
|
||||
// basic validity requirements.
|
||||
// TODO(conner): add more control to sanity checks, allowing us to delay
|
||||
// spending "problem" outputs, e.g. possibly batching with other classes
|
||||
// if fees are too low.
|
||||
btx := btcutil.NewTx(sweepTx)
|
||||
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashCache := txscript.NewTxSigHashes(sweepTx)
|
||||
|
||||
// With all the inputs in place, use each output's unique witness
|
||||
// function to generate the final witness required for spending.
|
||||
addWitness := func(idx int, tso SpendableOutput) error {
|
||||
witness, err := tso.BuildWitness(
|
||||
s.cfg.Signer, sweepTx, hashCache, idx,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sweepTx.TxIn[idx].Witness = witness
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finally we'll attach a valid witness to each csv and cltv input
|
||||
// within the sweeping transaction.
|
||||
for i, input := range csvInputs {
|
||||
if err := addWitness(i, input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add offset to relative indexes so cltv witnesses don't overwrite csv
|
||||
// witnesses.
|
||||
offset := len(csvInputs)
|
||||
for i, input := range cltvInputs {
|
||||
if err := addWitness(offset+i, input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sweepTx, nil
|
||||
}
|
224
utxonursery.go
224
utxonursery.go
@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
@ -193,14 +192,6 @@ type NurseryConfig struct {
|
||||
FetchClosedChannel func(chanID *wire.OutPoint) (
|
||||
*channeldb.ChannelCloseSummary, error)
|
||||
|
||||
// Estimator is used when crafting sweep transactions to estimate the
|
||||
// necessary fee relative to the expected size of the sweep transaction.
|
||||
Estimator lnwallet.FeeEstimator
|
||||
|
||||
// GenSweepScript generates a P2WKH script belonging to the wallet where
|
||||
// funds can be swept.
|
||||
GenSweepScript func() ([]byte, error)
|
||||
|
||||
// Notifier provides the utxo nursery the ability to subscribe to
|
||||
// transaction confirmation events, which advance outputs through their
|
||||
// persistence state transitions.
|
||||
@ -210,13 +201,13 @@ type NurseryConfig struct {
|
||||
// transaction to the appropriate network.
|
||||
PublishTransaction func(*wire.MsgTx) error
|
||||
|
||||
// Signer is used by the utxo nursery to generate valid witnesses at the
|
||||
// time the incubated outputs need to be spent.
|
||||
Signer lnwallet.Signer
|
||||
|
||||
// Store provides access to and modification of the persistent state
|
||||
// maintained about the utxo nursery's incubating outputs.
|
||||
Store NurseryStore
|
||||
|
||||
// Sweeper provides functionality to generate sweep transactions.
|
||||
// Nursery uses this to sweep final outputs back into the wallet.
|
||||
Sweeper *sweep.UtxoSweeper
|
||||
}
|
||||
|
||||
// utxoNursery is a system dedicated to incubating time-locked outputs created
|
||||
@ -880,7 +871,15 @@ func (u *utxoNursery) graduateClass(classHeight uint32) error {
|
||||
// generated a sweep txn for this height. Generate one if there
|
||||
// are kindergarten outputs or cltv crib outputs to be spent.
|
||||
if len(kgtnOutputs) > 0 {
|
||||
finalTx, err = u.createSweepTx(kgtnOutputs, classHeight)
|
||||
csvSpendableOutputs := make([]sweep.CsvSpendableOutput,
|
||||
len(kgtnOutputs))
|
||||
for i := range kgtnOutputs {
|
||||
csvSpendableOutputs[i] = &kgtnOutputs[i]
|
||||
}
|
||||
|
||||
finalTx, err = u.cfg.Sweeper.CreateSweepTx(
|
||||
csvSpendableOutputs, classHeight)
|
||||
|
||||
if err != nil {
|
||||
utxnLog.Errorf("Failed to create sweep txn at "+
|
||||
"height=%d", classHeight)
|
||||
@ -936,203 +935,6 @@ func (u *utxoNursery) graduateClass(classHeight uint32) error {
|
||||
return u.cfg.Store.GraduateHeight(classHeight)
|
||||
}
|
||||
|
||||
// craftSweepTx accepts a list of kindergarten outputs, and baby
|
||||
// outputs which don't require a second-layer claim, and signs and generates a
|
||||
// signed txn that spends from them. This method also makes an accurate fee
|
||||
// estimate before generating the required witnesses.
|
||||
func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput,
|
||||
classHeight uint32) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a transaction which sweeps all the newly mature outputs into
|
||||
// an output controlled by the wallet.
|
||||
|
||||
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
||||
// be more efficient on-chain.
|
||||
|
||||
// Assemble the kindergarten class into a slice csv spendable outputs,
|
||||
// and also a set of regular spendable outputs. The set of regular
|
||||
// outputs are CLTV locked outputs that have had their timelocks
|
||||
// expire.
|
||||
var (
|
||||
csvOutputs []sweep.CsvSpendableOutput
|
||||
cltvOutputs []sweep.SpendableOutput
|
||||
weightEstimate lnwallet.TxWeightEstimator
|
||||
)
|
||||
|
||||
// Allocate enough room for both types of kindergarten outputs.
|
||||
csvOutputs = make([]sweep.CsvSpendableOutput, 0, len(kgtnOutputs))
|
||||
cltvOutputs = make([]sweep.SpendableOutput, 0, len(kgtnOutputs))
|
||||
|
||||
// Our sweep transaction will pay to a single segwit p2wkh address,
|
||||
// ensure it contributes to our weight estimate.
|
||||
weightEstimate.AddP2WKHOutput()
|
||||
|
||||
// For each kindergarten output, use its witness type to determine the
|
||||
// estimate weight of its witness, and add it to the proper set of
|
||||
// spendable outputs.
|
||||
for i := range kgtnOutputs {
|
||||
input := &kgtnOutputs[i]
|
||||
|
||||
switch input.WitnessType() {
|
||||
|
||||
// Outputs on a past commitment transaction that pay directly
|
||||
// to us.
|
||||
case lnwallet.CommitmentTimeLock:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// Outgoing second layer HTLC's that have confirmed within the
|
||||
// chain, and the output they produced is now mature enough to
|
||||
// sweep.
|
||||
case lnwallet.HtlcOfferedTimeoutSecondLevel:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// Incoming second layer HTLC's that have confirmed within the
|
||||
// chain, and the output they produced is now mature enough to
|
||||
// sweep.
|
||||
case lnwallet.HtlcAcceptedSuccessSecondLevel:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.ToLocalTimeoutWitnessSize,
|
||||
)
|
||||
csvOutputs = append(csvOutputs, input)
|
||||
|
||||
// An HTLC on the commitment transaction of the remote party,
|
||||
// that has had its absolute timelock expire.
|
||||
case lnwallet.HtlcOfferedRemoteTimeout:
|
||||
weightEstimate.AddWitnessInput(
|
||||
lnwallet.AcceptedHtlcTimeoutWitnessSize,
|
||||
)
|
||||
cltvOutputs = append(cltvOutputs, input)
|
||||
|
||||
default:
|
||||
utxnLog.Warnf("kindergarten output in nursery store "+
|
||||
"contains unexpected witness type: %v",
|
||||
input.WitnessType())
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
utxnLog.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+
|
||||
"inputs", len(csvOutputs), len(cltvOutputs))
|
||||
|
||||
txWeight := int64(weightEstimate.Weight())
|
||||
return u.populateSweepTx(txWeight, classHeight, csvOutputs, cltvOutputs)
|
||||
}
|
||||
|
||||
// populateSweepTx populate the final sweeping transaction with all witnesses
|
||||
// in place for all inputs using the provided txn fee. The created transaction
|
||||
// has a single output sending all the funds back to the source wallet, after
|
||||
// accounting for the fee estimate.
|
||||
func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32,
|
||||
csvInputs []sweep.CsvSpendableOutput,
|
||||
cltvInputs []sweep.SpendableOutput) (*wire.MsgTx, error) {
|
||||
|
||||
// Generate the receiving script to which the funds will be swept.
|
||||
pkScript, err := u.cfg.GenSweepScript()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sum up the total value contained in the inputs.
|
||||
var totalSum btcutil.Amount
|
||||
for _, o := range csvInputs {
|
||||
totalSum += o.Amount()
|
||||
}
|
||||
for _, o := range cltvInputs {
|
||||
totalSum += o.Amount()
|
||||
}
|
||||
|
||||
// Using the txn weight estimate, compute the required txn fee.
|
||||
feePerKw, err := u.cfg.Estimator.EstimateFeePerKW(6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txFee := feePerKw.FeeForWeight(txWeight)
|
||||
|
||||
// Sweep as much possible, after subtracting txn fees.
|
||||
sweepAmt := int64(totalSum - txFee)
|
||||
|
||||
// Create the sweep transaction that we will be building. We use
|
||||
// version 2 as it is required for CSV. The txn will sweep the amount
|
||||
// after fees to the pkscript generated above.
|
||||
sweepTx := wire.NewMsgTx(2)
|
||||
sweepTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: pkScript,
|
||||
Value: sweepAmt,
|
||||
})
|
||||
|
||||
// We'll also ensure that the transaction has the required lock time if
|
||||
// we're sweeping any cltvInputs.
|
||||
if len(cltvInputs) > 0 {
|
||||
sweepTx.LockTime = classHeight
|
||||
}
|
||||
|
||||
// Add all inputs to the sweep transaction. Ensure that for each
|
||||
// csvInput, we set the sequence number properly.
|
||||
for _, input := range csvInputs {
|
||||
sweepTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: *input.OutPoint(),
|
||||
Sequence: input.BlocksToMaturity(),
|
||||
})
|
||||
}
|
||||
for _, input := range cltvInputs {
|
||||
sweepTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: *input.OutPoint(),
|
||||
})
|
||||
}
|
||||
|
||||
// Before signing the transaction, check to ensure that it meets some
|
||||
// basic validity requirements.
|
||||
// TODO(conner): add more control to sanity checks, allowing us to delay
|
||||
// spending "problem" outputs, e.g. possibly batching with other classes
|
||||
// if fees are too low.
|
||||
btx := btcutil.NewTx(sweepTx)
|
||||
if err := blockchain.CheckTransactionSanity(btx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hashCache := txscript.NewTxSigHashes(sweepTx)
|
||||
|
||||
// With all the inputs in place, use each output's unique witness
|
||||
// function to generate the final witness required for spending.
|
||||
addWitness := func(idx int, tso sweep.SpendableOutput) error {
|
||||
witness, err := tso.BuildWitness(
|
||||
u.cfg.Signer, sweepTx, hashCache, idx,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sweepTx.TxIn[idx].Witness = witness
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finally we'll attach a valid witness to each csv and cltv input
|
||||
// within the sweeping transaction.
|
||||
for i, input := range csvInputs {
|
||||
if err := addWitness(i, input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add offset to relative indexes so cltv witnesses don't overwrite csv
|
||||
// witnesses.
|
||||
offset := len(csvInputs)
|
||||
for i, input := range cltvInputs {
|
||||
if err := addWitness(offset+i, input); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sweepTx, nil
|
||||
}
|
||||
|
||||
// sweepMatureOutputs generates and broadcasts the transaction that transfers
|
||||
// control of funds from a prior channel commitment transaction to the user's
|
||||
// wallet. The outputs swept were previously time locked (either absolute or
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/sweep"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
@ -431,6 +432,14 @@ func createNurseryTestContext(t *testing.T,
|
||||
|
||||
notifier := newNurseryMockNotifier(t)
|
||||
|
||||
sweeper := sweep.New(&sweep.UtxoSweeperConfig{
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
},
|
||||
Estimator: &mockFeeEstimator{},
|
||||
Signer: &nurseryMockSigner{},
|
||||
})
|
||||
|
||||
cfg := NurseryConfig{
|
||||
Notifier: notifier,
|
||||
FetchClosedChannels: func(pendingOnly bool) (
|
||||
@ -445,11 +454,7 @@ func createNurseryTestContext(t *testing.T,
|
||||
},
|
||||
Store: storeIntercepter,
|
||||
ChainIO: &mockChainIO{},
|
||||
GenSweepScript: func() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
},
|
||||
Estimator: &mockFeeEstimator{},
|
||||
Signer: &nurseryMockSigner{},
|
||||
Sweeper: sweeper,
|
||||
}
|
||||
|
||||
publishChan := make(chan wire.MsgTx, 1)
|
||||
|
Loading…
Reference in New Issue
Block a user