mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-14 19:01:10 +02:00
contractcourt: offer second-level outputs at CSV-1
This commit moves the offering of second-level outputs one block earlier. The sweeper will check the required locktime and wait until it matures. This is needed so the second-level outputs can be aggregated properly.
This commit is contained in:
parent
c644deb49f
commit
acde08c65a
@ -349,6 +349,25 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
|
|||||||
"height %v", h, h.htlc.RHash[:], waitHeight)
|
"height %v", h, h.htlc.RHash[:], waitHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduct one block so this input is offered to the sweeper one block
|
||||||
|
// earlier since the sweeper will wait for one block to trigger the
|
||||||
|
// sweeping.
|
||||||
|
//
|
||||||
|
// TODO(yy): this is done so the outputs can be aggregated
|
||||||
|
// properly. Suppose CSV locks of five 2nd-level outputs all
|
||||||
|
// expire at height 840000, there is a race in block digestion
|
||||||
|
// between contractcourt and sweeper:
|
||||||
|
// - G1: block 840000 received in contractcourt, it now offers
|
||||||
|
// the outputs to the sweeper.
|
||||||
|
// - G2: block 840000 received in sweeper, it now starts to
|
||||||
|
// sweep the received outputs - there's no guarantee all
|
||||||
|
// fives have been received.
|
||||||
|
// To solve this, we either offer the outputs earlier, or
|
||||||
|
// implement `blockbeat`, and force contractcourt and sweeper
|
||||||
|
// to consume each block sequentially.
|
||||||
|
waitHeight--
|
||||||
|
|
||||||
|
// TODO(yy): let sweeper handles the wait?
|
||||||
err := waitForHeight(waitHeight, h.Notifier, h.quit)
|
err := waitForHeight(waitHeight, h.Notifier, h.quit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -364,10 +383,6 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
|
|||||||
Index: commitSpend.SpenderInputIndex,
|
Index: commitSpend.SpenderInputIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, let the sweeper sweep the second-level output.
|
|
||||||
log.Infof("%T(%x): CSV lock expired, offering second-layer "+
|
|
||||||
"output to sweeper: %v", h, h.htlc.RHash[:], op)
|
|
||||||
|
|
||||||
// Let the sweeper sweep the second-level output now that the
|
// Let the sweeper sweep the second-level output now that the
|
||||||
// CSV/CLTV locks have expired.
|
// CSV/CLTV locks have expired.
|
||||||
var witType input.StandardWitnessType
|
var witType input.StandardWitnessType
|
||||||
@ -380,7 +395,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
|
|||||||
op, witType,
|
op, witType,
|
||||||
input.LeaseHtlcAcceptedSuccessSecondLevel,
|
input.LeaseHtlcAcceptedSuccessSecondLevel,
|
||||||
&h.htlcResolution.SweepSignDesc,
|
&h.htlcResolution.SweepSignDesc,
|
||||||
h.htlcResolution.CsvDelay, h.broadcastHeight,
|
h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
|
||||||
h.htlc.RHash,
|
h.htlc.RHash,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -392,7 +407,8 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() (
|
|||||||
)
|
)
|
||||||
|
|
||||||
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
|
log.Infof("%T(%x): offering second-level success tx output to sweeper "+
|
||||||
"with no deadline and budget=%v", h, h.htlc.RHash[:], budget)
|
"with no deadline and budget=%v at height=%v", h,
|
||||||
|
h.htlc.RHash[:], budget, waitHeight)
|
||||||
|
|
||||||
// TODO(roasbeef): need to update above for leased types
|
// TODO(roasbeef): need to update above for leased types
|
||||||
_, err = h.Sweeper.SweepInput(
|
_, err = h.Sweeper.SweepInput(
|
||||||
|
@ -706,6 +706,25 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
|||||||
"height %v", h, h.htlc.RHash[:], waitHeight)
|
"height %v", h, h.htlc.RHash[:], waitHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deduct one block so this input is offered to the sweeper one
|
||||||
|
// block earlier since the sweeper will wait for one block to
|
||||||
|
// trigger the sweeping.
|
||||||
|
//
|
||||||
|
// TODO(yy): this is done so the outputs can be aggregated
|
||||||
|
// properly. Suppose CSV locks of five 2nd-level outputs all
|
||||||
|
// expire at height 840000, there is a race in block digestion
|
||||||
|
// between contractcourt and sweeper:
|
||||||
|
// - G1: block 840000 received in contractcourt, it now offers
|
||||||
|
// the outputs to the sweeper.
|
||||||
|
// - G2: block 840000 received in sweeper, it now starts to
|
||||||
|
// sweep the received outputs - there's no guarantee all
|
||||||
|
// fives have been received.
|
||||||
|
// To solve this, we either offer the outputs earlier, or
|
||||||
|
// implement `blockbeat`, and force contractcourt and sweeper
|
||||||
|
// to consume each block sequentially.
|
||||||
|
waitHeight--
|
||||||
|
|
||||||
|
// TODO(yy): let sweeper handles the wait?
|
||||||
err := waitForHeight(waitHeight, h.Notifier, h.quit)
|
err := waitForHeight(waitHeight, h.Notifier, h.quit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -735,8 +754,8 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
|||||||
op, csvWitnessType,
|
op, csvWitnessType,
|
||||||
input.LeaseHtlcOfferedTimeoutSecondLevel,
|
input.LeaseHtlcOfferedTimeoutSecondLevel,
|
||||||
&h.htlcResolution.SweepSignDesc,
|
&h.htlcResolution.SweepSignDesc,
|
||||||
h.htlcResolution.CsvDelay, h.broadcastHeight,
|
h.htlcResolution.CsvDelay,
|
||||||
h.htlc.RHash,
|
uint32(commitSpend.SpendingHeight), h.htlc.RHash,
|
||||||
)
|
)
|
||||||
// Calculate the budget for this sweep.
|
// Calculate the budget for this sweep.
|
||||||
budget := calculateBudget(
|
budget := calculateBudget(
|
||||||
@ -746,8 +765,8 @@ func (h *htlcTimeoutResolver) handleCommitSpend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
log.Infof("%T(%x): offering second-level timeout tx output to "+
|
log.Infof("%T(%x): offering second-level timeout tx output to "+
|
||||||
"sweeper with no deadline and budget=%v", h,
|
"sweeper with no deadline and budget=%v at height=%v",
|
||||||
h.htlc.RHash[:], budget)
|
h, h.htlc.RHash[:], budget, waitHeight)
|
||||||
|
|
||||||
_, err = h.Sweeper.SweepInput(
|
_, err = h.Sweeper.SweepInput(
|
||||||
inp,
|
inp,
|
||||||
|
@ -652,7 +652,7 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) {
|
|||||||
// failed, or excluded from the sweeper and return inputs that
|
// failed, or excluded from the sweeper and return inputs that
|
||||||
// are either new or has been published but failed back, which
|
// are either new or has been published but failed back, which
|
||||||
// will be retried again here.
|
// will be retried again here.
|
||||||
inputs := s.updateSweeperInputs()
|
s.updateSweeperInputs()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
// A new inputs is offered to the sweeper. We check to see if
|
// A new inputs is offered to the sweeper. We check to see if
|
||||||
@ -670,7 +670,7 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) {
|
|||||||
// If this input is forced, we perform an sweep
|
// If this input is forced, we perform an sweep
|
||||||
// immediately.
|
// immediately.
|
||||||
if input.params.Force {
|
if input.params.Force {
|
||||||
inputs = s.updateSweeperInputs()
|
inputs := s.updateSweeperInputs()
|
||||||
s.sweepPendingInputs(inputs)
|
s.sweepPendingInputs(inputs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -716,6 +716,9 @@ func (s *UtxoSweeper) collector(blockEpochs <-chan *chainntnfs.BlockEpoch) {
|
|||||||
// Update the sweeper to the best height.
|
// Update the sweeper to the best height.
|
||||||
s.currentHeight = epoch.Height
|
s.currentHeight = epoch.Height
|
||||||
|
|
||||||
|
// Update the inputs with the latest height.
|
||||||
|
inputs := s.updateSweeperInputs()
|
||||||
|
|
||||||
log.Debugf("Received new block: height=%v, attempt "+
|
log.Debugf("Received new block: height=%v, attempt "+
|
||||||
"sweeping %d inputs", epoch.Height, len(inputs))
|
"sweeping %d inputs", epoch.Height, len(inputs))
|
||||||
|
|
||||||
@ -1497,6 +1500,17 @@ func (s *UtxoSweeper) updateSweeperInputs() InputsMap {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the input has a CSV that's not yet reached, we will skip
|
||||||
|
// this input and wait for the expiry.
|
||||||
|
locktime = input.BlocksToMaturity() + input.HeightHint()
|
||||||
|
if s.currentHeight < int32(locktime)-1 {
|
||||||
|
log.Infof("Skipping input %v due to CSV expiry=%v not "+
|
||||||
|
"reached, current height is %v", op, locktime,
|
||||||
|
s.currentHeight)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If this input is new or has been failed to be published,
|
// If this input is new or has been failed to be published,
|
||||||
// we'd retry it. The assumption here is that when an error is
|
// we'd retry it. The assumption here is that when an error is
|
||||||
// returned from `PublishTransaction`, it means the tx has
|
// returned from `PublishTransaction`, it means the tx has
|
||||||
|
@ -2479,6 +2479,8 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
defer inp1.AssertExpectations(t)
|
defer inp1.AssertExpectations(t)
|
||||||
inp2 := &input.MockInput{}
|
inp2 := &input.MockInput{}
|
||||||
defer inp2.AssertExpectations(t)
|
defer inp2.AssertExpectations(t)
|
||||||
|
inp3 := &input.MockInput{}
|
||||||
|
defer inp3.AssertExpectations(t)
|
||||||
|
|
||||||
// Create a list of inputs using all the states.
|
// Create a list of inputs using all the states.
|
||||||
//
|
//
|
||||||
@ -2486,6 +2488,8 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
// returned.
|
// returned.
|
||||||
inp1.On("RequiredLockTime").Return(
|
inp1.On("RequiredLockTime").Return(
|
||||||
uint32(s.currentHeight), false).Once()
|
uint32(s.currentHeight), false).Once()
|
||||||
|
inp1.On("BlocksToMaturity").Return(uint32(0)).Once()
|
||||||
|
inp1.On("HeightHint").Return(uint32(s.currentHeight)).Once()
|
||||||
input0 := &SweeperInput{state: Init, Input: inp1}
|
input0 := &SweeperInput{state: Init, Input: inp1}
|
||||||
|
|
||||||
// These inputs won't hit RequiredLockTime so we won't mock.
|
// These inputs won't hit RequiredLockTime so we won't mock.
|
||||||
@ -2496,6 +2500,8 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
// returned.
|
// returned.
|
||||||
inp1.On("RequiredLockTime").Return(
|
inp1.On("RequiredLockTime").Return(
|
||||||
uint32(s.currentHeight), false).Once()
|
uint32(s.currentHeight), false).Once()
|
||||||
|
inp1.On("BlocksToMaturity").Return(uint32(0)).Once()
|
||||||
|
inp1.On("HeightHint").Return(uint32(s.currentHeight)).Once()
|
||||||
input3 := &SweeperInput{state: PublishFailed, Input: inp1}
|
input3 := &SweeperInput{state: PublishFailed, Input: inp1}
|
||||||
|
|
||||||
// These inputs won't hit RequiredLockTime so we won't mock.
|
// These inputs won't hit RequiredLockTime so we won't mock.
|
||||||
@ -2509,6 +2515,14 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
uint32(s.currentHeight+1), true).Once()
|
uint32(s.currentHeight+1), true).Once()
|
||||||
input7 := &SweeperInput{state: Init, Input: inp2}
|
input7 := &SweeperInput{state: Init, Input: inp2}
|
||||||
|
|
||||||
|
// Mock the input to have a CSV expiry in the future so it will NOT be
|
||||||
|
// returned.
|
||||||
|
inp3.On("RequiredLockTime").Return(
|
||||||
|
uint32(s.currentHeight), false).Once()
|
||||||
|
inp3.On("BlocksToMaturity").Return(uint32(2)).Once()
|
||||||
|
inp3.On("HeightHint").Return(uint32(s.currentHeight)).Once()
|
||||||
|
input8 := &SweeperInput{state: Init, Input: inp3}
|
||||||
|
|
||||||
// Add the inputs to the sweeper. After the update, we should see the
|
// Add the inputs to the sweeper. After the update, we should see the
|
||||||
// terminated inputs being removed.
|
// terminated inputs being removed.
|
||||||
s.inputs = map[wire.OutPoint]*SweeperInput{
|
s.inputs = map[wire.OutPoint]*SweeperInput{
|
||||||
@ -2520,6 +2534,7 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
{Index: 5}: input5,
|
{Index: 5}: input5,
|
||||||
{Index: 6}: input6,
|
{Index: 6}: input6,
|
||||||
{Index: 7}: input7,
|
{Index: 7}: input7,
|
||||||
|
{Index: 8}: input8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect the inputs with `Swept`, `Excluded`, and `Failed` to be
|
// We expect the inputs with `Swept`, `Excluded`, and `Failed` to be
|
||||||
@ -2530,6 +2545,7 @@ func TestUpdateSweeperInputs(t *testing.T) {
|
|||||||
{Index: 2}: input2,
|
{Index: 2}: input2,
|
||||||
{Index: 3}: input3,
|
{Index: 3}: input3,
|
||||||
{Index: 7}: input7,
|
{Index: 7}: input7,
|
||||||
|
{Index: 8}: input8,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect only the inputs with `Init` and `PublishFailed` to be
|
// We expect only the inputs with `Init` and `PublishFailed` to be
|
||||||
|
Loading…
x
Reference in New Issue
Block a user