mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-13 19:16:56 +01:00
sweep: make sure exclusive inputs are not grouped
This commit fixes the grouping logic in `BudgetAggregator` to make sure the exclusive inputs are never grouped.
This commit is contained in:
parent
0fc5301d12
commit
0527b2d7a6
2 changed files with 67 additions and 11 deletions
|
@ -496,10 +496,12 @@ type clusterGroup map[fn.Option[int32]][]SweeperInput
|
||||||
|
|
||||||
// ClusterInputs creates a list of input sets from pending inputs.
|
// ClusterInputs creates a list of input sets from pending inputs.
|
||||||
// 1. filter out inputs whose budget cannot cover min relay fee.
|
// 1. filter out inputs whose budget cannot cover min relay fee.
|
||||||
// 2. group the inputs into clusters based on their deadline height.
|
// 2. filter a list of exclusive inputs.
|
||||||
// 3. sort the inputs in each cluster by their budget.
|
// 3. group the inputs into clusters based on their deadline height.
|
||||||
// 4. optionally split a cluster if it exceeds the max input limit.
|
// 4. sort the inputs in each cluster by their budget.
|
||||||
// 5. create input sets from each of the clusters.
|
// 5. optionally split a cluster if it exceeds the max input limit.
|
||||||
|
// 6. create input sets from each of the clusters.
|
||||||
|
// 7. create input sets for each of the exclusive inputs.
|
||||||
func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet {
|
func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet {
|
||||||
// Filter out inputs that have a budget below min relay fee.
|
// Filter out inputs that have a budget below min relay fee.
|
||||||
filteredInputs := b.filterInputs(inputs)
|
filteredInputs := b.filterInputs(inputs)
|
||||||
|
@ -507,9 +509,22 @@ func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet {
|
||||||
// Create clusters to group inputs based on their deadline height.
|
// Create clusters to group inputs based on their deadline height.
|
||||||
clusters := make(clusterGroup, len(filteredInputs))
|
clusters := make(clusterGroup, len(filteredInputs))
|
||||||
|
|
||||||
|
// exclusiveInputs is a set of inputs that are not to be included in
|
||||||
|
// any cluster. These inputs can only be swept independently as there's
|
||||||
|
// no guarantee which input will be confirmed first, which means
|
||||||
|
// grouping exclusive inputs may jeopardize non-exclusive inputs.
|
||||||
|
exclusiveInputs := make(InputsMap)
|
||||||
|
|
||||||
// Iterate all the inputs and group them based on their specified
|
// Iterate all the inputs and group them based on their specified
|
||||||
// deadline heights.
|
// deadline heights.
|
||||||
for _, input := range filteredInputs {
|
for _, input := range filteredInputs {
|
||||||
|
// Put exclusive inputs in their own set.
|
||||||
|
if input.params.ExclusiveGroup != nil {
|
||||||
|
log.Tracef("Input %v is exclusive", input.OutPoint())
|
||||||
|
exclusiveInputs[*input.OutPoint()] = input
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
height := input.params.DeadlineHeight
|
height := input.params.DeadlineHeight
|
||||||
cluster, ok := clusters[height]
|
cluster, ok := clusters[height]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -534,6 +549,12 @@ func (b *BudgetAggregator) ClusterInputs(inputs InputsMap) []InputSet {
|
||||||
inputSets = append(inputSets, sets...)
|
inputSets = append(inputSets, sets...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create input sets from the exclusive inputs.
|
||||||
|
for _, input := range exclusiveInputs {
|
||||||
|
sets := b.createInputSets([]SweeperInput{*input})
|
||||||
|
inputSets = append(inputSets, sets...)
|
||||||
|
}
|
||||||
|
|
||||||
return inputSets
|
return inputSets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -801,10 +801,10 @@ func TestBudgetInputSetClusterInputs(t *testing.T) {
|
||||||
wt := &input.MockWitnessType{}
|
wt := &input.MockWitnessType{}
|
||||||
defer wt.AssertExpectations(t)
|
defer wt.AssertExpectations(t)
|
||||||
|
|
||||||
// Mock the `SizeUpperBound` method to return the size six times since
|
// Mock the `SizeUpperBound` method to return the size 10 times since
|
||||||
// we are using nine inputs.
|
// we are using ten inputs.
|
||||||
const wtSize = 100
|
const wtSize = 100
|
||||||
wt.On("SizeUpperBound").Return(wtSize, true, nil).Times(9)
|
wt.On("SizeUpperBound").Return(wtSize, true, nil).Times(10)
|
||||||
wt.On("String").Return("mock witness type")
|
wt.On("String").Return("mock witness type")
|
||||||
|
|
||||||
// Mock the estimator to return a constant fee rate.
|
// Mock the estimator to return a constant fee rate.
|
||||||
|
@ -827,6 +827,36 @@ func TestBudgetInputSetClusterInputs(t *testing.T) {
|
||||||
// Create testing pending inputs.
|
// Create testing pending inputs.
|
||||||
inputs := make(InputsMap)
|
inputs := make(InputsMap)
|
||||||
|
|
||||||
|
// Create a mock input that is exclusive.
|
||||||
|
inpExclusive := &input.MockInput{}
|
||||||
|
defer inpExclusive.AssertExpectations(t)
|
||||||
|
|
||||||
|
// We expect the high budget input to call this method three times,
|
||||||
|
// 1. in `filterInputs`
|
||||||
|
// 2. in `createInputSet`
|
||||||
|
// 3. when assigning the input to the exclusiveInputs.
|
||||||
|
// 4. when iterating the exclusiveInputs.
|
||||||
|
opExclusive := wire.OutPoint{Hash: chainhash.Hash{1, 2, 3, 4, 5}}
|
||||||
|
inpExclusive.On("OutPoint").Return(&opExclusive).Times(4)
|
||||||
|
|
||||||
|
// Mock the `WitnessType` method to return the witness type.
|
||||||
|
inpExclusive.On("WitnessType").Return(wt)
|
||||||
|
|
||||||
|
// Mock the `RequiredTxOut` to return nil.
|
||||||
|
inpExclusive.On("RequiredTxOut").Return(nil)
|
||||||
|
|
||||||
|
// Add the exclusive input to the inputs map. We expect this input to
|
||||||
|
// be in its own input set although it has deadline1.
|
||||||
|
exclusiveGroup := uint64(123)
|
||||||
|
inputs[opExclusive] = &SweeperInput{
|
||||||
|
Input: inpExclusive,
|
||||||
|
params: Params{
|
||||||
|
Budget: budgetHigh,
|
||||||
|
DeadlineHeight: deadline1,
|
||||||
|
ExclusiveGroup: &exclusiveGroup,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// For each deadline height, create two inputs with different budgets,
|
// For each deadline height, create two inputs with different budgets,
|
||||||
// one below the min fee rate and one above it. We should see the lower
|
// one below the min fee rate and one above it. We should see the lower
|
||||||
// one being filtered out.
|
// one being filtered out.
|
||||||
|
@ -910,12 +940,17 @@ func TestBudgetInputSetClusterInputs(t *testing.T) {
|
||||||
// Call the method under test.
|
// Call the method under test.
|
||||||
result := b.ClusterInputs(inputs)
|
result := b.ClusterInputs(inputs)
|
||||||
|
|
||||||
// We expect three input sets to be returned, one for each deadline.
|
// We expect four input sets to be returned, one for each deadline and
|
||||||
require.Len(t, result, 3)
|
// extra one for the exclusive input.
|
||||||
|
require.Len(t, result, 4)
|
||||||
|
|
||||||
// Check each input set has exactly two inputs.
|
// The last set should be the exclusive input that has only one input.
|
||||||
|
setExclusive := result[3]
|
||||||
|
require.Len(t, setExclusive.Inputs(), 1)
|
||||||
|
|
||||||
|
// Check the each of rest has exactly two inputs.
|
||||||
deadlines := make(map[fn.Option[int32]]struct{})
|
deadlines := make(map[fn.Option[int32]]struct{})
|
||||||
for _, set := range result {
|
for _, set := range result[:3] {
|
||||||
// We expect two inputs in each set.
|
// We expect two inputs in each set.
|
||||||
require.Len(t, set.Inputs(), 2)
|
require.Len(t, set.Inputs(), 2)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue