2018-12-06 11:37:54 +01:00
|
|
|
package sweep
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2018-12-07 08:36:58 +01:00
|
|
|
"sort"
|
2019-10-23 13:00:25 +02:00
|
|
|
"strings"
|
2018-12-07 08:36:58 +01:00
|
|
|
|
2018-12-06 11:37:54 +01:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2022-03-18 18:37:44 +01:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2018-12-06 11:37:54 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2019-01-16 15:47:43 +01:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2021-09-23 21:40:37 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2019-10-31 03:43:05 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2018-12-06 11:37:54 +01:00
|
|
|
)
|
|
|
|
|
2018-12-07 08:36:58 +01:00
|
|
|
var (
|
|
|
|
// DefaultMaxInputsPerTx specifies the default maximum number of inputs
|
|
|
|
// allowed in a single sweep tx. If more need to be swept, multiple txes
|
|
|
|
// are created and published.
|
|
|
|
DefaultMaxInputsPerTx = 100
|
|
|
|
)
|
|
|
|
|
2019-12-10 15:04:10 +01:00
|
|
|
// txInput is an interface that provides the input data required for tx
|
|
|
|
// generation.
|
|
|
|
type txInput interface {
|
|
|
|
input.Input
|
|
|
|
parameters() Params
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:36:58 +01:00
|
|
|
// inputSet is a set of inputs that can be used as the basis to generate a tx
|
|
|
|
// on.
|
2019-01-16 15:47:43 +01:00
|
|
|
type inputSet []input.Input
|
2018-12-07 08:36:58 +01:00
|
|
|
|
|
|
|
// generateInputPartitionings goes through all given inputs and constructs sets
|
|
|
|
// of inputs that can be used to generate a sensible transaction. Each set
|
|
|
|
// contains up to the configured maximum number of inputs. Negative yield
|
|
|
|
// inputs are skipped. No input sets with a total value after fees below the
|
|
|
|
// dust limit are returned.
|
2019-12-10 15:04:10 +01:00
|
|
|
func generateInputPartitionings(sweepableInputs []txInput,
|
2021-09-23 21:40:37 +02:00
|
|
|
feePerKW chainfee.SatPerKWeight, maxInputsPerTx int,
|
|
|
|
wallet Wallet) ([]inputSet, error) {
|
2018-12-07 08:36:58 +01:00
|
|
|
|
|
|
|
// Sort input by yield. We will start constructing input sets starting
|
|
|
|
// with the highest yield inputs. This is to prevent the construction
|
|
|
|
// of a set with an output below the dust limit, causing the sweep
|
|
|
|
// process to stop, while there are still higher value inputs
|
|
|
|
// available. It also allows us to stop evaluating more inputs when the
|
|
|
|
// first input in this ordering is encountered with a negative yield.
|
|
|
|
//
|
|
|
|
// Yield is calculated as the difference between value and added fee
|
|
|
|
// for this input. The fee calculation excludes fee components that are
|
|
|
|
// common to all inputs, as those wouldn't influence the order. The
|
|
|
|
// single component that is differentiating is witness size.
|
|
|
|
//
|
|
|
|
// For witness size, the upper limit is taken. The actual size depends
|
|
|
|
// on the signature length, which is not known yet at this point.
|
|
|
|
yields := make(map[wire.OutPoint]int64)
|
|
|
|
for _, input := range sweepableInputs {
|
2019-10-07 13:41:46 +02:00
|
|
|
size, _, err := input.WitnessType().SizeUpperBound()
|
2018-12-07 08:36:58 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"failed adding input weight: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
yields[*input.OutPoint()] = input.SignDesc().Output.Value -
|
|
|
|
int64(feePerKW.FeeForWeight(int64(size)))
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(sweepableInputs, func(i, j int) bool {
|
2019-12-09 15:40:05 +01:00
|
|
|
// Because of the specific ordering and termination condition
|
|
|
|
// that is described above, we place force sweeps at the start
|
|
|
|
// of the list. Otherwise we can't be sure that they will be
|
|
|
|
// included in an input set.
|
|
|
|
if sweepableInputs[i].parameters().Force {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:36:58 +01:00
|
|
|
return yields[*sweepableInputs[i].OutPoint()] >
|
|
|
|
yields[*sweepableInputs[j].OutPoint()]
|
|
|
|
})
|
|
|
|
|
|
|
|
// Select blocks of inputs up to the configured maximum number.
|
|
|
|
var sets []inputSet
|
|
|
|
for len(sweepableInputs) > 0 {
|
2019-12-10 15:04:10 +01:00
|
|
|
// Start building a set of positive-yield tx inputs under the
|
|
|
|
// condition that the tx will be published with the specified
|
|
|
|
// fee rate.
|
2021-09-23 21:40:37 +02:00
|
|
|
txInputs := newTxInputSet(wallet, feePerKW, maxInputsPerTx)
|
2018-12-07 08:36:58 +01:00
|
|
|
|
2019-12-10 15:04:10 +01:00
|
|
|
// From the set of sweepable inputs, keep adding inputs to the
|
|
|
|
// input set until the tx output value no longer goes up or the
|
|
|
|
// maximum number of inputs is reached.
|
|
|
|
txInputs.addPositiveYieldInputs(sweepableInputs)
|
|
|
|
|
|
|
|
// If there are no positive yield inputs, we can stop here.
|
|
|
|
inputCount := len(txInputs.inputs)
|
|
|
|
if inputCount == 0 {
|
2018-12-07 08:36:58 +01:00
|
|
|
return sets, nil
|
|
|
|
}
|
|
|
|
|
2019-12-10 16:06:45 +01:00
|
|
|
// Check the current output value and add wallet utxos if
|
|
|
|
// needed to push the output value to the lower limit.
|
|
|
|
if err := txInputs.tryAddWalletInputsIfNeeded(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-12-07 08:36:58 +01:00
|
|
|
// If the output value of this block of inputs does not reach
|
|
|
|
// the dust limit, stop sweeping. Because of the sorting,
|
|
|
|
// continuing with the remaining inputs will only lead to sets
|
2019-12-10 16:06:45 +01:00
|
|
|
// with an even lower output value.
|
2020-11-06 19:58:05 +01:00
|
|
|
if !txInputs.enoughInput() {
|
2021-09-23 21:40:37 +02:00
|
|
|
// The change output is always a p2wpkh here.
|
|
|
|
dl := lnwallet.DustLimitForSize(input.P2WPKHSize)
|
2020-11-06 19:58:05 +01:00
|
|
|
log.Debugf("Set value %v (r=%v, c=%v) below dust "+
|
|
|
|
"limit of %v", txInputs.totalOutput(),
|
|
|
|
txInputs.requiredOutput, txInputs.changeOutput,
|
2021-09-23 21:40:37 +02:00
|
|
|
dl)
|
2018-12-07 08:36:58 +01:00
|
|
|
return sets, nil
|
|
|
|
}
|
|
|
|
|
2019-12-10 16:06:45 +01:00
|
|
|
log.Infof("Candidate sweep set of size=%v (+%v wallet inputs), "+
|
|
|
|
"has yield=%v, weight=%v",
|
|
|
|
inputCount, len(txInputs.inputs)-inputCount,
|
2020-11-06 19:58:05 +01:00
|
|
|
txInputs.totalOutput()-txInputs.walletInputTotal,
|
|
|
|
txInputs.weightEstimate(true).weight())
|
2018-12-07 08:36:58 +01:00
|
|
|
|
2019-12-10 15:04:10 +01:00
|
|
|
sets = append(sets, txInputs.inputs)
|
|
|
|
sweepableInputs = sweepableInputs[inputCount:]
|
2018-12-07 08:36:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return sets, nil
|
|
|
|
}
|
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// createSweepTx builds a signed tx spending the inputs to the given outputs,
|
|
|
|
// sending any leftover change to the change script.
|
|
|
|
func createSweepTx(inputs []input.Input, outputs []*wire.TxOut,
|
|
|
|
changePkScript []byte, currentBlockHeight uint32,
|
2021-09-23 21:40:37 +02:00
|
|
|
feePerKw chainfee.SatPerKWeight, signer input.Signer) (*wire.MsgTx,
|
|
|
|
error) {
|
2021-01-14 11:49:27 +01:00
|
|
|
|
2022-03-18 18:37:46 +01:00
|
|
|
inputs, estimator := getWeightEstimate(
|
|
|
|
inputs, outputs, feePerKw, changePkScript,
|
|
|
|
)
|
2020-09-04 11:28:17 +02:00
|
|
|
txFee := estimator.fee()
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
var (
|
|
|
|
// Create the sweep transaction that we will be building. We
|
|
|
|
// use version 2 as it is required for CSV.
|
|
|
|
sweepTx = wire.NewMsgTx(2)
|
2020-11-06 19:35:01 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
// Track whether any of the inputs require a certain locktime.
|
|
|
|
locktime = int32(-1)
|
2020-11-06 19:35:01 +01:00
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
// We keep track of total input amount, and required output
|
|
|
|
// amount to use for calculating the change amount below.
|
2020-11-16 12:52:22 +01:00
|
|
|
totalInput btcutil.Amount
|
|
|
|
requiredOutput btcutil.Amount
|
2020-12-07 09:34:13 +01:00
|
|
|
|
|
|
|
// We'll add the inputs as we go so we know the final ordering
|
|
|
|
// of inputs to sign.
|
|
|
|
idxs []input.Input
|
2020-11-16 12:52:22 +01:00
|
|
|
)
|
2020-12-07 09:34:13 +01:00
|
|
|
|
|
|
|
// We start by adding all inputs that commit to an output. We do this
|
|
|
|
// since the input and output index must stay the same for the
|
|
|
|
// signatures to be valid.
|
2018-12-06 11:37:54 +01:00
|
|
|
for _, o := range inputs {
|
2020-11-16 12:52:22 +01:00
|
|
|
if o.RequiredTxOut() == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
idxs = append(idxs, o)
|
2020-11-06 19:35:01 +01:00
|
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: *o.OutPoint(),
|
|
|
|
Sequence: o.BlocksToMaturity(),
|
|
|
|
})
|
2020-11-16 12:52:22 +01:00
|
|
|
sweepTx.AddTxOut(o.RequiredTxOut())
|
2020-11-06 19:35:01 +01:00
|
|
|
|
|
|
|
if lt, ok := o.RequiredLockTime(); ok {
|
|
|
|
// If another input commits to a different locktime,
|
|
|
|
// they cannot be combined in the same transcation.
|
|
|
|
if locktime != -1 && locktime != int32(lt) {
|
|
|
|
return nil, fmt.Errorf("incompatible locktime")
|
|
|
|
}
|
|
|
|
|
|
|
|
locktime = int32(lt)
|
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
|
|
|
requiredOutput += btcutil.Amount(o.RequiredTxOut().Value)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// Sum up the value contained in the remaining inputs, and add them to
|
|
|
|
// the sweep transaction.
|
|
|
|
for _, o := range inputs {
|
|
|
|
if o.RequiredTxOut() != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
idxs = append(idxs, o)
|
2020-11-16 12:52:22 +01:00
|
|
|
sweepTx.AddTxIn(&wire.TxIn{
|
|
|
|
PreviousOutPoint: *o.OutPoint(),
|
|
|
|
Sequence: o.BlocksToMaturity(),
|
|
|
|
})
|
|
|
|
|
|
|
|
if lt, ok := o.RequiredLockTime(); ok {
|
|
|
|
if locktime != -1 && locktime != int32(lt) {
|
|
|
|
return nil, fmt.Errorf("incompatible locktime")
|
|
|
|
}
|
|
|
|
|
|
|
|
locktime = int32(lt)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalInput += btcutil.Amount(o.SignDesc().Output.Value)
|
|
|
|
}
|
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// Add the outputs given, if any.
|
|
|
|
for _, o := range outputs {
|
|
|
|
sweepTx.AddTxOut(o)
|
|
|
|
requiredOutput += btcutil.Amount(o.Value)
|
|
|
|
}
|
|
|
|
|
|
|
|
if requiredOutput+txFee > totalInput {
|
sweep: remove all unconfirmed descendant transactions when a sweep conflicts
Before this commit, we we were trying to sweep an anchor output, and
that output was spent by someone else (not the sweeper), then we would
report this back to the original resolver (allowing it to be cleaned
up), and also remove the set of inputs spent by that transaction from
the set we need to sweep.
However, it's possible that if a user is spending unconfirmed outputs,
then the wallet is holding onto an invalid transaction, as the outputs
that were used as inputs have been double spent elsewhere.
In this commit, we fix this issue by recursively removing all descendant
transactions of our past sweeps that have an intersecting input set as
the spending transaction. In cases where a user spent an unconfirmed
output to funding a channel, and that output was a descendant of the now
swept anchor output, the funds will now properly be marked as available.
Fixes #6241
2022-02-18 03:25:30 +01:00
|
|
|
return nil, fmt.Errorf("insufficient input to create sweep "+
|
|
|
|
"tx: input_sum=%v, output_sum=%v", totalInput,
|
|
|
|
requiredOutput+txFee)
|
2021-01-14 11:49:27 +01:00
|
|
|
}
|
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// The value remaining after the required output and fees, go to
|
|
|
|
// change. Not that this fee is what we would have to pay in case the
|
|
|
|
// sweep tx has a change output.
|
|
|
|
changeAmt := totalInput - requiredOutput - txFee
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2021-09-23 21:40:37 +02:00
|
|
|
// We'll calculate the dust limit for the given changePkScript since it
|
|
|
|
// is variable.
|
|
|
|
changeLimit := lnwallet.DustLimitForSize(len(changePkScript))
|
|
|
|
|
2020-11-06 19:35:01 +01:00
|
|
|
// The txn will sweep the amount after fees to the pkscript generated
|
|
|
|
// above.
|
2021-09-23 21:40:37 +02:00
|
|
|
if changeAmt >= changeLimit {
|
2020-11-06 19:44:12 +01:00
|
|
|
sweepTx.AddTxOut(&wire.TxOut{
|
2021-01-14 11:49:27 +01:00
|
|
|
PkScript: changePkScript,
|
2020-11-16 12:52:22 +01:00
|
|
|
Value: int64(changeAmt),
|
2020-11-06 19:44:12 +01:00
|
|
|
})
|
2020-12-09 12:24:01 +01:00
|
|
|
} else {
|
|
|
|
log.Infof("Change amt %v below dustlimit %v, not adding "+
|
2021-09-23 21:40:37 +02:00
|
|
|
"change output", changeAmt, changeLimit)
|
2020-11-06 19:44:12 +01:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-11-06 19:35:01 +01:00
|
|
|
// We'll default to using the current block height as locktime, if none
|
|
|
|
// of the inputs commits to a different locktime.
|
2018-12-06 11:37:54 +01:00
|
|
|
sweepTx.LockTime = currentBlockHeight
|
2020-11-06 19:35:01 +01:00
|
|
|
if locktime != -1 {
|
|
|
|
sweepTx.LockTime = uint32(locktime)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2022-03-18 18:37:44 +01:00
|
|
|
prevInputFetcher, err := input.MultiPrevOutFetcher(inputs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error creating prev input fetcher "+
|
|
|
|
"for hash cache: %v", err)
|
|
|
|
}
|
|
|
|
hashCache := txscript.NewTxSigHashes(sweepTx, prevInputFetcher)
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2018-11-18 05:48:41 +01:00
|
|
|
// With all the inputs in place, use each output's unique input script
|
2018-12-06 11:37:54 +01:00
|
|
|
// function to generate the final witness required for spending.
|
2019-01-16 15:47:43 +01:00
|
|
|
addInputScript := func(idx int, tso input.Input) error {
|
2018-11-18 05:48:41 +01:00
|
|
|
inputScript, err := tso.CraftInputScript(
|
2022-03-18 18:37:44 +01:00
|
|
|
signer, sweepTx, hashCache, prevInputFetcher, idx,
|
2018-12-06 11:37:54 +01:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-18 05:48:41 +01:00
|
|
|
sweepTx.TxIn[idx].Witness = inputScript.Witness
|
|
|
|
|
|
|
|
if len(inputScript.SigScript) != 0 {
|
|
|
|
sweepTx.TxIn[idx].SignatureScript = inputScript.SigScript
|
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:34:13 +01:00
|
|
|
for idx, inp := range idxs {
|
|
|
|
if err := addInputScript(idx, inp); err != nil {
|
2018-12-06 11:37:54 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-19 16:51:32 +01:00
|
|
|
log.Infof("Creating sweep transaction %v for %v inputs (%s) "+
|
2020-09-04 11:28:17 +02:00
|
|
|
"using %v sat/kw, tx_weight=%v, tx_fee=%v, parents_count=%v, "+
|
|
|
|
"parents_fee=%v, parents_weight=%v",
|
|
|
|
sweepTx.TxHash(), len(inputs),
|
|
|
|
inputTypeSummary(inputs), int64(feePerKw),
|
|
|
|
estimator.weight(), txFee,
|
|
|
|
len(estimator.parents), estimator.parentsFee,
|
|
|
|
estimator.parentsWeight,
|
|
|
|
)
|
2020-03-19 16:51:32 +01:00
|
|
|
|
2018-12-06 11:37:54 +01:00
|
|
|
return sweepTx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getWeightEstimate returns a weight estimate for the given inputs.
|
|
|
|
// Additionally, it returns counts for the number of csv and cltv inputs.
|
2021-01-14 11:49:27 +01:00
|
|
|
func getWeightEstimate(inputs []input.Input, outputs []*wire.TxOut,
|
2022-03-18 18:37:46 +01:00
|
|
|
feeRate chainfee.SatPerKWeight, outputPkScript []byte) ([]input.Input,
|
|
|
|
*weightEstimator) {
|
2020-09-04 11:28:17 +02:00
|
|
|
|
2018-12-06 11:37:54 +01:00
|
|
|
// We initialize a weight estimator so we can accurately asses the
|
|
|
|
// amount of fees we need to pay for this sweep transaction.
|
|
|
|
//
|
|
|
|
// TODO(roasbeef): can be more intelligent about buffering outputs to
|
|
|
|
// be more efficient on-chain.
|
2020-09-04 11:28:17 +02:00
|
|
|
weightEstimate := newWeightEstimator(feeRate)
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2021-01-14 11:49:27 +01:00
|
|
|
// Our sweep transaction will always pay to the given set of outputs.
|
|
|
|
for _, o := range outputs {
|
|
|
|
weightEstimate.addOutput(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there is any leftover change after paying to the given outputs
|
2022-03-18 18:37:46 +01:00
|
|
|
// and required outputs, it will go to a single segwit p2wkh or p2tr
|
|
|
|
// address. This will be our change address, so ensure it contributes to
|
|
|
|
// our weight estimate. Note that if we have other outputs, we might end
|
|
|
|
// up creating a sweep tx without a change output. It is okay to add the
|
2021-01-14 11:49:27 +01:00
|
|
|
// change output to the weight estimate regardless, since the estimated
|
|
|
|
// fee will just be subtracted from this already dust output, and
|
|
|
|
// trimmed.
|
2022-03-18 18:37:46 +01:00
|
|
|
if txscript.IsPayToTaproot(outputPkScript) {
|
|
|
|
weightEstimate.addP2TROutput()
|
|
|
|
} else {
|
|
|
|
weightEstimate.addP2WKHOutput()
|
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
|
|
|
// 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.
|
2019-10-23 13:00:25 +02:00
|
|
|
var sweepInputs []input.Input
|
2018-12-06 11:37:54 +01:00
|
|
|
for i := range inputs {
|
2019-01-16 15:47:43 +01:00
|
|
|
inp := inputs[i]
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2020-09-04 14:03:14 +02:00
|
|
|
err := weightEstimate.add(inp)
|
2018-12-06 11:37:54 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Warn(err)
|
|
|
|
|
|
|
|
// Skip inputs for which no weight estimate can be
|
|
|
|
// given.
|
|
|
|
continue
|
|
|
|
}
|
2019-10-23 13:00:25 +02:00
|
|
|
|
2020-11-16 12:52:22 +01:00
|
|
|
// If this input comes with a committed output, add that as
|
|
|
|
// well.
|
|
|
|
if inp.RequiredTxOut() != nil {
|
|
|
|
weightEstimate.addOutput(inp.RequiredTxOut())
|
|
|
|
}
|
|
|
|
|
2019-01-16 15:47:43 +01:00
|
|
|
sweepInputs = append(sweepInputs, inp)
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|
|
|
|
|
2020-09-04 11:28:17 +02:00
|
|
|
return sweepInputs, weightEstimate
|
2019-10-23 13:00:25 +02:00
|
|
|
}
|
2018-12-06 11:37:54 +01:00
|
|
|
|
2019-10-23 13:00:25 +02:00
|
|
|
// inputSummary returns a string containing a human readable summary about the
|
|
|
|
// witness types of a list of inputs.
|
|
|
|
func inputTypeSummary(inputs []input.Input) string {
|
2020-03-19 16:51:32 +01:00
|
|
|
// Sort inputs by witness type.
|
|
|
|
sortedInputs := make([]input.Input, len(inputs))
|
|
|
|
copy(sortedInputs, inputs)
|
|
|
|
sort.Slice(sortedInputs, func(i, j int) bool {
|
|
|
|
return sortedInputs[i].WitnessType().String() <
|
|
|
|
sortedInputs[j].WitnessType().String()
|
|
|
|
})
|
2019-10-23 13:00:25 +02:00
|
|
|
|
|
|
|
var parts []string
|
2020-03-19 16:51:32 +01:00
|
|
|
for _, i := range sortedInputs {
|
|
|
|
part := fmt.Sprintf("%v (%v)",
|
|
|
|
*i.OutPoint(), i.WitnessType())
|
|
|
|
|
2019-10-23 13:00:25 +02:00
|
|
|
parts = append(parts, part)
|
|
|
|
}
|
|
|
|
return strings.Join(parts, ", ")
|
2018-12-06 11:37:54 +01:00
|
|
|
}
|