multi: modify sweeper.CreateSweepTx to accept conf target, style changes

In this commit, we modify the newly introduced UtxoSweeper.CreateSweepTx
to accept the confirmation target as a param of the method rather than a
struct level variable. We do this as this allows each caller to decide
at sweep time, what the fee rate should be, rather than using a global
value that is meant to work in all scenarios. For example, anytime
we're sweeping an output with a CLTV lock that's has a dependant
transaction we need to sweep/cancel, we may require a higher fee rate
than a regular force close with a CSV output.
This commit is contained in:
Olaoluwa Osuntokun 2018-10-17 16:27:11 -07:00
parent 90fe860a3c
commit fc21bf091a
No known key found for this signature in database
GPG Key ID: CE58F7F8E20FD9A2
6 changed files with 93 additions and 71 deletions

@ -5,10 +5,11 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/lightningnetwork/lnd/sweep"
"io" "io"
"io/ioutil" "io/ioutil"
"github.com/lightningnetwork/lnd/sweep"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs"
@ -20,6 +21,12 @@ var (
endian = binary.BigEndian endian = binary.BigEndian
) )
const (
// sweepConfTarget is the default number of blocks that we'll use as a
// confirmation target when sweeping.
sweepConfTarget = 6
)
// ContractResolver is an interface which packages a state machine which is // ContractResolver is an interface which packages a state machine which is
// able to carry out the necessary steps required to fully resolve a Bitcoin // able to carry out the necessary steps required to fully resolve a Bitcoin
// contract on-chain. Resolvers are fully encodable to ensure callers are able // contract on-chain. Resolvers are fully encodable to ensure callers are able
@ -446,19 +453,27 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
"incoming+remote htlc confirmed", h, "incoming+remote htlc confirmed", h,
h.payHash[:]) h.payHash[:])
// Before we can craft out sweeping transaction, we
// need to create an input which contains all the items
// required to add this input to a sweeping transaction,
// and generate a witness.
input := sweep.MakeHtlcSucceedInput( input := sweep.MakeHtlcSucceedInput(
&h.htlcResolution.ClaimOutpoint, &h.htlcResolution.ClaimOutpoint,
&h.htlcResolution.SweepSignDesc, &h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:], h.htlcResolution.Preimage[:],
) )
// With the input created, we can now generate the full
// sweep transaction, that we'll use to move these
// coins back into the backing wallet.
//
// TODO: Set tx lock time to current block height
// instead of zero. Will be taken care of once sweeper
// implementation is complete.
var err error 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( h.sweepTx, err = h.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, 0) []sweep.Input{&input}, sweepConfTarget, 0,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1216,16 +1231,26 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
// If the sweep transaction isn't already generated, and the remote // If the sweep transaction isn't already generated, and the remote
// party broadcast the commitment transaction then we'll create it now. // party broadcast the commitment transaction then we'll create it now.
case c.sweepTx == nil && !isLocalCommitTx: case c.sweepTx == nil && !isLocalCommitTx:
// As we haven't already generated the sweeping transaction,
// we'll now craft an input with all the information required
// to create a fully valid sweeping transaction to recover
// these coins.
input := sweep.MakeBaseInput( input := sweep.MakeBaseInput(
&c.commitResolution.SelfOutPoint, &c.commitResolution.SelfOutPoint,
lnwallet.CommitmentNoDelay, lnwallet.CommitmentNoDelay,
&c.commitResolution.SelfOutputSignDesc) &c.commitResolution.SelfOutputSignDesc,
)
// With out input constructed, we'll now request that the
// sweeper construct a valid sweeping transaction for this
// input.
//
// TODO: Set tx lock time to current block height instead of // TODO: Set tx lock time to current block height instead of
// zero. Will be taken care of once sweeper implementation is // zero. Will be taken care of once sweeper implementation is
// complete. // complete.
c.sweepTx, err = c.Sweeper.CreateSweepTx( c.sweepTx, err = c.Sweeper.CreateSweepTx(
[]sweep.Input{&input}, 0) []sweep.Input{&input}, sweepConfTarget, 0,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -6,7 +6,6 @@ import (
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/lightningnetwork/lnd/sweep"
"image/color" "image/color"
"math/big" "math/big"
"net" "net"
@ -16,6 +15,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/lightningnetwork/lnd/sweep"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr" "github.com/btcsuite/btcd/connmgr"
@ -59,10 +60,6 @@ const (
// durations exceeding this value will be eligible to have their // durations exceeding this value will be eligible to have their
// backoffs reduced. // backoffs reduced.
defaultStableConnDuration = 10 * time.Minute defaultStableConnDuration = 10 * time.Minute
// sweepTxConfirmationTarget assigns a confirmation target for sweep
// txes on which the fee calculation will be based.
sweepTxConfirmationTarget = 6
) )
var ( var (
@ -591,13 +588,13 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
GenSweepScript: func() ([]byte, error) { GenSweepScript: func() ([]byte, error) {
return newSweepPkScript(cc.wallet) return newSweepPkScript(cc.wallet)
}, },
Signer: cc.wallet.Cfg.Signer, Signer: cc.wallet.Cfg.Signer,
ConfTarget: sweepTxConfirmationTarget,
}) })
s.utxoNursery = newUtxoNursery(&NurseryConfig{ s.utxoNursery = newUtxoNursery(&NurseryConfig{
ChainIO: cc.chainIO, ChainIO: cc.chainIO,
ConfDepth: 1, ConfDepth: 1,
SweepTxConfTarget: 6,
FetchClosedChannels: chanDB.FetchClosedChannels, FetchClosedChannels: chanDB.FetchClosedChannels,
FetchClosedChannel: chanDB.FetchClosedChannel, FetchClosedChannel: chanDB.FetchClosedChannel,
Notifier: cc.chainNotifier, Notifier: cc.chainNotifier,

@ -16,9 +16,9 @@ type Input interface {
// be generated in order to spend this output. // be generated in order to spend this output.
WitnessType() lnwallet.WitnessType WitnessType() lnwallet.WitnessType
// SignDesc returns a reference to a spendable output's sign descriptor, // SignDesc returns a reference to a spendable output's sign
// which is used during signing to compute a valid witness that spends // descriptor, which is used during signing to compute a valid witness
// this output. // that spends this output.
SignDesc() *lnwallet.SignDescriptor SignDesc() *lnwallet.SignDescriptor
// BuildWitness returns a valid witness allowing this output to be // BuildWitness returns a valid witness allowing this output to be
@ -41,8 +41,8 @@ type inputKit struct {
signDesc lnwallet.SignDescriptor signDesc lnwallet.SignDescriptor
} }
// OutPoint returns the breached output's identifier that is to be included as a // OutPoint returns the breached output's identifier that is to be included as
// transaction input. // a transaction input.
func (i *inputKit) OutPoint() *wire.OutPoint { func (i *inputKit) OutPoint() *wire.OutPoint {
return &i.outpoint return &i.outpoint
} }
@ -67,8 +67,7 @@ type BaseInput struct {
// MakeBaseInput assembles a new BaseInput that can be used to construct a // MakeBaseInput assembles a new BaseInput that can be used to construct a
// sweep transaction. // sweep transaction.
func MakeBaseInput(outpoint *wire.OutPoint, func MakeBaseInput(outpoint *wire.OutPoint, witnessType lnwallet.WitnessType,
witnessType lnwallet.WitnessType,
signDescriptor *lnwallet.SignDescriptor) BaseInput { signDescriptor *lnwallet.SignDescriptor) BaseInput {
return BaseInput{ return BaseInput{
@ -82,8 +81,8 @@ func MakeBaseInput(outpoint *wire.OutPoint,
// BuildWitness computes a valid witness that allows us to spend from the // BuildWitness computes a valid witness that allows us to spend from the
// breached output. It does so by generating the witness generation function, // breached output. It does so by generating the witness generation function,
// which is parameterized primarily by the witness type and sign descriptor. The // which is parameterized primarily by the witness type and sign descriptor.
// method then returns the witness computed by invoking this function. // The method then returns the witness computed by invoking this function.
func (bi *BaseInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx, func (bi *BaseInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) { hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
@ -102,8 +101,8 @@ func (bi *BaseInput) BlocksToMaturity() uint32 {
} }
// HtlcSucceedInput constitutes a sweep input that needs a pre-image. The input // 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 // is expected to reside on the commitment tx of the remote party and should
// be a second level tx output. // not be a second level tx output.
type HtlcSucceedInput struct { type HtlcSucceedInput struct {
inputKit inputKit
@ -127,8 +126,8 @@ func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
} }
// BuildWitness computes a valid witness that allows us to spend from the // 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 // breached output. For HtlcSpendInput it will need to make the preimage part
// the witness. // of the witness.
func (h *HtlcSucceedInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx, func (h *HtlcSucceedInput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx,
hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) { hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) {
@ -149,6 +148,7 @@ func (h *HtlcSucceedInput) BlocksToMaturity() uint32 {
return 0 return 0
} }
// Add compile-time constraint ensuring input structs implement Input interface. // Compile-time constraints to ensure each input struct implement the Input
// interface.
var _ Input = (*BaseInput)(nil) var _ Input = (*BaseInput)(nil)
var _ Input = (*HtlcSucceedInput)(nil) var _ Input = (*HtlcSucceedInput)(nil)

@ -5,9 +5,9 @@ import (
"github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/build"
) )
// log is a logger that is initialized with no output filters. This // log is a logger that is initialized with no output filters. This means the
// means the package will not perform any logging by default until the caller // package will not perform any logging by default until the caller requests
// requests it. // it.
var log btclog.Logger var log btclog.Logger
// The default amount of logging is none. // The default amount of logging is none.
@ -15,15 +15,15 @@ func init() {
UseLogger(build.NewSubLogger("SWPR", nil)) UseLogger(build.NewSubLogger("SWPR", nil))
} }
// DisableLog disables all library log output. Logging output is disabled // DisableLog disables all library log output. Logging output is disabled by
// by default until UseLogger is called. // default until UseLogger is called.
func DisableLog() { func DisableLog() {
UseLogger(btclog.Disabled) UseLogger(btclog.Disabled)
} }
// UseLogger uses a specified Logger to output package logging info. // UseLogger uses a specified Logger to output package logging info. This
// This should be used in preference to SetLogWriter if the caller is also // should be used in preference to SetLogWriter if the caller is also using
// using btclog. // btclog.
func UseLogger(logger btclog.Logger) { func UseLogger(logger btclog.Logger) {
log = logger log = logger
} }

@ -8,30 +8,27 @@ import (
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
) )
// UtxoSweeper provides the functionality to generate sweep txes. The plan is to // UtxoSweeper provides the functionality to generate sweep txes. The plan is
// extend UtxoSweeper in the future to also manage the actual sweeping process // to extend UtxoSweeper in the future to also manage the actual sweeping
// by itself. // process by itself.
type UtxoSweeper struct { type UtxoSweeper struct {
cfg *UtxoSweeperConfig cfg *UtxoSweeperConfig
} }
// UtxoSweeperConfig contains dependencies of UtxoSweeper. // UtxoSweeperConfig contains dependencies of UtxoSweeper.
type UtxoSweeperConfig struct { type UtxoSweeperConfig struct {
// GenSweepScript generates a P2WKH script belonging to the wallet where // GenSweepScript generates a P2WKH script belonging to the wallet
// funds can be swept. // where funds can be swept.
GenSweepScript func() ([]byte, error) GenSweepScript func() ([]byte, error)
// Estimator is used when crafting sweep transactions to estimate the // Estimator is used when crafting sweep transactions to estimate the
// necessary fee relative to the expected size of the sweep transaction. // necessary fee relative to the expected size of the sweep
// transaction.
Estimator lnwallet.FeeEstimator Estimator lnwallet.FeeEstimator
// Signer is used by the sweeper to generate valid witnesses at the // Signer is used by the sweeper to generate valid witnesses at the
// time the incubated outputs need to be spent. // time the incubated outputs need to be spent.
Signer lnwallet.Signer Signer lnwallet.Signer
// ConfTarget specifies a target for the number of blocks until an
// initial confirmation.
ConfTarget uint32
} }
// New returns a new UtxoSweeper instance. // New returns a new UtxoSweeper instance.
@ -45,18 +42,18 @@ func New(cfg *UtxoSweeperConfig) *UtxoSweeper {
// spends from them. This method also makes an accurate fee estimate before // spends from them. This method also makes an accurate fee estimate before
// generating the required witnesses. // generating the required witnesses.
// //
// The created transaction has a single output sending all the funds back to the // The created transaction has a single output sending all the funds back to
// source wallet, after accounting for the fee estimate. // the source wallet, after accounting for the fee estimate.
// //
// The value of currentBlockHeight argument will be set as the tx locktime. This // The value of currentBlockHeight argument will be set as the tx locktime.
// function assumes that all CLTV inputs will be unlocked after // This function assumes that all CLTV inputs will be unlocked after
// currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry // currentBlockHeight. Reasons not to use the maximum of all actual CLTV expiry
// values of the inputs: // values of the inputs:
// //
// - Make handling re-orgs easier. // - Make handling re-orgs easier.
// - Thwart future possible fee sniping attempts. // - Thwart future possible fee sniping attempts.
// - Make us blend in with the bitcoind wallet. // - Make us blend in with the bitcoind wallet.
func (s *UtxoSweeper) CreateSweepTx(inputs []Input, func (s *UtxoSweeper) CreateSweepTx(inputs []Input, confTarget uint32,
currentBlockHeight uint32) (*wire.MsgTx, error) { currentBlockHeight uint32) (*wire.MsgTx, error) {
// Generate the receiving script to which the funds will be swept. // Generate the receiving script to which the funds will be swept.
@ -66,7 +63,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
} }
// Using the txn weight estimate, compute the required txn fee. // Using the txn weight estimate, compute the required txn fee.
feePerKw, err := s.cfg.Estimator.EstimateFeePerKW(s.cfg.ConfTarget) feePerKw, err := s.cfg.Estimator.EstimateFeePerKW(confTarget)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,7 +97,6 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
// Add all inputs to the sweep transaction. Ensure that for each // Add all inputs to the sweep transaction. Ensure that for each
// csvInput, we set the sequence number properly. // csvInput, we set the sequence number properly.
for _, input := range inputs { for _, input := range inputs {
sweepTx.AddTxIn(&wire.TxIn{ sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(), PreviousOutPoint: *input.OutPoint(),
@ -110,9 +106,10 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
// Before signing the transaction, check to ensure that it meets some // Before signing the transaction, check to ensure that it meets some
// basic validity requirements. // 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 // TODO(conner): add more control to sanity checks, allowing us to
// if fees are too low. // delay spending "problem" outputs, e.g. possibly batching with other
// classes if fees are too low.
btx := btcutil.NewTx(sweepTx) btx := btcutil.NewTx(sweepTx)
if err := blockchain.CheckTransactionSanity(btx); err != nil { if err := blockchain.CheckTransactionSanity(btx); err != nil {
return nil, err return nil, err
@ -149,13 +146,11 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input,
// getWeightEstimate returns a weight estimate for the given inputs. // getWeightEstimate returns a weight estimate for the given inputs.
// Additionally, it returns counts for the number of csv and cltv inputs. // Additionally, it returns counts for the number of csv and cltv inputs.
func (s *UtxoSweeper) getWeightEstimate(inputs []Input) ([]Input, int64, int, int) { func (s *UtxoSweeper) getWeightEstimate(inputs []Input) ([]Input, int64, int, int) {
// We initialize a weight estimator so we can accurately asses the
// Create a transaction which sweeps all the newly mature outputs into // amount of fees we need to pay for this sweep transaction.
// an output controlled by the wallet. //
// TODO(roasbeef): can be more intelligent about buffering outputs to // TODO(roasbeef): can be more intelligent about buffering outputs to
// be more efficient on-chain. // be more efficient on-chain.
var weightEstimate lnwallet.TxWeightEstimator var weightEstimate lnwallet.TxWeightEstimator
// Our sweep transaction will pay to a single segwit p2wkh address, // Our sweep transaction will pay to a single segwit p2wkh address,
@ -165,10 +160,10 @@ func (s *UtxoSweeper) getWeightEstimate(inputs []Input) ([]Input, int64, int, in
// For each output, use its witness type to determine the estimate // For each output, use its witness type to determine the estimate
// weight of its witness, and add it to the proper set of spendable // weight of its witness, and add it to the proper set of spendable
// outputs. // outputs.
csvCount := 0 var (
cltvCount := 0 sweepInputs []Input
csvCount, cltvCount int
var sweepInputs []Input )
for i := range inputs { for i := range inputs {
input := inputs[i] input := inputs[i]

@ -4,11 +4,12 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"github.com/lightningnetwork/lnd/sweep"
"io" "io"
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/lightningnetwork/lnd/sweep"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
@ -182,6 +183,10 @@ type NurseryConfig struct {
// determining outputs in the chain as confirmed. // determining outputs in the chain as confirmed.
ConfDepth uint32 ConfDepth uint32
// SweepTxConfTarget assigns a confirmation target for sweep txes on
// which the fee calculation will be based.
SweepTxConfTarget uint32
// FetchClosedChannels provides access to a user's channels, such that // FetchClosedChannels provides access to a user's channels, such that
// they can be marked fully closed after incubation has concluded. // they can be marked fully closed after incubation has concluded.
FetchClosedChannels func(pendingOnly bool) ( FetchClosedChannels func(pendingOnly bool) (
@ -871,15 +876,15 @@ func (u *utxoNursery) graduateClass(classHeight uint32) error {
// generated a sweep txn for this height. Generate one if there // generated a sweep txn for this height. Generate one if there
// are kindergarten outputs or cltv crib outputs to be spent. // are kindergarten outputs or cltv crib outputs to be spent.
if len(kgtnOutputs) > 0 { if len(kgtnOutputs) > 0 {
sweepInputs := make([]sweep.Input, sweepInputs := make([]sweep.Input, len(kgtnOutputs))
len(kgtnOutputs))
for i := range kgtnOutputs { for i := range kgtnOutputs {
sweepInputs[i] = &kgtnOutputs[i] sweepInputs[i] = &kgtnOutputs[i]
} }
finalTx, err = u.cfg.Sweeper.CreateSweepTx( finalTx, err = u.cfg.Sweeper.CreateSweepTx(
sweepInputs, classHeight) sweepInputs, u.cfg.SweepTxConfTarget,
classHeight,
)
if err != nil { if err != nil {
utxnLog.Errorf("Failed to create sweep txn at "+ utxnLog.Errorf("Failed to create sweep txn at "+
"height=%d", classHeight) "height=%d", classHeight)