Merge pull request #1978 from joostjager/sweeper-prep

sweep: prepare for sweeper
This commit is contained in:
Olaoluwa Osuntokun 2018-10-17 16:04:25 -07:00 committed by GitHub
commit c9e42a6ce0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 527 additions and 381 deletions

@ -20,6 +20,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/sweep"
)
var (
@ -748,33 +749,6 @@ func (b *breachArbiter) handleBreachHandoff(breachEvent *ContractBreachEvent) {
go b.exactRetribution(cfChan, retInfo)
}
// SpendableOutput an interface which can be used by the breach arbiter to
// construct a transaction spending from outputs we control.
type SpendableOutput interface {
// Amount returns the number of satoshis contained within the output.
Amount() btcutil.Amount
// Outpoint returns the reference to the output being spent, used to
// construct the corresponding transaction input.
OutPoint() *wire.OutPoint
// WitnessType returns an enum specifying the type of witness that must
// be generated in order to spend this output.
WitnessType() lnwallet.WitnessType
// SignDesc returns a reference to a spendable output's sign descriptor,
// which is used during signing to compute a valid witness that spends
// this output.
SignDesc() *lnwallet.SignDescriptor
// BuildWitness returns a valid witness allowing this output to be
// spent, the witness should be attached to the transaction at the
// location determined by the given `txinIdx`.
BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
txinIdx int) ([][]byte, error)
}
// breachedOutput contains all the information needed to sweep a breached
// output. A breached output is an output that we are now entitled to due to a
// revoked commitment transaction being broadcast.
@ -850,9 +824,16 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
return bo.witnessFunc(txn, hashCache, txinIdx)
}
// Add compile-time constraint ensuring breachedOutput implements
// SpendableOutput.
var _ SpendableOutput = (*breachedOutput)(nil)
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent.
func (bo *breachedOutput) BlocksToMaturity() uint32 {
return 0
}
// Add compile-time constraint ensuring breachedOutput implements the Input
// interface.
var _ sweep.Input = (*breachedOutput)(nil)
// retributionInfo encapsulates all the data needed to sweep all the contested
// funds within a channel whose contract has been breached by the prior
@ -963,13 +944,13 @@ func (b *breachArbiter) createJusticeTx(
// outputs, while simultaneously computing the estimated weight of the
// transaction.
var (
spendableOutputs []SpendableOutput
spendableOutputs []sweep.Input
weightEstimate lnwallet.TxWeightEstimator
)
// Allocate enough space to potentially hold each of the breached
// outputs in the retribution info.
spendableOutputs = make([]SpendableOutput, 0, len(r.breachedOutputs))
spendableOutputs = make([]sweep.Input, 0, len(r.breachedOutputs))
// The justice transaction we construct will be a segwit transaction
// that pays to a p2wkh output. Components such as the version,
@ -1023,7 +1004,7 @@ func (b *breachArbiter) createJusticeTx(
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
// spendable outputs by sweeping the funds into a single p2wkh output.
func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
inputs ...SpendableOutput) (*wire.MsgTx, error) {
inputs ...sweep.Input) (*wire.MsgTx, error) {
// First, we obtain a new public key script from the wallet which we'll
// sweep the funds to.
@ -1037,7 +1018,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// Compute the total amount contained in the inputs.
var totalAmt btcutil.Amount
for _, input := range inputs {
totalAmt += input.Amount()
totalAmt += btcutil.Amount(input.SignDesc().Output.Value)
}
// We'll actually attempt to target inclusion within the next two
@ -1085,7 +1066,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
// witness, and attaching it to the transaction. This function accepts
// an integer index representing the intended txin index, and the
// breached output from which it will spend.
addWitness := func(idx int, so SpendableOutput) error {
addWitness := func(idx int, so sweep.Input) error {
// First, we construct a valid witness for this outpoint and
// transaction using the SpendableOutput's witness generation
// function.

@ -3,6 +3,7 @@ package contractcourt
import (
"errors"
"fmt"
"github.com/lightningnetwork/lnd/sweep"
"sync"
"sync/atomic"
@ -130,6 +131,9 @@ type ChainArbitratorConfig struct {
// DisableChannel disables a channel, resulting in it not being able to
// forward payments.
DisableChannel func(wire.OutPoint) error
// Sweeper allows resolvers to sweep their final outputs.
Sweeper *sweep.UtxoSweeper
}
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all

@ -5,10 +5,10 @@ import (
"crypto/sha256"
"encoding/binary"
"fmt"
"github.com/lightningnetwork/lnd/sweep"
"io"
"io/ioutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
@ -446,59 +446,19 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
"incoming+remote htlc confirmed", h,
h.payHash[:])
// In this case, we can sweep it directly from the
// commitment output. We'll first grab a fresh address
// from the wallet to sweep the output.
addr, err := h.NewSweepAddr()
if err != nil {
return nil, err
}
// With our address obtained, we'll query for an
// estimate to be confirmed at ease.
//
// TODO(roasbeef): signal up if fee would be too large
// to sweep singly, need to batch
feePerKw, err := h.FeeEstimator.EstimateFeePerKW(6)
if err != nil {
return nil, err
}
log.Debugf("%T(%x): using %v sat/kw to sweep htlc"+
"incoming+remote htlc confirmed", h,
h.payHash[:], int64(feePerKw))
// Using a weight estimator, we'll compute the total
// fee required, and from that the value we'll end up
// with.
totalWeight := (&lnwallet.TxWeightEstimator{}).
AddWitnessInput(lnwallet.OfferedHtlcSuccessWitnessSize).
AddP2WKHOutput().Weight()
totalFees := feePerKw.FeeForWeight(int64(totalWeight))
sweepAmt := h.htlcResolution.SweepSignDesc.Output.Value -
int64(totalFees)
// With the fee computation finished, we'll now
// construct the sweep transaction.
htlcPoint := h.htlcResolution.ClaimOutpoint
h.sweepTx = wire.NewMsgTx(2)
h.sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: htlcPoint,
})
h.sweepTx.AddTxOut(&wire.TxOut{
PkScript: addr,
Value: sweepAmt,
})
// With the transaction fully assembled, we can now
// generate a valid witness for the transaction.
h.htlcResolution.SweepSignDesc.SigHashes = txscript.NewTxSigHashes(
h.sweepTx,
)
h.sweepTx.TxIn[0].Witness, err = lnwallet.SenderHtlcSpendRedeem(
h.Signer, &h.htlcResolution.SweepSignDesc, h.sweepTx,
input := sweep.MakeHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
)
var err error
// TODO: Set tx lock time to current block height instead of
// zero. Will be taken care of once sweeper implementation is
// complete.
h.sweepTx, err = h.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, 0)
if err != nil {
return nil, err
}
@ -1256,46 +1216,16 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// If the sweep transaction isn't already generated, and the remote
// party broadcast the commitment transaction then we'll create it now.
case c.sweepTx == nil && !isLocalCommitTx:
// Now that the commitment transaction has confirmed, we'll
// craft a transaction to sweep this output into the wallet.
signDesc := c.commitResolution.SelfOutputSignDesc
input := sweep.MakeBaseInput(
&c.commitResolution.SelfOutPoint,
lnwallet.CommitmentNoDelay,
&c.commitResolution.SelfOutputSignDesc)
// First, we'll estimate the total weight so we can compute
// fees properly. We'll use a lax estimate, as this output is
// in no immediate danger.
feePerKw, err := c.FeeEstimator.EstimateFeePerKW(6)
if err != nil {
return nil, err
}
log.Debugf("%T(%v): using %v sat/kw for sweep tx", c,
c.chanPoint, int64(feePerKw))
totalWeight := (&lnwallet.TxWeightEstimator{}).
AddP2WKHInput().
AddP2WKHOutput().Weight()
totalFees := feePerKw.FeeForWeight(int64(totalWeight))
sweepAmt := signDesc.Output.Value - int64(totalFees)
c.sweepTx = wire.NewMsgTx(2)
c.sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: c.commitResolution.SelfOutPoint,
})
sweepAddr, err := c.NewSweepAddr()
if err != nil {
return nil, err
}
c.sweepTx.AddTxOut(&wire.TxOut{
PkScript: sweepAddr,
Value: sweepAmt,
})
// With the transaction fully assembled, we can now generate a
// valid witness for the transaction.
signDesc.SigHashes = txscript.NewTxSigHashes(c.sweepTx)
c.sweepTx.TxIn[0].Witness, err = lnwallet.CommitSpendNoDelay(
c.Signer, &signDesc, c.sweepTx,
)
// TODO: Set tx lock time to current block height instead of
// zero. Will be taken care of once sweeper implementation is
// complete.
c.sweepTx, err = c.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, 0)
if err != nil {
return nil, err
}

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

@ -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.
@ -685,6 +695,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
DisableChannel: func(op wire.OutPoint) error {
return s.announceChanStatus(op, true)
},
Sweeper: sweeper,
}, chanDB)
s.breachArbiter = newBreachArbiter(&BreachConfig{

154
sweep/input.go Normal file

@ -0,0 +1,154 @@
package sweep
import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwallet"
)
// Input contains all data needed to construct a sweep tx input.
type Input interface {
// Outpoint returns the reference to the output being spent, used to
// construct the corresponding transaction input.
OutPoint() *wire.OutPoint
// WitnessType returns an enum specifying the type of witness that must
// be generated in order to spend this output.
WitnessType() lnwallet.WitnessType
// SignDesc returns a reference to a spendable output's sign descriptor,
// which is used during signing to compute a valid witness that spends
// this output.
SignDesc() *lnwallet.SignDescriptor
// BuildWitness returns a valid witness allowing this output to be
// spent, the witness should be attached to the transaction at the
// location determined by the given `txinIdx`.
BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes,
txinIdx int) ([][]byte, error)
// BlocksToMaturity returns the relative timelock, as a number of
// blocks, that must be built on top of the confirmation height before
// the output can be spent. For non-CSV locked inputs this is always
// zero.
BlocksToMaturity() uint32
}
type inputKit struct {
outpoint wire.OutPoint
witnessType lnwallet.WitnessType
signDesc lnwallet.SignDescriptor
}
// OutPoint returns the breached output's identifier that is to be included as a
// transaction input.
func (i *inputKit) OutPoint() *wire.OutPoint {
return &i.outpoint
}
// WitnessType returns the type of witness that must be generated to spend the
// breached output.
func (i *inputKit) WitnessType() lnwallet.WitnessType {
return i.witnessType
}
// SignDesc returns the breached output's SignDescriptor, which is used during
// signing to compute the witness.
func (i *inputKit) SignDesc() *lnwallet.SignDescriptor {
return &i.signDesc
}
// BaseInput contains all the information needed to sweep a basic output
// (CSV/CLTV/no time lock)
type BaseInput struct {
inputKit
}
// MakeBaseInput assembles a new BaseInput that can be used to construct a
// sweep transaction.
func MakeBaseInput(outpoint *wire.OutPoint,
witnessType lnwallet.WitnessType,
signDescriptor *lnwallet.SignDescriptor) BaseInput {
return BaseInput{
inputKit{
outpoint: *outpoint,
witnessType: witnessType,
signDesc: *signDescriptor,
},
}
}
// BuildWitness computes a valid witness that allows us to spend from the
// breached output. It does so by generating the witness generation function,
// which is parameterized primarily by the witness type and sign descriptor. The
// method then returns the witness computed by invoking this function.
func (bi *BaseInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
witnessFunc := bi.witnessType.GenWitnessFunc(
signer, bi.SignDesc(),
)
return witnessFunc(txn, hashCache, txinIdx)
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent. For non-CSV locked inputs this is always zero.
func (bi *BaseInput) BlocksToMaturity() uint32 {
return 0
}
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input
// is expected to reside on the commitment tx of the remote party and should not
// be a second level tx output.
type HtlcSucceedInput struct {
inputKit
preimage []byte
}
// MakeHtlcSucceedInput assembles a new redeem input that can be used to
// construct a sweep transaction.
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
signDescriptor *lnwallet.SignDescriptor,
preimage []byte) HtlcSucceedInput {
return HtlcSucceedInput{
inputKit: inputKit{
outpoint: *outpoint,
witnessType: lnwallet.HtlcAcceptedRemoteSuccess,
signDesc: *signDescriptor,
},
preimage: preimage,
}
}
// BuildWitness computes a valid witness that allows us to spend from the
// breached output. For HtlcSpendInput it will need to make the preimage part of
// the witness.
func (h *HtlcSucceedInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
desc := h.signDesc
desc.SigHashes = hashCache
desc.InputIndex = txinIdx
return lnwallet.SenderHtlcSpendRedeem(
signer, &desc, txn,
h.preimage,
)
}
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
// must be built on top of the confirmation height before the output can be
// spent.
func (h *HtlcSucceedInput) BlocksToMaturity() uint32 {
return 0
}
// Add compile-time constraint ensuring input structs implement Input interface.
var _ Input = (*BaseInput)(nil)
var _ Input = (*HtlcSucceedInput)(nil)

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)
}

234
sweep/sweeper.go Normal file

@ -0,0 +1,234 @@
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 inputs and signs and generates a txn that
// spends from them. This method also makes an accurate fee estimate before
// generating the required witnesses.
//
// The created transaction has a single output sending all the funds back to the
// source wallet, after accounting for the fee estimate.
//
// 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 []Input,
currentBlockHeight uint32) (*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
}
txWeight, csvCount, cltvCount := s.getWeightEstimate(inputs)
// 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.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV) "+
"using %v sat/kw", len(inputs), csvCount, cltvCount,
int64(feePerKw))
txFee := feePerKw.FeeForWeight(txWeight)
// Sum up the total value contained in the inputs.
var totalSum btcutil.Amount
for _, o := range inputs {
totalSum += btcutil.Amount(o.SignDesc().Output.Value)
}
// 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,
})
sweepTx.LockTime = currentBlockHeight
// Add all inputs to the sweep transaction. Ensure that for each
// csvInput, we set the sequence number properly.
for _, input := range inputs {
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
Sequence: input.BlocksToMaturity(),
})
}
// 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 Input) 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 inputs {
if err := addWitness(i, input); err != nil {
return nil, err
}
}
return sweepTx, nil
}
// getWeightEstimate returns a weight estimate for the given inputs.
// Additionally, it returns counts for the number of csv and cltv inputs.
func (s *UtxoSweeper) getWeightEstimate(inputs []Input) (int64, int, int) {
// 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.
var weightEstimate lnwallet.TxWeightEstimator
// 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.
csvCount := 0
cltvCount := 0
for i := range inputs {
input := inputs[i]
switch input.WitnessType() {
// Outputs on a remote commitment transaction that pay directly
// to us.
case lnwallet.CommitmentNoDelay:
weightEstimate.AddP2WKHInput()
// Outputs on a past commitment transaction that pay directly
// to us.
case lnwallet.CommitmentTimeLock:
weightEstimate.AddWitnessInput(
lnwallet.ToLocalTimeoutWitnessSize,
)
csvCount++
// 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,
)
csvCount++
// 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,
)
csvCount++
// An HTLC on the commitment transaction of the remote party,
// that has had its absolute timelock expire.
case lnwallet.HtlcOfferedRemoteTimeout:
weightEstimate.AddWitnessInput(
lnwallet.AcceptedHtlcTimeoutWitnessSize,
)
cltvCount++
// An HTLC on the commitment transaction of the remote party,
// that can be swept with the preimage.
case lnwallet.HtlcAcceptedRemoteSuccess:
weightEstimate.AddWitnessInput(
lnwallet.OfferedHtlcSuccessWitnessSize,
)
default:
log.Warnf("kindergarten output in nursery store "+
"contains unexpected witness type: %v",
input.WitnessType())
continue
}
}
txWeight := int64(weightEstimate.Weight())
return txWeight, csvCount, cltvCount
}

@ -4,11 +4,11 @@ import (
"bytes"
"encoding/binary"
"fmt"
"github.com/lightningnetwork/lnd/sweep"
"io"
"sync"
"sync/atomic"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
@ -192,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.
@ -209,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
@ -879,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)
sweepInputs := make([]sweep.Input,
len(kgtnOutputs))
for i := range kgtnOutputs {
sweepInputs[i] = &kgtnOutputs[i]
}
finalTx, err = u.cfg.Sweeper.CreateSweepTx(
sweepInputs, classHeight)
if err != nil {
utxnLog.Errorf("Failed to create sweep txn at "+
"height=%d", classHeight)
@ -935,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 []CsvSpendableOutput
cltvOutputs []SpendableOutput
weightEstimate lnwallet.TxWeightEstimator
)
// Allocate enough room for both types of kindergarten outputs.
csvOutputs = make([]CsvSpendableOutput, 0, len(kgtnOutputs))
cltvOutputs = make([]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 []CsvSpendableOutput,
cltvInputs []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 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
@ -1635,29 +1438,6 @@ func newSweepPkScript(wallet lnwallet.WalletController) ([]byte, error) {
return txscript.PayToAddrScript(sweepAddr)
}
// CsvSpendableOutput is a SpendableOutput that contains all of the information
// necessary to construct, sign, and sweep an output locked with a CSV delay.
type CsvSpendableOutput interface {
SpendableOutput
// ConfHeight returns the height at which this output was confirmed.
// A zero value indicates that the output has not been confirmed.
ConfHeight() uint32
// SetConfHeight marks the height at which the output is confirmed in
// the chain.
SetConfHeight(height uint32)
// BlocksToMaturity returns the relative timelock, as a number of
// blocks, that must be built on top of the confirmation height before
// the output can be spent.
BlocksToMaturity() uint32
// OriginChanPoint returns the outpoint of the channel from which this
// output is derived.
OriginChanPoint() *wire.OutPoint
}
// babyOutput represents a two-stage CSV locked output, and is used to track
// htlc outputs through incubation. The first stage requires broadcasting a
// presigned timeout txn that spends from the CLTV locked output on the
@ -1969,7 +1749,7 @@ func readTxOut(r io.Reader, txo *wire.TxOut) error {
return nil
}
// Compile-time constraint to ensure kidOutput and babyOutput implement the
// CsvSpendableOutput interface.
var _ CsvSpendableOutput = (*kidOutput)(nil)
var _ CsvSpendableOutput = (*babyOutput)(nil)
// Compile-time constraint to ensure kidOutput implements the
// Input interface.
var _ sweep.Input = (*kidOutput)(nil)

@ -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)