contractcourt: simplify htlcTimeoutResolver, unify with HTLC contest logic
In this commit, we simplify the existing `htlcTImeoutResolver` with some newly refactored out methods from the `htlcTimeoutContestResolver`. The resulting logic is easier to follow as it's more linear, and only deals with spend notifications rather than both spend _and_ confirmation notifications.
This commit is contained in:
parent
e1a07b68e8
commit
6b24b6dabd
@ -511,8 +511,10 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
||||
|
||||
// The force close request should trigger broadcast of the commitment
|
||||
// transaction.
|
||||
assertStateTransitions(t, arbLog.newStates,
|
||||
StateBroadcastCommit, StateCommitmentBroadcasted)
|
||||
assertStateTransitions(
|
||||
t, arbLog.newStates, StateBroadcastCommit,
|
||||
StateCommitmentBroadcasted,
|
||||
)
|
||||
select {
|
||||
case <-respChan:
|
||||
case <-time.After(5 * time.Second):
|
||||
@ -529,7 +531,17 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
||||
}
|
||||
|
||||
// Now notify about the local force close getting confirmed.
|
||||
closeTx := &wire.MsgTx{}
|
||||
closeTx := &wire.MsgTx{
|
||||
TxIn: []*wire.TxIn{
|
||||
{
|
||||
PreviousOutPoint: wire.OutPoint{},
|
||||
Witness: [][]byte{
|
||||
{0x1},
|
||||
{0x2},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
htlcOp := wire.OutPoint{
|
||||
Hash: closeTx.TxHash(),
|
||||
@ -569,8 +581,10 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
||||
&channeldb.ChannelCloseSummary{},
|
||||
}
|
||||
|
||||
assertStateTransitions(t, arbLog.newStates, StateContractClosed,
|
||||
StateWaitingFullResolution)
|
||||
assertStateTransitions(
|
||||
t, arbLog.newStates, StateContractClosed,
|
||||
StateWaitingFullResolution,
|
||||
)
|
||||
|
||||
// htlcOutgoingContestResolver is now active and waiting for the HTLC to
|
||||
// expire. It should not yet have passed it on for incubation.
|
||||
@ -592,12 +606,13 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
||||
t.Fatalf("no response received")
|
||||
}
|
||||
|
||||
// Notify resolver that output of the commitment has been spent.
|
||||
notifier.confChan <- &chainntnfs.TxConfirmation{}
|
||||
// Notify resolver that the HTLC output of the commitment has been
|
||||
// spent.
|
||||
notifier.spendChan <- &chainntnfs.SpendDetail{SpendingTx: closeTx}
|
||||
|
||||
// As this is our own commitment transaction, the HTLC will go through
|
||||
// to the second level. Channel arbitrator should still not be marked as
|
||||
// resolved.
|
||||
// to the second level. Channel arbitrator should still not be marked
|
||||
// as resolved.
|
||||
select {
|
||||
case <-resolved:
|
||||
t.Fatalf("channel resolved prematurely")
|
||||
@ -605,7 +620,7 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
|
||||
}
|
||||
|
||||
// Notify resolver that the second level transaction is spent.
|
||||
notifier.spendChan <- &chainntnfs.SpendDetail{}
|
||||
notifier.spendChan <- &chainntnfs.SpendDetail{SpendingTx: closeTx}
|
||||
|
||||
// At this point channel should be marked as resolved.
|
||||
assertStateTransitions(t, arbLog.newStates, StateFullyResolved)
|
||||
|
@ -91,7 +91,9 @@ const (
|
||||
// claimCleanUp is a helper method that's called once the HTLC output is spent
|
||||
// by the remote party. It'll extract the preimage, add it to the global cache,
|
||||
// and finally send the appropriate clean up message.
|
||||
func (h *htlcTimeoutResolver) claimCleanUp(commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
|
||||
func (h *htlcTimeoutResolver) claimCleanUp(
|
||||
commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
|
||||
|
||||
// Depending on if this is our commitment or not, then we'll be looking
|
||||
// for a different witness pattern.
|
||||
spenderIndex := commitSpend.SpenderInputIndex
|
||||
@ -172,15 +174,51 @@ func (h *htlcTimeoutResolver) chainDetailsToWatch() (*wire.OutPoint, []byte, err
|
||||
// we need to watch.
|
||||
outPointToWatch := h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
|
||||
witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness
|
||||
scriptToWatch, err := input.WitnessScriptHash(
|
||||
witness[len(witness)-1],
|
||||
)
|
||||
scriptToWatch, err := input.WitnessScriptHash(witness[len(witness)-1])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return &outPointToWatch, scriptToWatch, nil
|
||||
}
|
||||
|
||||
// isSuccessSpend returns true if the passed spend on the specified commitment
|
||||
// is a success spend that reveals the pre-image or not.
|
||||
func isSuccessSpend(spend *chainntnfs.SpendDetail, localCommit bool) bool {
|
||||
// Based on the spending input index and transaction, obtain the
|
||||
// witness that tells us what type of spend this is.
|
||||
spenderIndex := spend.SpenderInputIndex
|
||||
spendingInput := spend.SpendingTx.TxIn[spenderIndex]
|
||||
spendingWitness := spendingInput.Witness
|
||||
|
||||
// If this is the remote commitment then the only possible spends for
|
||||
// outgoing HTLCs are:
|
||||
//
|
||||
// RECVR: <0> <sender sig> <recvr sig> <preimage> (2nd level success spend)
|
||||
// REVOK: <sig> <key>
|
||||
// SENDR: <sig> 0
|
||||
//
|
||||
// In this case, if 5 witness elements are present (factoring the
|
||||
// witness script), and the 3rd element is the size of the pre-image,
|
||||
// then this is a remote spend. If not, then we swept it ourselves, or
|
||||
// revoked their output.
|
||||
if !localCommit {
|
||||
return len(spendingWitness) == expectedRemoteWitnessSuccessSize &&
|
||||
len(spendingWitness[remotePreimageIndex]) == lntypes.HashSize
|
||||
}
|
||||
|
||||
// Otherwise, for our commitment, the only possible spends for an
|
||||
// outgoing HTLC are:
|
||||
//
|
||||
// SENDR: <0> <sendr sig> <recvr sig> <0> (2nd level timeout)
|
||||
// RECVR: <recvr sig> <preimage>
|
||||
// REVOK: <revoke sig> <revoke key>
|
||||
//
|
||||
// So the only success case has the pre-image as the 2nd (index 1)
|
||||
// element in the witness.
|
||||
return len(spendingWitness[localPreimageIndex]) == lntypes.HashSize
|
||||
}
|
||||
|
||||
// Resolve kicks off full resolution of an outgoing HTLC output. If it's our
|
||||
// commitment, it isn't resolved until we see the second level HTLC txn
|
||||
// confirmed. If it's the remote party's commitment, we don't resolve until we
|
||||
@ -243,40 +281,33 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// With the output sent to the nursery, we'll now wait until the output
|
||||
// has been fully resolved before sending the clean up message.
|
||||
//
|
||||
// TODO(roasbeef): need to be able to cancel nursery?
|
||||
// * if they pull on-chain while we're waiting
|
||||
|
||||
// If we don't have a second layer transaction, then this is a remote
|
||||
// party's commitment, so we'll watch for a direct spend.
|
||||
if h.htlcResolution.SignedTimeoutTx == nil {
|
||||
// We'll block until: the HTLC output has been spent, and the
|
||||
// transaction spending that output is sufficiently confirmed.
|
||||
log.Infof("%T(%v): waiting for nursery to spend CLTV-locked "+
|
||||
"output", h, h.htlcResolution.ClaimOutpoint)
|
||||
if err := waitForOutputResolution(); err != nil {
|
||||
// Now that we've handed off the HTLC to the nursery, we'll watch for a
|
||||
// spend of the output, and make our next move off of that. Depending
|
||||
// on if this is our commitment, or the remote party's commitment,
|
||||
// we'll be watching a different outpoint and script.
|
||||
outpointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Otherwise, this is our commitment, so we'll watch for the
|
||||
// second-level transaction to be sufficiently confirmed.
|
||||
secondLevelTXID := h.htlcResolution.SignedTimeoutTx.TxHash()
|
||||
sweepScript := h.htlcResolution.SignedTimeoutTx.TxOut[0].PkScript
|
||||
confNtfn, err := h.Notifier.RegisterConfirmationsNtfn(
|
||||
&secondLevelTXID, sweepScript, 1, h.broadcastHeight,
|
||||
spendNtfn, err := h.Notifier.RegisterSpendNtfn(
|
||||
outpointToWatch, scriptToWatch, h.broadcastHeight,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("%T(%v): waiting second-level tx (txid=%v) to be "+
|
||||
log.Infof("%T(%v): waiting for HTLC output %v to be spent"+
|
||||
"fully confirmed", h, h.htlcResolution.ClaimOutpoint,
|
||||
secondLevelTXID)
|
||||
outpointToWatch)
|
||||
|
||||
// We'll block here until either we exit, or the HTLC output on the
|
||||
// commitment transaction has been spent.
|
||||
var (
|
||||
spend *chainntnfs.SpendDetail
|
||||
ok bool
|
||||
)
|
||||
select {
|
||||
case _, ok := <-confNtfn.Confirmed:
|
||||
case spend, ok = <-spendNtfn.Spend:
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("quitting")
|
||||
}
|
||||
@ -284,19 +315,25 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
case <-h.Quit:
|
||||
return nil, fmt.Errorf("quitting")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(roasbeef): need to watch for remote party sweeping with pre-image?
|
||||
// * have another waiting on spend above, will check the type, if it's
|
||||
// pre-image, then we'll cancel, and send a clean up back with
|
||||
// pre-image, also add to preimage cache
|
||||
// If the spend reveals the pre-image, then we'll enter the clean up
|
||||
// workflow to pass the pre-image back to the incoming link, add it to
|
||||
// the witness cache, and exit.
|
||||
if isSuccessSpend(spend, h.htlcResolution.SignedTimeoutTx != nil) {
|
||||
log.Infof("%T(%v): HTLC has been swept with pre-image by "+
|
||||
"remote party during timeout flow! Adding pre-image to "+
|
||||
"witness cache", h.htlcResolution.ClaimOutpoint)
|
||||
|
||||
return h.claimCleanUp(spend)
|
||||
}
|
||||
|
||||
log.Infof("%T(%v): resolving htlc with incoming fail msg, fully "+
|
||||
"confirmed", h, h.htlcResolution.ClaimOutpoint)
|
||||
|
||||
// At this point, the second-level transaction is sufficiently
|
||||
// confirmed, or a transaction directly spending the output is.
|
||||
// Therefore, we can now send back our clean up message.
|
||||
// Therefore, we can now send back our clean up message, failing the
|
||||
// HTLC on the incoming link.
|
||||
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
||||
if err := h.DeliverResolutionMsg(ResolutionMsg{
|
||||
SourceChan: h.ShortChanID,
|
||||
@ -307,7 +344,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) {
|
||||
}
|
||||
|
||||
// Finally, if this was an output on our commitment transaction, we'll
|
||||
// for the second-level HTLC output to be spent, and for that
|
||||
// wait for the second-level HTLC output to be spent, and for that
|
||||
// transaction itself to confirm.
|
||||
if h.htlcResolution.SignedTimeoutTx != nil {
|
||||
log.Infof("%T(%v): waiting for nursery to spend CSV delayed "+
|
||||
|
Loading…
Reference in New Issue
Block a user