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.
This commit is contained in:
Joost Jager 2018-09-26 08:59:30 -07:00
parent 4dab405623
commit 7d69df77ed
No known key found for this signature in database
GPG Key ID: AE6B0D042C8E38D9
4 changed files with 109 additions and 90 deletions

View File

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

View File

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

View File

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

View File

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