sweep/tx_input_set: account for required outputs in input set
If inputs require outputs to be added at the same time, this will change the weight and amount calculations, so we must account for that. We wait to get the weight estimator for the sweep tx until needed, such that we can easily choose whether to include a change output or not in the estimate. This is needed for the case where the second level transactions can pay for their own fee, so no change output is needed.
This commit is contained in:
4 files changed
+238
-55
No files matched your search
+94
-28
@@ -31,15 +31,22 @@ const (
|
||||
)
|
||||
|
||||
type txInputSetState struct {
|
||||
// weightEstimate is the (worst case) tx weight with the current set of
|
||||
// inputs.
|
||||
weightEstimate *weightEstimator
|
||||
// feeRate is the fee rate to use for the sweep transaction.
|
||||
feeRate chainfee.SatPerKWeight
|
||||
|
||||
// inputTotal is the total value of all inputs.
|
||||
inputTotal btcutil.Amount
|
||||
|
||||
// outputValue is the value of the tx output.
|
||||
outputValue btcutil.Amount
|
||||
// requiredOutput is the sum of the outputs committed to by the inputs.
|
||||
requiredOutput btcutil.Amount
|
||||
|
||||
// changeOutput is the value of the change output. This will be what is
|
||||
// left over after subtracting the requiredOutput and the tx fee from
|
||||
// the inputTotal.
|
||||
//
|
||||
// NOTE: This might be below the dust limit, or even negative since it
|
||||
// is the change remaining in csse we pay the fee for a change output.
|
||||
changeOutput btcutil.Amount
|
||||
|
||||
// inputs is the set of tx inputs.
|
||||
inputs []input.Input
|
||||
@@ -52,11 +59,42 @@ type txInputSetState struct {
|
||||
force bool
|
||||
}
|
||||
|
||||
// weightEstimate is the (worst case) tx weight with the current set of
|
||||
// inputs. It takes a parameter whether to add a change output or not.
|
||||
func (t *txInputSetState) weightEstimate(change bool) *weightEstimator {
|
||||
weightEstimate := newWeightEstimator(t.feeRate)
|
||||
for _, i := range t.inputs {
|
||||
// Can ignore error, because it has already been checked when
|
||||
// calculating the yields.
|
||||
_ = weightEstimate.add(i)
|
||||
|
||||
r := i.RequiredTxOut()
|
||||
if r != nil {
|
||||
weightEstimate.addOutput(r)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a change output to the weight estimate if requested.
|
||||
if change {
|
||||
weightEstimate.addP2WKHOutput()
|
||||
}
|
||||
|
||||
return weightEstimate
|
||||
}
|
||||
|
||||
// totalOutput is the total amount left for us after paying fees.
|
||||
//
|
||||
// NOTE: This might be dust.
|
||||
func (t *txInputSetState) totalOutput() btcutil.Amount {
|
||||
return t.requiredOutput + t.changeOutput
|
||||
}
|
||||
|
||||
func (t *txInputSetState) clone() txInputSetState {
|
||||
s := txInputSetState{
|
||||
weightEstimate: t.weightEstimate.clone(),
|
||||
feeRate: t.feeRate,
|
||||
inputTotal: t.inputTotal,
|
||||
outputValue: t.outputValue,
|
||||
changeOutput: t.changeOutput,
|
||||
requiredOutput: t.requiredOutput,
|
||||
walletInputTotal: t.walletInputTotal,
|
||||
force: t.force,
|
||||
inputs: make([]input.Input, len(t.inputs)),
|
||||
@@ -97,7 +135,7 @@ func newTxInputSet(wallet Wallet, feePerKW,
|
||||
dustLimit := dustLimit(relayFee)
|
||||
|
||||
state := txInputSetState{
|
||||
weightEstimate: newWeightEstimator(feePerKW),
|
||||
feeRate: feePerKW,
|
||||
}
|
||||
|
||||
b := txInputSet{
|
||||
@@ -107,16 +145,36 @@ func newTxInputSet(wallet Wallet, feePerKW,
|
||||
txInputSetState: state,
|
||||
}
|
||||
|
||||
// Add the sweep tx output to the weight estimate.
|
||||
b.weightEstimate.addP2WKHOutput()
|
||||
|
||||
return &b
|
||||
}
|
||||
|
||||
// dustLimitReached returns true if we've accumulated enough inputs to meet the
|
||||
// dust limit.
|
||||
func (t *txInputSet) dustLimitReached() bool {
|
||||
return t.outputValue >= t.dustLimit
|
||||
// enoughInput returns true if we've accumulated enough inputs to pay the fees
|
||||
// and have at least one output that meets the dust limit.
|
||||
func (t *txInputSet) enoughInput() bool {
|
||||
// If we have a change output above dust, then we certainly have enough
|
||||
// inputs to the transaction.
|
||||
if t.changeOutput >= t.dustLimit {
|
||||
return true
|
||||
}
|
||||
|
||||
// We did not have enough input for a change output. Check if we have
|
||||
// enough input to pay the fees for a transaction with no change
|
||||
// output.
|
||||
fee := t.weightEstimate(false).fee()
|
||||
if t.inputTotal < t.requiredOutput+fee {
|
||||
return false
|
||||
}
|
||||
|
||||
// We could pay the fees, but we still need at least one output to be
|
||||
// above the dust limit for the tx to be valid (we assume that these
|
||||
// required outputs only get added if they are above dust)
|
||||
for _, inp := range t.inputs {
|
||||
if inp.RequiredTxOut() != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// add adds a new input to the set. It returns a bool indicating whether the
|
||||
@@ -131,28 +189,35 @@ func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *tx
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the input comes with a required tx out that is below dust, we
|
||||
// won't add it.
|
||||
reqOut := inp.RequiredTxOut()
|
||||
if reqOut != nil && btcutil.Amount(reqOut.Value) < t.dustLimit {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone the current set state.
|
||||
s := t.clone()
|
||||
|
||||
// Add the new input.
|
||||
s.inputs = append(s.inputs, inp)
|
||||
|
||||
// Can ignore error, because it has already been checked when
|
||||
// calculating the yields.
|
||||
_ = s.weightEstimate.add(inp)
|
||||
|
||||
// Add the value of the new input.
|
||||
value := btcutil.Amount(inp.SignDesc().Output.Value)
|
||||
s.inputTotal += value
|
||||
|
||||
// Recalculate the tx fee.
|
||||
fee := s.weightEstimate.fee()
|
||||
fee := s.weightEstimate(true).fee()
|
||||
|
||||
// Calculate the new output value.
|
||||
s.outputValue = s.inputTotal - fee
|
||||
if reqOut != nil {
|
||||
s.requiredOutput += btcutil.Amount(reqOut.Value)
|
||||
}
|
||||
s.changeOutput = s.inputTotal - s.requiredOutput - fee
|
||||
|
||||
// Calculate the yield of this input from the change in tx output value.
|
||||
inputYield := s.outputValue - t.outputValue
|
||||
// Calculate the yield of this input from the change in total tx output
|
||||
// value.
|
||||
inputYield := s.totalOutput() - t.totalOutput()
|
||||
|
||||
switch constraints {
|
||||
|
||||
@@ -192,11 +257,11 @@ func (t *txInputSet) addToState(inp input.Input, constraints addConstraints) *tx
|
||||
// value of the wallet input and what we get out of this
|
||||
// transaction. To prevent attaching and locking a big utxo for
|
||||
// very little benefit.
|
||||
if !s.force && s.walletInputTotal >= s.outputValue {
|
||||
if !s.force && s.walletInputTotal >= s.totalOutput() {
|
||||
log.Debugf("Rejecting wallet input of %v, because it "+
|
||||
"would make a negative yielding transaction "+
|
||||
"(%v)",
|
||||
value, s.outputValue-s.walletInputTotal)
|
||||
value, s.totalOutput()-s.walletInputTotal)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -250,8 +315,9 @@ func (t *txInputSet) addPositiveYieldInputs(sweepableInputs []txInput) {
|
||||
// tryAddWalletInputsIfNeeded retrieves utxos from the wallet and tries adding as
|
||||
// many as required to bring the tx output value above the given minimum.
|
||||
func (t *txInputSet) tryAddWalletInputsIfNeeded() error {
|
||||
// If we've already reached the dust limit, no action is needed.
|
||||
if t.dustLimitReached() {
|
||||
// If we've already have enough to pay the transaction fees and have at
|
||||
// least one output materialize, no action is needed.
|
||||
if t.enoughInput() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -275,7 +341,7 @@ func (t *txInputSet) tryAddWalletInputsIfNeeded() error {
|
||||
}
|
||||
|
||||
// Return if we've reached the minimum output amount.
|
||||
if t.dustLimitReached() {
|
||||
if t.enoughInput() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user