From 9fcc7ee390270d5502a7cd229611a8ab723e9e58 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Fri, 21 Sep 2018 10:49:58 -0700 Subject: [PATCH 1/6] utxonursery: move spendable output structs to sweep package This commit moves the output structs to a new package as a preparation for moving more logic into that package. --- breacharbiter.go | 38 +++++-------------------------- sweep/input.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ utxonursery.go | 42 +++++++++-------------------------- 3 files changed, 74 insertions(+), 64 deletions(-) create mode 100644 sweep/input.go diff --git a/breacharbiter.go b/breacharbiter.go index df19bff1..c2b24bd3 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -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. @@ -852,7 +826,7 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx, // Add compile-time constraint ensuring breachedOutput implements // SpendableOutput. -var _ SpendableOutput = (*breachedOutput)(nil) +var _ sweep.SpendableOutput = (*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 +937,13 @@ func (b *breachArbiter) createJusticeTx( // outputs, while simultaneously computing the estimated weight of the // transaction. var ( - spendableOutputs []SpendableOutput + spendableOutputs []sweep.SpendableOutput 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.SpendableOutput, 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 +997,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.SpendableOutput) (*wire.MsgTx, error) { // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. @@ -1085,7 +1059,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.SpendableOutput) error { // First, we construct a valid witness for this outpoint and // transaction using the SpendableOutput's witness generation // function. diff --git a/sweep/input.go b/sweep/input.go new file mode 100644 index 00000000..aab96fa3 --- /dev/null +++ b/sweep/input.go @@ -0,0 +1,58 @@ +package sweep + +import ( + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/lnwallet" +) + +// 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) +} + +// 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 +} diff --git a/utxonursery.go b/utxonursery.go index 8eed7cb5..37ebdf11 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/lightningnetwork/lnd/sweep" "io" "sync" "sync/atomic" @@ -953,14 +954,14 @@ func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput, // outputs are CLTV locked outputs that have had their timelocks // expire. var ( - csvOutputs []CsvSpendableOutput - cltvOutputs []SpendableOutput + csvOutputs []sweep.CsvSpendableOutput + cltvOutputs []sweep.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)) + 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. @@ -1028,8 +1029,8 @@ func (u *utxoNursery) createSweepTx(kgtnOutputs []kidOutput, // 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) { + 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() @@ -1099,7 +1100,7 @@ func (u *utxoNursery) populateSweepTx(txWeight int64, classHeight uint32, // 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 { + addWitness := func(idx int, tso sweep.SpendableOutput) error { witness, err := tso.BuildWitness( u.cfg.Signer, sweepTx, hashCache, idx, ) @@ -1635,29 +1636,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 @@ -1971,5 +1949,5 @@ func readTxOut(r io.Reader, txo *wire.TxOut) error { // Compile-time constraint to ensure kidOutput and babyOutput implement the // CsvSpendableOutput interface. -var _ CsvSpendableOutput = (*kidOutput)(nil) -var _ CsvSpendableOutput = (*babyOutput)(nil) +var _ sweep.CsvSpendableOutput = (*kidOutput)(nil) +var _ sweep.CsvSpendableOutput = (*babyOutput)(nil) From 4dab4056237597055bc58bac0e01751b6659fd61 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 25 Sep 2018 22:03:16 -0700 Subject: [PATCH 2/6] 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. --- log.go | 10 +- server.go | 26 +++-- sweep/log.go | 45 ++++++++ sweep/sweeper.go | 249 ++++++++++++++++++++++++++++++++++++++++++++ utxonursery.go | 224 +++------------------------------------ utxonursery_test.go | 15 ++- 6 files changed, 341 insertions(+), 228 deletions(-) create mode 100644 sweep/log.go create mode 100644 sweep/sweeper.go diff --git a/log.go b/log.go index 1fe1b658..a041cbe8 100644 --- a/log.go +++ b/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 diff --git a/server.go b/server.go index 6c2ee8ba..a3b52904 100644 --- a/server.go +++ b/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. diff --git a/sweep/log.go b/sweep/log.go new file mode 100644 index 00000000..a5112dfb --- /dev/null +++ b/sweep/log.go @@ -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) +} diff --git a/sweep/sweeper.go b/sweep/sweeper.go new file mode 100644 index 00000000..db39838c --- /dev/null +++ b/sweep/sweeper.go @@ -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 +} diff --git a/utxonursery.go b/utxonursery.go index 37ebdf11..15783bfe 100644 --- a/utxonursery.go +++ b/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 diff --git a/utxonursery_test.go b/utxonursery_test.go index 0d5668a7..a7249495 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -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) From 7d69df77edc5a55c7c659cb45f9ed3a81596ab45 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 26 Sep 2018 08:59:30 -0700 Subject: [PATCH 3/6] sweep: create new Input interface This commit introduces a common interface for sweep inputs. It eliminates the type checking from UtxoSweeper. Also the formerly present Amount() getter is removed. It was redundant because this value is present in SignDesc().Output.Value as well. --- breacharbiter.go | 23 +++++++----- sweep/input.go | 93 ++++++++++++++++++++++++++++++++++-------------- sweep/sweeper.go | 69 +++++++++++------------------------ utxonursery.go | 14 ++++---- 4 files changed, 109 insertions(+), 90 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index c2b24bd3..3a68220b 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -824,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 _ sweep.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 @@ -937,13 +944,13 @@ func (b *breachArbiter) createJusticeTx( // outputs, while simultaneously computing the estimated weight of the // transaction. var ( - spendableOutputs []sweep.SpendableOutput + spendableOutputs []sweep.Input weightEstimate lnwallet.TxWeightEstimator ) // Allocate enough space to potentially hold each of the breached // outputs in the retribution info. - spendableOutputs = make([]sweep.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, @@ -997,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 ...sweep.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. @@ -1011,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 @@ -1059,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 sweep.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. diff --git a/sweep/input.go b/sweep/input.go index aab96fa3..29d27098 100644 --- a/sweep/input.go +++ b/sweep/input.go @@ -3,16 +3,11 @@ package sweep import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/lnwallet" ) -// 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 - +// 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 @@ -32,27 +27,73 @@ type SpendableOutput interface { BuildWitness(signer lnwallet.Signer, txn *wire.MsgTx, hashCache *txscript.TxSigHashes, txinIdx int) ([][]byte, error) -} - -// 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. + // the output can be spent. For non-CSV locked inputs this is always + // zero. BlocksToMaturity() uint32 - - // OriginChanPoint returns the outpoint of the channel from which this - // output is derived. - OriginChanPoint() *wire.OutPoint } + +// BaseInput contains all the information needed to sweep an output. +type BaseInput struct { + outpoint wire.OutPoint + witnessType lnwallet.WitnessType + signDesc lnwallet.SignDescriptor +} + +// 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{ + outpoint: *outpoint, + witnessType: witnessType, + signDesc: *signDescriptor, + } +} + +// OutPoint returns the breached output's identifier that is to be included as a +// transaction input. +func (bi *BaseInput) OutPoint() *wire.OutPoint { + return &bi.outpoint +} + +// WitnessType returns the type of witness that must be generated to spend the +// breached output. +func (bi *BaseInput) WitnessType() lnwallet.WitnessType { + return bi.witnessType +} + +// SignDesc returns the breached output's SignDescriptor, which is used during +// signing to compute the witness. +func (bi *BaseInput) SignDesc() *lnwallet.SignDescriptor { + return &bi.signDesc +} + +// 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 +} + +// Add compile-time constraint ensuring BaseInput implements +// SpendableOutput. +var _ Input = (*BaseInput)(nil) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index db39838c..7e2cc916 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -53,7 +53,7 @@ func New(cfg *UtxoSweeperConfig) *UtxoSweeper { // - 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, +func (s *UtxoSweeper) CreateSweepTx(inputs []Input, currentBlockHeight uint32) (*wire.MsgTx, error) { // Create a transaction which sweeps all the newly mature outputs into @@ -62,18 +62,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, // 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)) + var weightEstimate lnwallet.TxWeightEstimator // Our sweep transaction will pay to a single segwit p2wkh address, // ensure it contributes to our weight estimate. @@ -82,6 +71,8 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, // 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] @@ -93,7 +84,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, weightEstimate.AddWitnessInput( lnwallet.ToLocalTimeoutWitnessSize, ) - csvOutputs = append(csvOutputs, input) + csvCount++ // Outgoing second layer HTLC's that have confirmed within the // chain, and the output they produced is now mature enough to @@ -102,7 +93,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, weightEstimate.AddWitnessInput( lnwallet.ToLocalTimeoutWitnessSize, ) - csvOutputs = append(csvOutputs, input) + csvCount++ // Incoming second layer HTLC's that have confirmed within the // chain, and the output they produced is now mature enough to @@ -111,7 +102,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, weightEstimate.AddWitnessInput( lnwallet.ToLocalTimeoutWitnessSize, ) - csvOutputs = append(csvOutputs, input) + csvCount++ // An HTLC on the commitment transaction of the remote party, // that has had its absolute timelock expire. @@ -119,9 +110,11 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, weightEstimate.AddWitnessInput( lnwallet.AcceptedHtlcTimeoutWitnessSize, ) - cltvOutputs = append(cltvOutputs, input) + cltvCount++ default: + // TODO: Also add non-timelocked outputs + log.Warnf("kindergarten output in nursery store "+ "contains unexpected witness type: %v", input.WitnessType()) @@ -129,11 +122,11 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, } } - log.Infof("Creating sweep transaction for %v CSV inputs, %v CLTV "+ - "inputs", len(csvOutputs), len(cltvOutputs)) + log.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV)", + csvCount+cltvCount, csvCount, cltvCount) txWeight := int64(weightEstimate.Weight()) - return s.populateSweepTx(txWeight, currentBlockHeight, csvOutputs, cltvOutputs) + return s.populateSweepTx(txWeight, currentBlockHeight, inputs) } // populateSweepTx populate the final sweeping transaction with all witnesses @@ -141,8 +134,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []CsvSpendableOutput, // 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) { + inputs []Input) (*wire.MsgTx, error) { // Generate the receiving script to which the funds will be swept. pkScript, err := s.cfg.GenSweepScript() @@ -152,11 +144,8 @@ func (s *UtxoSweeper) populateSweepTx(txWeight int64, currentBlockHeight uint32, // 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() + for _, o := range inputs { + totalSum += btcutil.Amount(o.SignDesc().Output.Value) } // Using the txn weight estimate, compute the required txn fee. @@ -181,25 +170,16 @@ func (s *UtxoSweeper) populateSweepTx(txWeight int64, currentBlockHeight uint32, 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 - } + 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 { + for _, input := range inputs { 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. @@ -215,7 +195,7 @@ func (s *UtxoSweeper) populateSweepTx(txWeight int64, currentBlockHeight uint32, // 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 { + addWitness := func(idx int, tso Input) error { witness, err := tso.BuildWitness( s.cfg.Signer, sweepTx, hashCache, idx, ) @@ -230,20 +210,11 @@ func (s *UtxoSweeper) populateSweepTx(txWeight int64, currentBlockHeight uint32, // Finally we'll attach a valid witness to each csv and cltv input // within the sweeping transaction. - for i, input := range csvInputs { + for i, input := range inputs { 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 } diff --git a/utxonursery.go b/utxonursery.go index 15783bfe..8557ad55 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -871,14 +871,14 @@ 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 { - csvSpendableOutputs := make([]sweep.CsvSpendableOutput, + sweepInputs := make([]sweep.Input, len(kgtnOutputs)) for i := range kgtnOutputs { - csvSpendableOutputs[i] = &kgtnOutputs[i] + sweepInputs[i] = &kgtnOutputs[i] } finalTx, err = u.cfg.Sweeper.CreateSweepTx( - csvSpendableOutputs, classHeight) + sweepInputs, classHeight) if err != nil { utxnLog.Errorf("Failed to create sweep txn at "+ @@ -1749,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 _ sweep.CsvSpendableOutput = (*kidOutput)(nil) -var _ sweep.CsvSpendableOutput = (*babyOutput)(nil) +// Compile-time constraint to ensure kidOutput implements the +// Input interface. + +var _ sweep.Input = (*kidOutput)(nil) From c1d845aa0d891098e5e9b7026f1c271699b64b08 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 26 Sep 2018 09:46:48 -0700 Subject: [PATCH 4/6] cnct: reuse sweep tx logic for commit resolver Removes duplicate sweep tx code from commit resolver and delegates generation to UtxoSweeper in the sweep package. --- contractcourt/chain_arbitrator.go | 4 +++ contractcourt/contract_resolvers.go | 49 ++++++----------------------- server.go | 1 + sweep/sweeper.go | 11 +++++-- 4 files changed, 23 insertions(+), 42 deletions(-) diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index db401735..5f8b161d 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -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 diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 3ecc6be9..09a1cecf 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "encoding/binary" "fmt" + "github.com/lightningnetwork/lnd/sweep" "io" "io/ioutil" @@ -1256,46 +1257,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 } diff --git a/server.go b/server.go index a3b52904..7ebe5189 100644 --- a/server.go +++ b/server.go @@ -695,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{ diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 7e2cc916..3a2197e3 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -73,11 +73,17 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, // outputs. csvCount := 0 cltvCount := 0 + unknownCount := 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: @@ -113,8 +119,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, cltvCount++ default: - // TODO: Also add non-timelocked outputs - + unknownCount++ log.Warnf("kindergarten output in nursery store "+ "contains unexpected witness type: %v", input.WitnessType()) @@ -123,7 +128,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, } log.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV)", - csvCount+cltvCount, csvCount, cltvCount) + len(inputs)-unknownCount, csvCount, cltvCount) txWeight := int64(weightEstimate.Weight()) return s.populateSweepTx(txWeight, currentBlockHeight, inputs) From 6977d59e3590b7a68ccda63bf711f705c506e60c Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 26 Sep 2018 11:00:10 -0700 Subject: [PATCH 5/6] cnct: reuse sweep tx logic for success resolver --- contractcourt/contract_resolvers.go | 63 +++-------------- sweep/input.go | 105 +++++++++++++++++++++------- sweep/sweeper.go | 7 ++ 3 files changed, 98 insertions(+), 77 deletions(-) diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index 09a1cecf..56e035e6 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -9,7 +9,6 @@ import ( "io" "io/ioutil" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" @@ -447,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 } diff --git a/sweep/input.go b/sweep/input.go index 29d27098..795adc09 100644 --- a/sweep/input.go +++ b/sweep/input.go @@ -35,13 +35,36 @@ type Input interface { BlocksToMaturity() uint32 } -// BaseInput contains all the information needed to sweep an output. -type BaseInput struct { +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, @@ -49,30 +72,14 @@ func MakeBaseInput(outpoint *wire.OutPoint, signDescriptor *lnwallet.SignDescriptor) BaseInput { return BaseInput{ - outpoint: *outpoint, - witnessType: witnessType, - signDesc: *signDescriptor, + inputKit{ + outpoint: *outpoint, + witnessType: witnessType, + signDesc: *signDescriptor, + }, } } -// OutPoint returns the breached output's identifier that is to be included as a -// transaction input. -func (bi *BaseInput) OutPoint() *wire.OutPoint { - return &bi.outpoint -} - -// WitnessType returns the type of witness that must be generated to spend the -// breached output. -func (bi *BaseInput) WitnessType() lnwallet.WitnessType { - return bi.witnessType -} - -// SignDesc returns the breached output's SignDescriptor, which is used during -// signing to compute the witness. -func (bi *BaseInput) SignDesc() *lnwallet.SignDescriptor { - return &bi.signDesc -} - // 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 @@ -94,6 +101,54 @@ func (bi *BaseInput) BlocksToMaturity() uint32 { return 0 } -// Add compile-time constraint ensuring BaseInput implements -// SpendableOutput. +// 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) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 3a2197e3..a4c3f498 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -118,6 +118,13 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, ) 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: unknownCount++ log.Warnf("kindergarten output in nursery store "+ From 95bf858ac3fe1ce93a8faeb01811eb2e0eaea352 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 17 Oct 2018 12:43:02 +0200 Subject: [PATCH 6/6] sweep: refactor functions and unify tx info logging --- sweep/sweeper.go | 196 ++++++++++++++++++++++++----------------------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index a4c3f498..0bd101b2 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -41,10 +41,13 @@ func New(cfg *UtxoSweeperConfig) *UtxoSweeper { } } -// CreateSweepTx accepts a list of outputs and signs and generates a txn that +// 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 @@ -56,6 +59,98 @@ func New(cfg *UtxoSweeperConfig) *UtxoSweeper { 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. @@ -73,7 +168,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, // outputs. csvCount := 0 cltvCount := 0 - unknownCount := 0 + for i := range inputs { input := inputs[i] @@ -126,7 +221,6 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, ) default: - unknownCount++ log.Warnf("kindergarten output in nursery store "+ "contains unexpected witness type: %v", input.WitnessType()) @@ -134,99 +228,7 @@ func (s *UtxoSweeper) CreateSweepTx(inputs []Input, } } - log.Infof("Creating sweep transaction for %v inputs (%v CSV, %v CLTV)", - len(inputs)-unknownCount, csvCount, cltvCount) - txWeight := int64(weightEstimate.Weight()) - return s.populateSweepTx(txWeight, currentBlockHeight, inputs) -} - -// 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, - inputs []Input) (*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 inputs { - totalSum += btcutil.Amount(o.SignDesc().Output.Value) - } - - // 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, - }) - - 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 + + return txWeight, csvCount, cltvCount }