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)