htlcswitch: keep final htlc outcome
This commit is contained in:
20 files changed
+466
-111
No files matched your search
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user