htlcswitch: keep final htlc outcome

This commit is contained in:
Joost Jager committed 2022-10-27 16:42:36 +02:00
1 parent b85cda2a1d
commit 28256b7ea8
20 files changed
+466 -111

No files matched your search

+2 -2
View File
@@ -2451,7 +2451,7 @@ func forceStateTransition(chanA, chanB *lnwallet.LightningChannel) error {
return err
}
bobRevocation, _, err := chanB.RevokeCurrentCommitment()
bobRevocation, _, _, err := chanB.RevokeCurrentCommitment()
if err != nil {
return err
}
@@ -2468,7 +2468,7 @@ func forceStateTransition(chanA, chanB *lnwallet.LightningChannel) error {
return err
}
aliceRevocation, _, err := chanA.RevokeCurrentCommitment()
aliceRevocation, _, _, err := chanA.RevokeCurrentCommitment()
if err != nil {
return err
}
+5
View File
@@ -187,6 +187,11 @@ type ChainArbitratorConfig struct {
// complete.
SubscribeBreachComplete func(op *wire.OutPoint, c chan struct{}) (
bool, error)
// PutFinalHtlcOutcome stores the final outcome of an htlc in the
// database.
PutFinalHtlcOutcome func(chanId lnwire.ShortChannelID,
htlcId uint64, settled bool) error
}
// ChainArbitrator is a sub-system that oversees the on-chain resolution of all
+32
View File
@@ -1460,6 +1460,10 @@ const (
// other party time it out, or eventually learn of the pre-image, in
// which case we'll claim on chain.
HtlcIncomingWatchAction = 5
// HtlcIncomingDustFinalAction indicates that we should mark an incoming
// dust htlc as final because it can't be claimed on-chain.
HtlcIncomingDustFinalAction = 6
)
// String returns a human readable string describing a chain action.
@@ -1483,6 +1487,9 @@ func (c ChainAction) String() string {
case HtlcIncomingWatchAction:
return "HtlcIncomingWatchAction"
case HtlcIncomingDustFinalAction:
return "HtlcIncomingDustFinalAction"
default:
return "<unknown action>"
}
@@ -1698,6 +1705,10 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32,
"needed for incoming dust htlc=%x",
c.cfg.ChanPoint, htlc.RHash[:])
actionMap[HtlcIncomingDustFinalAction] = append(
actionMap[HtlcIncomingDustFinalAction], htlc,
)
continue
}
@@ -2213,6 +2224,27 @@ func (c *ChannelArbitrator) prepContractResolutions(
htlcResolvers = append(htlcResolvers, resolver)
}
// We've lost an htlc because it isn't manifested on the
// commitment transaction that closed the channel.
case HtlcIncomingDustFinalAction:
for _, htlc := range htlcs {
htlc := htlc
key := channeldb.CircuitKey{
ChanID: c.cfg.ShortChanID,
HtlcID: htlc.HtlcIndex,
}
// Mark this dust htlc as final failed.
chainArbCfg := c.cfg.ChainArbitratorConfig
err := chainArbCfg.PutFinalHtlcOutcome(
key.ChanID, key.HtlcID, false,
)
if err != nil {
return nil, nil, err
}
}
// Finally, if this is an outgoing HTLC we've sent, then we'll
// launch a resolver to watch for the pre-image (and settle
// backwards), or just timeout.
+16
View File
@@ -206,6 +206,8 @@ type chanArbTestCtx struct {
breachSubscribed chan struct{}
breachResolutionChan chan struct{}
finalHtlcs map[uint64]bool
}
func (c *chanArbTestCtx) CleanUp() {
@@ -306,6 +308,7 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
chanArbCtx := &chanArbTestCtx{
breachSubscribed: make(chan struct{}),
finalHtlcs: make(map[uint64]bool),
}
chanPoint := wire.OutPoint{}
@@ -360,6 +363,13 @@ func createTestChannelArbitrator(t *testing.T, log ArbitratorLog,
},
Clock: clock.NewDefaultClock(),
Sweeper: mockSweeper,
PutFinalHtlcOutcome: func(chanId lnwire.ShortChannelID,
htlcId uint64, settled bool) error {
chanArbCtx.finalHtlcs[htlcId] = settled
return nil
},
}
// We'll use the resolvedChan to synchronize on call to
@@ -966,6 +976,12 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
t.Fatalf("unable to stop chan arb: %v", err)
}
// Assert that a final resolution was stored for the incoming dust htlc.
expectedFinalHtlcs := map[uint64]bool{
incomingDustHtlc.HtlcIndex: false,
}
require.Equal(t, expectedFinalHtlcs, chanArbCtx.finalHtlcs)
// We'll no re-create the resolver, notice that we use the existing
// arbLog so it carries over the same on-disk state.
chanArbCtxNew, err := chanArbCtx.Restart(nil)
@@ -50,6 +50,18 @@ func newIncomingContestResolver(
}
}
func (h *htlcIncomingContestResolver) processFinalHtlcFail() error {
// Mark the htlc as final failed.
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, false,
)
if err != nil {
return err
}
return nil
}
// Resolve attempts to resolve this contract. As we don't yet know of the
// preimage for the contract, we'll wait for one of two things to happen:
//
@@ -83,6 +95,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
// link has ran.
h.resolved = true
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// We write a report to disk that indicates we could not decode
// the htlc.
resReport := h.report().resolverReport(
@@ -129,6 +145,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
h.htlcExpiry, currentHeight)
h.resolved = true
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// Finally, get our report and checkpoint our resolver with a
// timeout outcome report.
report := h.report().resolverReport(
@@ -201,6 +221,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
h.resolved = true
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
// Checkpoint our resolver with an abandoned outcome
// because we take no further action on this htlc.
report := h.report().resolverReport(
@@ -343,6 +367,10 @@ func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
h.htlcExpiry, currentHeight)
h.resolved = true
if err := h.processFinalHtlcFail(); err != nil {
return nil, err
}
report := h.report().resolverReport(
nil,
channeldb.ResolverTypeIncomingHtlc,
+32 -17
View File
@@ -16,6 +16,7 @@ import (
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
const (
@@ -298,14 +299,15 @@ func (o *mockOnionProcessor) ReconstructHopIterator(r io.Reader, rHash []byte) (
}
type incomingResolverTestContext struct {
registry *mockRegistry
witnessBeacon *mockWitnessBeacon
resolver *htlcIncomingContestResolver
notifier *mock.ChainNotifier
onionProcessor *mockOnionProcessor
resolveErr chan error
nextResolver ContractResolver
t *testing.T
registry *mockRegistry
witnessBeacon *mockWitnessBeacon
resolver *htlcIncomingContestResolver
notifier *mock.ChainNotifier
onionProcessor *mockOnionProcessor
resolveErr chan error
nextResolver ContractResolver
finalHtlcOutcomeStored bool
t *testing.T
}
func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolverTestContext {
@@ -323,12 +325,27 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
checkPointChan := make(chan struct{}, 1)
c := &incomingResolverTestContext{
registry: registry,
witnessBeacon: witnessBeacon,
notifier: notifier,
onionProcessor: onionProcessor,
t: t,
}
chainCfg := ChannelArbitratorConfig{
ChainArbitratorConfig: ChainArbitratorConfig{
Notifier: notifier,
PreimageDB: witnessBeacon,
Registry: registry,
OnionProcessor: onionProcessor,
PutFinalHtlcOutcome: func(chanId lnwire.ShortChannelID,
htlcId uint64, settled bool) error {
c.finalHtlcOutcomeStored = true
return nil
},
},
PutResolverReport: func(_ kvdb.RwTx,
_ *channeldb.ResolverReport) error {
@@ -346,7 +363,8 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
return nil
},
}
resolver := &htlcIncomingContestResolver{
c.resolver = &htlcIncomingContestResolver{
htlcSuccessResolver: &htlcSuccessResolver{
contractResolverKit: *newContractResolverKit(cfg),
htlcResolution: lnwallet.IncomingHtlcResolution{},
@@ -359,14 +377,7 @@ func newIncomingResolverTestContext(t *testing.T, isExit bool) *incomingResolver
htlcExpiry: testHtlcExpiry,
}
return &incomingResolverTestContext{
registry: registry,
witnessBeacon: witnessBeacon,
resolver: resolver,
notifier: notifier,
onionProcessor: onionProcessor,
t: t,
}
return c
}
func (i *incomingResolverTestContext) resolve() {
@@ -400,6 +411,10 @@ func (i *incomingResolverTestContext) waitForResult(expectSuccessRes bool) {
if i.nextResolver != nil {
i.t.Fatal("expected no next resolver")
}
require.True(i.t, i.finalHtlcOutcomeStored,
"expected final htlc outcome to be stored")
return
}
+8
View File
@@ -469,6 +469,14 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() (
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
outcome channeldb.ResolverOutcome) error {
// Mark the htlc as final settled.
err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
)
if err != nil {
return err
}
// Create a resolver report for claiming of the htlc itself.
amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
reports := []*channeldb.ResolverReport{
+21 -3
View File
@@ -31,6 +31,8 @@ type htlcResolverTestContext struct {
resolverResultChan chan resolveResult
resolutionChan chan ResolutionMsg
finalHtlcOutcomeStored bool
t *testing.T
}
@@ -75,6 +77,13 @@ func newHtlcResolverTestContext(t *testing.T,
testCtx.resolutionChan <- msgs[0]
return nil
},
PutFinalHtlcOutcome: func(chanId lnwire.ShortChannelID,
htlcId uint64, settled bool) error {
testCtx.finalHtlcOutcomeStored = true
return nil
},
},
PutResolverReport: func(_ kvdb.RwTx,
report *channeldb.ResolverReport) error {
@@ -186,6 +195,7 @@ func TestHtlcSuccessSingleStage(t *testing.T) {
reports: []*channeldb.ResolverReport{
claim,
},
finalHtlcStored: true,
},
}
@@ -269,6 +279,7 @@ func TestHtlcSuccessSecondStageResolution(t *testing.T) {
secondStage,
firstStage,
},
finalHtlcStored: true,
},
}
@@ -450,6 +461,7 @@ func TestHtlcSuccessSecondStageResolutionSweeper(t *testing.T) {
secondStage,
firstStage,
},
finalHtlcStored: true,
},
}
@@ -465,9 +477,10 @@ type checkpoint struct {
preCheckpoint func(*htlcResolverTestContext, bool) error
// data we expect the resolver to be checkpointed with next.
incubating bool
resolved bool
reports []*channeldb.ResolverReport
incubating bool
resolved bool
reports []*channeldb.ResolverReport
finalHtlcStored bool
}
// testHtlcSuccess tests resolution of a success resolver. It takes a a list of
@@ -573,6 +586,11 @@ func runFromCheckpoint(t *testing.T, ctx *htlcResolverTestContext,
}
}
// Check that the final htlc outcome is stored.
if cp.finalHtlcStored != ctx.finalHtlcOutcomeStored {
t.Fatal("final htlc store expectation failed")
}
// Finally encode the resolver, and store it for later use.
b := bytes.Buffer{}
if err := resolver.Encode(&b); err != nil {