sweep: broadcast sweep transactions in descending fee rate order
In this commit, we address another issue that arose with the introduction of the fee rate buckets. We'll use an example to explain the problem space: Let's say we have inputs A, B, and C within the same fee rate bucket. If A's fee rate is bumped to a higher bucket, then it's currently possible for the lower fee rate bucket to be swept first, which would produce an invalid RBF transaction since we're removing an input from the original without providing a higher fee. By the time we get to the higher fee rate bucket, we broadcast a valid RBF transaction _only_ sweeping input A, which would evict the transaction sweeping inputs B and C from the mempool. To prevent this eviction, we can simply broadcast the higher fee rate sweep transactions first, to ensure we have valid RBF transactions.
This commit is contained in:
parent
5172a5e255
commit
682aebdd53
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -488,8 +489,17 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch,
|
|||||||
s.timer = nil
|
s.timer = nil
|
||||||
|
|
||||||
// We'll attempt to cluster all of our inputs with
|
// We'll attempt to cluster all of our inputs with
|
||||||
// similar fee rates.
|
// similar fee rates. Before attempting to sweep them,
|
||||||
for _, cluster := range s.clusterBySweepFeeRate() {
|
// we'll sort them in descending fee rate order. We do
|
||||||
|
// this to ensure any inputs which have had their fee
|
||||||
|
// rate bumped are broadcast first in order enforce the
|
||||||
|
// RBF policy.
|
||||||
|
inputClusters := s.clusterBySweepFeeRate()
|
||||||
|
sort.Slice(inputClusters, func(i, j int) bool {
|
||||||
|
return inputClusters[i].sweepFeeRate >
|
||||||
|
inputClusters[j].sweepFeeRate
|
||||||
|
})
|
||||||
|
for _, cluster := range inputClusters {
|
||||||
// Examine pending inputs and try to construct
|
// Examine pending inputs and try to construct
|
||||||
// lists of inputs.
|
// lists of inputs.
|
||||||
inputLists, err := s.getInputLists(
|
inputLists, err := s.getInputLists(
|
||||||
|
@ -975,7 +975,8 @@ func TestGiveUp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestDifferentFeePreferences ensures that the sweeper can have different
|
// TestDifferentFeePreferences ensures that the sweeper can have different
|
||||||
// transactions for different fee preferences.
|
// transactions for different fee preferences. These transactions should be
|
||||||
|
// broadcast from highest to lowest fee rate.
|
||||||
func TestDifferentFeePreferences(t *testing.T) {
|
func TestDifferentFeePreferences(t *testing.T) {
|
||||||
ctx := createSweeperTestContext(t)
|
ctx := createSweeperTestContext(t)
|
||||||
|
|
||||||
@ -1009,27 +1010,25 @@ func TestDifferentFeePreferences(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the sweeper's batch ticker, which should cause the sweep
|
// Start the sweeper's batch ticker, which should cause the sweep
|
||||||
// transactions to be broadcast.
|
// transactions to be broadcast in order of high to low fee preference.
|
||||||
ctx.tick()
|
ctx.tick()
|
||||||
|
|
||||||
ctx.receiveTx()
|
// The first transaction broadcast should be the one spending the higher
|
||||||
ctx.receiveTx()
|
// fee rate inputs.
|
||||||
|
sweepTx1 := ctx.receiveTx()
|
||||||
|
assertTxSweepsInputs(t, &sweepTx1, input1, input2)
|
||||||
|
|
||||||
|
// The second should be the one spending the lower fee rate inputs.
|
||||||
|
sweepTx2 := ctx.receiveTx()
|
||||||
|
assertTxSweepsInputs(t, &sweepTx2, input3)
|
||||||
|
|
||||||
// With the transactions broadcast, we'll mine a block to so that the
|
// With the transactions broadcast, we'll mine a block to so that the
|
||||||
// result is delivered to each respective client.
|
// result is delivered to each respective client.
|
||||||
ctx.backend.mine()
|
ctx.backend.mine()
|
||||||
|
resultChans := []chan Result{resultChan1, resultChan2, resultChan3}
|
||||||
// We should expect to see a single transaction that sweeps the high fee
|
for _, resultChan := range resultChans {
|
||||||
// preference inputs.
|
ctx.expectResult(resultChan, nil)
|
||||||
sweepTx1 := receiveSpendTx(t, resultChan1)
|
}
|
||||||
assertTxSweepsInputs(t, sweepTx1, input1, input2)
|
|
||||||
sweepTx2 := receiveSpendTx(t, resultChan2)
|
|
||||||
assertTxSweepsInputs(t, sweepTx2, input1, input2)
|
|
||||||
|
|
||||||
// We should expect to see a distinct transaction that sweeps the low
|
|
||||||
// fee preference inputs.
|
|
||||||
sweepTx3 := receiveSpendTx(t, resultChan3)
|
|
||||||
assertTxSweepsInputs(t, sweepTx3, input3)
|
|
||||||
|
|
||||||
ctx.finish(1)
|
ctx.finish(1)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user