From 7d69df77edc5a55c7c659cb45f9ed3a81596ab45 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Wed, 26 Sep 2018 08:59:30 -0700 Subject: [PATCH] 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)