Merge pull request #10117 from yyforyongyu/keep-exclusive
contractcourt+sweep: make anchor inputs exclusive
This commit is contained in:
7 files changed
+114
-75
No files matched your search
@@ -202,7 +202,10 @@ func (c *anchorResolver) Launch() error {
|
|||||||
// an output that we want to sweep only if it is economical to do so.
|
// an output that we want to sweep only if it is economical to do so.
|
||||||
//
|
//
|
||||||
// An exclusive group is not necessary anymore, because we know that
|
// An exclusive group is not necessary anymore, because we know that
|
||||||
// this is the only anchor that can be swept.
|
// this is the only anchor that can be swept. However, to avoid this
|
||||||
|
// anchor input being group with other inputs, we still keep the
|
||||||
|
// exclusive group here such that the anchor will be swept
|
||||||
|
// independently.
|
||||||
//
|
//
|
||||||
// We also clear the parent tx information for cpfp, because the
|
// We also clear the parent tx information for cpfp, because the
|
||||||
// commitment tx is confirmed.
|
// commitment tx is confirmed.
|
||||||
@@ -222,6 +225,8 @@ func (c *anchorResolver) Launch() error {
|
|||||||
c.broadcastHeight, nil,
|
c.broadcastHeight, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
exclusiveGroup := c.ShortChanID.ToUint64()
|
||||||
|
|
||||||
resultChan, err := c.Sweeper.SweepInput(
|
resultChan, err := c.Sweeper.SweepInput(
|
||||||
&anchorInput,
|
&anchorInput,
|
||||||
sweep.Params{
|
sweep.Params{
|
||||||
@@ -233,6 +238,10 @@ func (c *anchorResolver) Launch() error {
|
|||||||
// There's no rush to sweep the anchor, so we use a nil
|
// There's no rush to sweep the anchor, so we use a nil
|
||||||
// deadline here.
|
// deadline here.
|
||||||
DeadlineHeight: fn.None[int32](),
|
DeadlineHeight: fn.None[int32](),
|
||||||
|
|
||||||
|
// Use the chan id as the exclusive group. This prevents
|
||||||
|
// any of the anchors from being batched together.
|
||||||
|
ExclusiveGroup: &exclusiveGroup,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,13 @@
|
|||||||
|
|
||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
|
|
||||||
|
- Previously, when sweeping non-time sensitive anchor outputs, they might be
|
||||||
|
grouped with other non-time sensitive outputs such as `to_local` outputs,
|
||||||
|
which potentially allow the sweeping tx to be pinned. This is now
|
||||||
|
[fixed](https://github.com/lightningnetwork/lnd/pull/10117) by moving sweeping
|
||||||
|
anchors into its own tx, which means the anchor outputs won't be swept in a
|
||||||
|
high fee environment.
|
||||||
|
|
||||||
## RPC Additions
|
## RPC Additions
|
||||||
|
|
||||||
## lncli Additions
|
## lncli Additions
|
||||||
@@ -62,4 +69,6 @@
|
|||||||
## Tooling and Documentation
|
## Tooling and Documentation
|
||||||
|
|
||||||
# Contributors (Alphabetical Order)
|
# Contributors (Alphabetical Order)
|
||||||
* Olaoluwa Osuntokun
|
|
||||||
|
* Olaoluwa Osuntokun
|
||||||
|
* Yong Yu
|
||||||
@@ -978,15 +978,6 @@ func runChannelForceClosureTestRestart(ht *lntest.HarnessTest,
|
|||||||
Outpoint: commitSweep.Outpoint,
|
Outpoint: commitSweep.Outpoint,
|
||||||
AmountSat: uint64(aliceBalance),
|
AmountSat: uint64(aliceBalance),
|
||||||
}
|
}
|
||||||
op = fmt.Sprintf("%v:%v", anchorSweep.Outpoint.TxidStr,
|
|
||||||
anchorSweep.Outpoint.OutputIndex)
|
|
||||||
aliceReports[op] = &lnrpc.Resolution{
|
|
||||||
ResolutionType: lnrpc.ResolutionType_ANCHOR,
|
|
||||||
Outcome: lnrpc.ResolutionOutcome_CLAIMED,
|
|
||||||
SweepTxid: sweepTxid.String(),
|
|
||||||
Outpoint: anchorSweep.Outpoint,
|
|
||||||
AmountSat: uint64(anchorSweep.AmountSat),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we can find the commitment sweep in our set of known
|
// Check that we can find the commitment sweep in our set of known
|
||||||
// sweeps, using the simple transaction id ListSweeps output.
|
// sweeps, using the simple transaction id ListSweeps output.
|
||||||
@@ -1101,8 +1092,9 @@ func runChannelForceClosureTestRestart(ht *lntest.HarnessTest,
|
|||||||
|
|
||||||
// Since Alice had numInvoices (6) htlcs extended to Carol before force
|
// Since Alice had numInvoices (6) htlcs extended to Carol before force
|
||||||
// closing, we expect Alice to broadcast an htlc timeout txn for each
|
// closing, we expect Alice to broadcast an htlc timeout txn for each
|
||||||
// one.
|
// one. In addition, the anchor input is still pending due to it's
|
||||||
ht.AssertNumPendingSweeps(alice, numInvoices)
|
// uneconomical to sweep.
|
||||||
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
|
|
||||||
// Wait for them all to show up in the mempool
|
// Wait for them all to show up in the mempool
|
||||||
htlcTxid := ht.AssertNumTxsInMempool(1)[0]
|
htlcTxid := ht.AssertNumTxsInMempool(1)[0]
|
||||||
@@ -1198,7 +1190,9 @@ func runChannelForceClosureTestRestart(ht *lntest.HarnessTest,
|
|||||||
numBlocks := int(htlcCsvMaturityHeight - uint32(curHeight) - 1)
|
numBlocks := int(htlcCsvMaturityHeight - uint32(curHeight) - 1)
|
||||||
ht.MineEmptyBlocks(numBlocks)
|
ht.MineEmptyBlocks(numBlocks)
|
||||||
|
|
||||||
ht.AssertNumPendingSweeps(alice, numInvoices)
|
// We should see numInvoices HTLC sweeps plus the uneconomical anchor
|
||||||
|
// sweep.
|
||||||
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
|
|
||||||
// Fetch the htlc sweep transaction from the mempool.
|
// Fetch the htlc sweep transaction from the mempool.
|
||||||
htlcSweepTx := ht.GetNumTxsFromMempool(1)[0]
|
htlcSweepTx := ht.GetNumTxsFromMempool(1)[0]
|
||||||
@@ -1220,7 +1214,7 @@ func runChannelForceClosureTestRestart(ht *lntest.HarnessTest,
|
|||||||
}, defaultTimeout)
|
}, defaultTimeout)
|
||||||
require.NoError(ht, err, "timeout while checking force closed channel")
|
require.NoError(ht, err, "timeout while checking force closed channel")
|
||||||
|
|
||||||
ht.AssertNumPendingSweeps(alice, numInvoices)
|
ht.AssertNumPendingSweeps(alice, numInvoices+1)
|
||||||
|
|
||||||
// Ensure the htlc sweep transaction only has one input for each htlc
|
// Ensure the htlc sweep transaction only has one input for each htlc
|
||||||
// Alice extended before force closing.
|
// Alice extended before force closing.
|
||||||
|
|||||||
+29
-14
@@ -1224,8 +1224,8 @@ func testSweepHTLCs(ht *lntest.HarnessTest) {
|
|||||||
// 4. Alice force closes the channel.
|
// 4. Alice force closes the channel.
|
||||||
//
|
//
|
||||||
// Test:
|
// Test:
|
||||||
// 1. Alice's anchor sweeping is not attempted, instead, it should be swept
|
// 1. Alice's CPFP-anchor sweeping is not attempted, instead, it should be
|
||||||
// together with her to_local output using the no deadline path.
|
// swept using the no deadline path and failed due it's not economical.
|
||||||
// 2. Bob would also sweep his anchor and to_local outputs separately due to
|
// 2. Bob would also sweep his anchor and to_local outputs separately due to
|
||||||
// they have different deadline heights, which means only the to_local
|
// they have different deadline heights, which means only the to_local
|
||||||
// sweeping tx will succeed as the anchor sweeping is not economical.
|
// sweeping tx will succeed as the anchor sweeping is not economical.
|
||||||
@@ -1241,10 +1241,15 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
// config.
|
// config.
|
||||||
deadline := uint32(1000)
|
deadline := uint32(1000)
|
||||||
|
|
||||||
// deadlineA is the deadline used for Alice, since her commit output is
|
// deadlineA is the deadline used for Alice, given that,
|
||||||
// offered to the sweeper at CSV-1. With a deadline of 1000, her actual
|
// - the force close tx is broadcast at height 445, her inputs are
|
||||||
// width of her fee func is CSV+1000-1. Given we are using a CSV of 2
|
// registered at the same height, so her to_local and anchor outputs
|
||||||
// here, her fee func deadline then becomes 1001.
|
// have a deadline height of 1445.
|
||||||
|
// - the force close tx is mined at 446, which means her anchor output
|
||||||
|
// now has a deadline delta of (1445-446) = 999 blocks.
|
||||||
|
// - for her to_local output, with a deadline of 1000, the width of the
|
||||||
|
// fee func is CSV+1000-1. Given we are using a CSV of 2 here, her fee
|
||||||
|
// func deadline then becomes 1001.
|
||||||
deadlineA := deadline + 1
|
deadlineA := deadline + 1
|
||||||
|
|
||||||
// deadlineB is the deadline used for Bob, the actual deadline used by
|
// deadlineB is the deadline used for Bob, the actual deadline used by
|
||||||
@@ -1267,6 +1272,11 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
// conf target is the deadline.
|
// conf target is the deadline.
|
||||||
ht.SetFeeEstimateWithConf(startFeeRate, deadlineB)
|
ht.SetFeeEstimateWithConf(startFeeRate, deadlineB)
|
||||||
|
|
||||||
|
// Set up the starting fee for Alice's anchor sweeping. With this low
|
||||||
|
// fee rate, her anchor sweeping should be attempted and failed due to
|
||||||
|
// dust output generated in the sweeping tx.
|
||||||
|
ht.SetFeeEstimateWithConf(startFeeRate, deadline-1)
|
||||||
|
|
||||||
// toLocalCSV is the CSV delay for Alice's to_local output. We use a
|
// toLocalCSV is the CSV delay for Alice's to_local output. We use a
|
||||||
// small value to save us from mining blocks.
|
// small value to save us from mining blocks.
|
||||||
//
|
//
|
||||||
@@ -1427,10 +1437,10 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
// With Alice's starting fee rate being validated, we now calculate her
|
// With Alice's starting fee rate being validated, we now calculate her
|
||||||
// ending fee rate and fee rate delta.
|
// ending fee rate and fee rate delta.
|
||||||
//
|
//
|
||||||
// Alice sweeps two inputs - anchor and commit, so the starting budget
|
// Alice sweeps the to_local input, so the starting budget should come
|
||||||
// should come from the sum of these two. However, due to the value
|
// from the to_local balance. However, due to the value being too large,
|
||||||
// being too large, the actual ending fee rate used should be the
|
// the actual ending fee rate used should be the sweeper's max fee rate
|
||||||
// sweeper's max fee rate configured.
|
// configured.
|
||||||
aliceTxWeight := uint64(ht.CalculateTxWeight(aliceSweepTx))
|
aliceTxWeight := uint64(ht.CalculateTxWeight(aliceSweepTx))
|
||||||
aliceEndingFeeRate := sweep.DefaultMaxFeeRate.FeePerKWeight()
|
aliceEndingFeeRate := sweep.DefaultMaxFeeRate.FeePerKWeight()
|
||||||
aliceFeeRateDelta := (aliceEndingFeeRate - aliceStartingFeeRate) /
|
aliceFeeRateDelta := (aliceEndingFeeRate - aliceStartingFeeRate) /
|
||||||
@@ -1507,10 +1517,10 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We should see two txns in the mempool:
|
// We should see two txns in the mempool:
|
||||||
// - Alice's sweeping tx, which sweeps both her anchor and
|
// - Alice's sweeping tx, which sweeps her commit output, using
|
||||||
// commit outputs, using the increased fee rate.
|
// the increased fee rate.
|
||||||
// - Bob's previous sweeping tx, which sweeps both his anchor
|
// - Bob's previous sweeping tx, which sweeps his commit output,
|
||||||
// and commit outputs, at the possible increased fee rate.
|
// at the possible increased fee rate.
|
||||||
txns := ht.GetNumTxsFromMempool(2)
|
txns := ht.GetNumTxsFromMempool(2)
|
||||||
|
|
||||||
// Assume the first tx is Alice's sweeping tx, if the second tx
|
// Assume the first tx is Alice's sweeping tx, if the second tx
|
||||||
@@ -1577,6 +1587,11 @@ func testSweepCommitOutputAndAnchor(ht *lntest.HarnessTest) {
|
|||||||
// Mine a block to confirm both sweeping txns, this is needed to clean
|
// Mine a block to confirm both sweeping txns, this is needed to clean
|
||||||
// up the mempool.
|
// up the mempool.
|
||||||
ht.MineBlocksAndAssertNumTxes(1, 2)
|
ht.MineBlocksAndAssertNumTxes(1, 2)
|
||||||
|
|
||||||
|
// Finally, assert that both Alice and Bob still have the anchor
|
||||||
|
// outputs, which cannot be swept due to it being uneconomical.
|
||||||
|
ht.AssertNumPendingSweeps(alice, 1)
|
||||||
|
ht.AssertNumPendingSweeps(bob, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBumpForceCloseFee tests that when a force close transaction, in
|
// testBumpForceCloseFee tests that when a force close transaction, in
|
||||||
|
|||||||
+6
-6
@@ -111,8 +111,8 @@ const (
|
|||||||
// error, which means they cannot be retried with increased budget.
|
// error, which means they cannot be retried with increased budget.
|
||||||
TxFatal
|
TxFatal
|
||||||
|
|
||||||
// sentinalEvent is used to check if an event is unknown.
|
// sentinelEvent is used to check if an event is unknown.
|
||||||
sentinalEvent
|
sentinelEvent
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a human-readable string for the event.
|
// String returns a human-readable string for the event.
|
||||||
@@ -137,13 +137,13 @@ func (e BumpEvent) String() string {
|
|||||||
|
|
||||||
// Unknown returns true if the event is unknown.
|
// Unknown returns true if the event is unknown.
|
||||||
func (e BumpEvent) Unknown() bool {
|
func (e BumpEvent) Unknown() bool {
|
||||||
return e >= sentinalEvent
|
return e >= sentinelEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
// BumpRequest is used by the caller to give the Bumper the necessary info to
|
// BumpRequest is used by the caller to give the Bumper the necessary info to
|
||||||
// create and manage potential fee bumps for a set of inputs.
|
// create and manage potential fee bumps for a set of inputs.
|
||||||
type BumpRequest struct {
|
type BumpRequest struct {
|
||||||
// Budget givens the total amount that can be used as fees by these
|
// Budget gives the total amount that can be used as fees by these
|
||||||
// inputs.
|
// inputs.
|
||||||
Budget btcutil.Amount
|
Budget btcutil.Amount
|
||||||
|
|
||||||
@@ -589,7 +589,7 @@ func (t *TxPublisher) createRBFCompliantTx(
|
|||||||
// used up the budget, we will return an error
|
// used up the budget, we will return an error
|
||||||
// indicating this tx cannot be made. The
|
// indicating this tx cannot be made. The
|
||||||
// sweeper should handle this error and try to
|
// sweeper should handle this error and try to
|
||||||
// cluster these inputs differetly.
|
// cluster these inputs differently.
|
||||||
increased, err = f.Increment()
|
increased, err = f.Increment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1332,7 +1332,7 @@ func (t *TxPublisher) createAndPublishTx(
|
|||||||
// the fee bumper retry it at next block.
|
// the fee bumper retry it at next block.
|
||||||
//
|
//
|
||||||
// NOTE: we may get this error if we've bypassed the mempool check,
|
// NOTE: we may get this error if we've bypassed the mempool check,
|
||||||
// which means we are suing neutrino backend.
|
// which means we are using neutrino backend.
|
||||||
if errors.Is(result.Err, chain.ErrInsufficientFee) ||
|
if errors.Is(result.Err, chain.ErrInsufficientFee) ||
|
||||||
errors.Is(result.Err, lnwallet.ErrMempoolFee) {
|
errors.Is(result.Err, lnwallet.ErrMempoolFee) {
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -73,7 +73,7 @@ func TestBumpResultValidate(t *testing.T) {
|
|||||||
// Unknown event type will give an error.
|
// Unknown event type will give an error.
|
||||||
b = BumpResult{
|
b = BumpResult{
|
||||||
Tx: &wire.MsgTx{},
|
Tx: &wire.MsgTx{},
|
||||||
Event: sentinalEvent,
|
Event: sentinelEvent,
|
||||||
}
|
}
|
||||||
require.ErrorIs(t, b.Validate(), ErrInvalidBumpResult)
|
require.ErrorIs(t, b.Validate(), ErrInvalidBumpResult)
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ func TestCreateAndCheckTx(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -550,7 +550,7 @@ func TestCreateRBFCompliantTx(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -1120,9 +1120,9 @@ func TestBroadcastImmediate(t *testing.T) {
|
|||||||
require.Empty(t, tp.subscriberChans.Len())
|
require.Empty(t, tp.subscriberChans.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreateAnPublishFail checks all the error cases are handled properly in
|
// TestCreateAndPublishFail checks all the error cases are handled properly in
|
||||||
// the method createAndPublish.
|
// the method createAndPublishTx.
|
||||||
func TestCreateAnPublishFail(t *testing.T) {
|
func TestCreateAndPublishFail(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a publisher using the mocks.
|
// Create a publisher using the mocks.
|
||||||
@@ -1152,7 +1152,7 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -1190,9 +1190,9 @@ func TestCreateAnPublishFail(t *testing.T) {
|
|||||||
require.True(t, resultOpt.IsNone())
|
require.True(t, resultOpt.IsNone())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCreateAnPublishSuccess checks the expected result is returned from the
|
// TestCreateAndPublishSuccess checks the expected result is returned from the
|
||||||
// method createAndPublish.
|
// method createAndPublishTx.
|
||||||
func TestCreateAnPublishSuccess(t *testing.T) {
|
func TestCreateAndPublishSuccess(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a publisher using the mocks.
|
// Create a publisher using the mocks.
|
||||||
@@ -1218,7 +1218,7 @@ func TestCreateAnPublishSuccess(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -1445,7 +1445,7 @@ func TestHandleFeeBumpTx(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -1830,7 +1830,7 @@ func TestHandleInitialBroadcastSuccess(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
@@ -1916,7 +1916,7 @@ func TestHandleInitialBroadcastFail(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// NOTE: we are not testing the utility of creating valid txes here, so
|
// NOTE: we are not testing the utility of creating valid txes here, so
|
||||||
// this is fine to be mocked. This behaves essentially as skipping the
|
// this is fine to be mocked. This behaves essentially as skipping the
|
||||||
// Signer check and alaways assume the tx has a valid sig.
|
// Signer check and always assume the tx has a valid sig.
|
||||||
script := &input.Script{}
|
script := &input.Script{}
|
||||||
m.signer.On("ComputeInputScript", mock.Anything,
|
m.signer.On("ComputeInputScript", mock.Anything,
|
||||||
mock.Anything).Return(script, nil)
|
mock.Anything).Return(script, nil)
|
||||||
|
|||||||
+38
-26
@@ -45,8 +45,9 @@ var (
|
|||||||
|
|
||||||
// Params contains the parameters that control the sweeping process.
|
// Params contains the parameters that control the sweeping process.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
// ExclusiveGroup is an identifier that, if set, prevents other inputs
|
// ExclusiveGroup is an identifier that, if set, ensures this input is
|
||||||
// with the same identifier from being batched together.
|
// swept in a transaction by itself, and not batched with any other
|
||||||
|
// inputs.
|
||||||
ExclusiveGroup *uint64
|
ExclusiveGroup *uint64
|
||||||
|
|
||||||
// DeadlineHeight specifies an absolute block height that this input
|
// DeadlineHeight specifies an absolute block height that this input
|
||||||
@@ -739,15 +740,23 @@ func (s *UtxoSweeper) collector() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeExclusiveGroup removes all inputs in the given exclusive group. This
|
// removeExclusiveGroup removes all inputs in the given exclusive group except
|
||||||
// function is called when one of the exclusive group inputs has been spent. The
|
// the input specified by the outpoint. This function is called when one of the
|
||||||
// other inputs won't ever be spendable and can be removed. This also prevents
|
// exclusive group inputs has been spent or updated. The other inputs won't ever
|
||||||
// them from being part of future sweep transactions that would fail. In
|
// be spendable and can be removed. This also prevents them from being part of
|
||||||
// addition sweep transactions of those inputs will be removed from the wallet.
|
// future sweep transactions that would fail. In addition sweep transactions of
|
||||||
func (s *UtxoSweeper) removeExclusiveGroup(group uint64) {
|
// those inputs will be removed from the wallet.
|
||||||
|
func (s *UtxoSweeper) removeExclusiveGroup(group uint64, op wire.OutPoint) {
|
||||||
for outpoint, input := range s.inputs {
|
for outpoint, input := range s.inputs {
|
||||||
outpoint := outpoint
|
outpoint := outpoint
|
||||||
|
|
||||||
|
// Skip the input that caused the exclusive group to be removed.
|
||||||
|
if outpoint == op {
|
||||||
|
log.Debugf("Skipped removing exclusive input %v", input)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Skip inputs that aren't exclusive.
|
// Skip inputs that aren't exclusive.
|
||||||
if input.params.ExclusiveGroup == nil {
|
if input.params.ExclusiveGroup == nil {
|
||||||
continue
|
continue
|
||||||
@@ -766,6 +775,8 @@ func (s *UtxoSweeper) removeExclusiveGroup(group uint64) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("Removing exclusive group for input %v", input)
|
||||||
|
|
||||||
// Signal result channels.
|
// Signal result channels.
|
||||||
s.signalResult(input, Result{
|
s.signalResult(input, Result{
|
||||||
Err: ErrExclusiveGroupSpend,
|
Err: ErrExclusiveGroupSpend,
|
||||||
@@ -1366,22 +1377,19 @@ func (s *UtxoSweeper) decideRBFInfo(
|
|||||||
func (s *UtxoSweeper) handleExistingInput(input *sweepInputMessage,
|
func (s *UtxoSweeper) handleExistingInput(input *sweepInputMessage,
|
||||||
oldInput *SweeperInput) {
|
oldInput *SweeperInput) {
|
||||||
|
|
||||||
// Before updating the input details, check if an exclusive group was
|
// Before updating the input details, check if a previous exclusive
|
||||||
// set. In case the same input is registered again without an exclusive
|
// group was set. In case the same input is registered again, the
|
||||||
// group set, the previous input and its sweep parameters are outdated
|
// previous input and its sweep parameters are outdated hence need to be
|
||||||
// hence need to be replaced. This scenario currently only happens for
|
// replaced. This scenario currently only happens for anchor outputs.
|
||||||
// anchor outputs. When a channel is force closed, in the worst case 3
|
// When a channel is force closed, in the worst case 3 different sweeps
|
||||||
// different sweeps with the same exclusive group are registered with
|
// with the same exclusive group are registered with the sweeper to bump
|
||||||
// the sweeper to bump the closing transaction (cpfp) when its time
|
// the closing transaction (cpfp) when its time critical. Receiving an
|
||||||
// critical. Receiving an input which was already registered with the
|
// input which was already registered with the sweeper means none of the
|
||||||
// sweeper but now without an exclusive group means non of the previous
|
// previous inputs were used as CPFP, so we need to make sure we update
|
||||||
// inputs were used as CPFP, so we need to make sure we update the
|
// the sweep parameters but also remove all inputs with the same
|
||||||
// sweep parameters but also remove all inputs with the same exclusive
|
// exclusive group because they are outdated too.
|
||||||
// group because the are outdated too.
|
|
||||||
var prevExclGroup *uint64
|
var prevExclGroup *uint64
|
||||||
if oldInput.params.ExclusiveGroup != nil &&
|
if oldInput.params.ExclusiveGroup != nil {
|
||||||
input.params.ExclusiveGroup == nil {
|
|
||||||
|
|
||||||
prevExclGroup = new(uint64)
|
prevExclGroup = new(uint64)
|
||||||
*prevExclGroup = *oldInput.params.ExclusiveGroup
|
*prevExclGroup = *oldInput.params.ExclusiveGroup
|
||||||
}
|
}
|
||||||
@@ -1400,7 +1408,7 @@ func (s *UtxoSweeper) handleExistingInput(input *sweepInputMessage,
|
|||||||
oldInput.listeners = append(oldInput.listeners, input.resultChan)
|
oldInput.listeners = append(oldInput.listeners, input.resultChan)
|
||||||
|
|
||||||
if prevExclGroup != nil {
|
if prevExclGroup != nil {
|
||||||
s.removeExclusiveGroup(*prevExclGroup)
|
s.removeExclusiveGroup(*prevExclGroup, input.input.OutPoint())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1492,7 +1500,9 @@ func (s *UtxoSweeper) markInputsSwept(tx *wire.MsgTx, isOurTx bool) {
|
|||||||
|
|
||||||
// Remove all other inputs in this exclusive group.
|
// Remove all other inputs in this exclusive group.
|
||||||
if input.params.ExclusiveGroup != nil {
|
if input.params.ExclusiveGroup != nil {
|
||||||
s.removeExclusiveGroup(*input.params.ExclusiveGroup)
|
s.removeExclusiveGroup(
|
||||||
|
*input.params.ExclusiveGroup, outpoint,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1907,7 +1917,9 @@ func (s *UtxoSweeper) markInputSwept(inp *SweeperInput, tx *wire.MsgTx) {
|
|||||||
|
|
||||||
// Remove all other inputs in this exclusive group.
|
// Remove all other inputs in this exclusive group.
|
||||||
if inp.params.ExclusiveGroup != nil {
|
if inp.params.ExclusiveGroup != nil {
|
||||||
s.removeExclusiveGroup(*inp.params.ExclusiveGroup)
|
s.removeExclusiveGroup(
|
||||||
|
*inp.params.ExclusiveGroup, inp.OutPoint(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user