From 28256b7ea88580954b202aff2f5322930d850120 Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 10 May 2022 12:33:44 +0200 Subject: [PATCH] htlcswitch: keep final htlc outcome --- channeldb/channel.go | 172 +++++++++++++++++- channeldb/channel_test.go | 40 +++- channeldb/db.go | 21 +++ channeldb/db_test.go | 2 +- contractcourt/breacharbiter_test.go | 4 +- contractcourt/chain_arbitrator.go | 5 + contractcourt/channel_arbitrator.go | 32 ++++ contractcourt/channel_arbitrator_test.go | 16 ++ .../htlc_incoming_contest_resolver.go | 28 +++ contractcourt/htlc_incoming_resolver_test.go | 49 +++-- contractcourt/htlc_success_resolver.go | 8 + contractcourt/htlc_success_resolver_test.go | 24 ++- htlcswitch/link.go | 3 +- htlcswitch/link_isolated_test.go | 2 +- htlcswitch/link_test.go | 6 +- lnwallet/channel.go | 16 +- lnwallet/channel_test.go | 140 +++++++------- lnwallet/test_utils.go | 4 +- lnwallet/transactions_test.go | 4 +- server.go | 1 + 20 files changed, 466 insertions(+), 111 deletions(-) diff --git a/channeldb/channel.go b/channeldb/channel.go index d1408748d..58956620e 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -16,6 +16,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcwallet/walletdb" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -135,6 +136,26 @@ var ( // sent was a revocation and false when it was a commitment signature. // This is nil in the case of new channels with no updates exchanged. lastWasRevokeKey = []byte("last-was-revoke") + + // finalHtlcsBucket contains the htlcs that have been resolved + // definitively. Within this bucket, there is a sub-bucket for each + // channel. In each channel bucket, the htlc indices are stored along + // with final outcome. + // + // final-htlcs -> chanID -> htlcIndex -> outcome + // + // 'outcome' is a byte value that encodes: + // + // | true false + // ------+------------------ + // bit 0 | settled failed + // bit 1 | offchain onchain + // + // This bucket is positioned at the root level, because its contents + // will be kept independent of the channel lifecycle. This is to avoid + // the situation where a channel force-closes autonomously and the user + // not being able to query for htlc outcomes anymore. + finalHtlcsBucket = []byte("final-htlcs") ) var ( @@ -618,6 +639,20 @@ func (c ChannelStatus) String() string { return statusStr } +// FinalHtlcByte defines a byte type that encodes information about the final +// htlc resolution. +type FinalHtlcByte byte + +const ( + // FinalHtlcSettledBit is the bit that encodes whether the htlc was + // settled or failed. + FinalHtlcSettledBit FinalHtlcByte = 1 << 0 + + // FinalHtlcOffchainBit is the bit that encodes whether the htlc was + // resolved offchain or onchain. + FinalHtlcOffchainBit FinalHtlcByte = 1 << 1 +) + // OpenChannel encapsulates the persistent and dynamic state of an open channel // with a remote node. An open channel supports several options for on-disk // serialization depending on the exact context. Full (upon channel creation) @@ -1043,6 +1078,26 @@ func fetchChanBucketRw(tx kvdb.RwTx, nodeKey *btcec.PublicKey, return chanBucket, nil } +func fetchFinalHtlcsBucketRw(tx kvdb.RwTx, + chanID lnwire.ShortChannelID) (kvdb.RwBucket, error) { + + finalHtlcsBucket, err := tx.CreateTopLevelBucket(finalHtlcsBucket) + if err != nil { + return nil, err + } + + var chanIDBytes [8]byte + byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64()) + chanBucket, err := finalHtlcsBucket.CreateBucketIfNotExists( + chanIDBytes[:], + ) + if err != nil { + return nil, err + } + + return chanBucket, nil +} + // fullSync syncs the contents of an OpenChannel while re-using an existing // database transaction. func (c *OpenChannel) fullSync(tx kvdb.RwTx) error { @@ -1739,8 +1794,12 @@ func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr) error { // persisted to be able to produce a valid commit signature if a restart would // occur. This method its to be called when we revoke our prior commitment // state. +// +// A map is returned of all the htlc resolutions that were locked in in this +// commitment. Keys correspond to htlc indices and values indicate whether the +// htlc was settled or failed. func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, - unsignedAckedUpdates []LogUpdate) error { + unsignedAckedUpdates []LogUpdate) (map[uint64]bool, error) { c.Lock() defer c.Unlock() @@ -1749,9 +1808,11 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, // state as all, as it's impossible to do so in a protocol compliant // manner. if c.hasChanStatus(ChanStatusRestored) { - return ErrNoRestoredChannelMutation + return nil, ErrNoRestoredChannelMutation } + var finalHtlcs = make(map[uint64]bool) + err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error { chanBucket, err := fetchChanBucketRw( tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash, @@ -1822,17 +1883,35 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, return err } - var validUpdates []LogUpdate + // Get the bucket where settled htlcs are recorded. + finalHtlcsBucket, err := fetchFinalHtlcsBucketRw( + tx, c.ShortChannelID, + ) + if err != nil { + return err + } + + var unsignedUpdates []LogUpdate for _, upd := range updates { - // Filter for updates that are not on our local - // commitment. + // Gather updates that are not on our local commitment. if upd.LogIndex >= newCommitment.LocalLogIndex { - validUpdates = append(validUpdates, upd) + unsignedUpdates = append(unsignedUpdates, upd) + + continue + } + + // The update was locked in. If the update was a + // resolution, then store it in the database. + err := processFinalHtlc( + finalHtlcsBucket, upd, finalHtlcs, + ) + if err != nil { + return err } } var b3 bytes.Buffer - err = serializeLogUpdates(&b3, validUpdates) + err = serializeLogUpdates(&b3, unsignedUpdates) if err != nil { return fmt.Errorf("unable to serialize log updates: %v", err) } @@ -1843,12 +1922,57 @@ func (c *OpenChannel) UpdateCommitment(newCommitment *ChannelCommitment, } return nil - }, func() {}) + }, func() { + finalHtlcs = make(map[uint64]bool) + }) + if err != nil { + return nil, err + } + + c.LocalCommitment = *newCommitment + + return finalHtlcs, nil +} + +// processFinalHtlc stores a final htlc outcome in the database if signaled via +// the supplied log update. An in-memory htlcs map is updated too. +func processFinalHtlc(finalHtlcsBucket walletdb.ReadWriteBucket, upd LogUpdate, + finalHtlcs map[uint64]bool) error { + + var ( + settled bool + id uint64 + ) + + switch msg := upd.UpdateMsg.(type) { + case *lnwire.UpdateFulfillHTLC: + settled = true + id = msg.ID + + case *lnwire.UpdateFailHTLC: + settled = false + id = msg.ID + + case *lnwire.UpdateFailMalformedHTLC: + settled = false + id = msg.ID + + default: + return nil + } + + err := putFinalHtlc( + finalHtlcsBucket, id, + FinalHtlcInfo{ + Settled: settled, + Offchain: true, + }, + ) if err != nil { return err } - c.LocalCommitment = *newCommitment + finalHtlcs[id] = settled return nil } @@ -2681,6 +2805,36 @@ func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg, return nil } +// FinalHtlcInfo contains information about the final outcome of an htlc. +type FinalHtlcInfo struct { + // Settled is true is the htlc was settled. If false, the htlc was + // failed. + Settled bool + + // Offchain indicates whether the htlc was resolved off-chain or + // on-chain. + Offchain bool +} + +// putFinalHtlc writes the final htlc outcome to the database. Additionally it +// records whether the htlc was resolved off-chain or on-chain. +func putFinalHtlc(finalHtlcsBucket kvdb.RwBucket, id uint64, + info FinalHtlcInfo) error { + + var key [8]byte + byteOrder.PutUint64(key[:], id) + + var finalHtlcByte FinalHtlcByte + if info.Settled { + finalHtlcByte |= FinalHtlcSettledBit + } + if info.Offchain { + finalHtlcByte |= FinalHtlcOffchainBit + } + + return finalHtlcsBucket.Put(key[:], []byte{byte(finalHtlcByte)}) +} + // NextLocalHtlcIndex returns the next unallocated local htlc index. To ensure // this always returns the next index that has been not been allocated, this // will first try to examine any pending commitments, before falling back to the diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 01145489c..11aa02168 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -641,7 +641,7 @@ func TestChannelStateTransition(t *testing.T) { }, } - err = channel.UpdateCommitment(&commitment, unsignedAckedUpdates) + _, err = channel.UpdateCommitment(&commitment, unsignedAckedUpdates) require.NoError(t, err, "unable to update commitment") // Assert that update is correctly written to the database. @@ -1457,3 +1457,41 @@ func TestKeyLocatorEncoding(t *testing.T) { // version are equal. require.Equal(t, keyLoc, decodedKeyLoc) } + +// TestFinalHtlcs tests final htlc storage and retrieval. +func TestFinalHtlcs(t *testing.T) { + t.Parallel() + + fullDB, err := MakeTestDB(t) + require.NoError(t, err, "unable to make test database") + + cdb := fullDB.ChannelStateDB() + + chanID := lnwire.ShortChannelID{ + BlockHeight: 1, + TxIndex: 2, + TxPosition: 3, + } + + // Test offchain final htlcs. + const offchainHtlcID = 1 + + err = kvdb.Update(cdb.backend, func(tx kvdb.RwTx) error { + bucket, err := fetchFinalHtlcsBucketRw( + tx, chanID, + ) + require.NoError(t, err) + + return putFinalHtlc(bucket, offchainHtlcID, FinalHtlcInfo{ + Settled: true, + Offchain: true, + }) + }, func() {}) + require.NoError(t, err) + + // Test onchain final htlcs. + const onchainHtlcID = 2 + + err = cdb.PutOnchainFinalHtlcOutcome(chanID, onchainHtlcID, true) + require.NoError(t, err) +} diff --git a/channeldb/db.go b/channeldb/db.go index f74745c23..5d73a99b6 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -1652,6 +1652,27 @@ func (c *ChannelStateDB) FetchHistoricalChannel(outPoint *wire.OutPoint) ( return channel, nil } +// PutOnchainFinalHtlcOutcome stores the final on-chain outcome of an htlc in +// the database. +func (c *ChannelStateDB) PutOnchainFinalHtlcOutcome( + chanID lnwire.ShortChannelID, htlcID uint64, settled bool) error { + + return kvdb.Update(c.backend, func(tx kvdb.RwTx) error { + finalHtlcsBucket, err := fetchFinalHtlcsBucketRw(tx, chanID) + if err != nil { + return err + } + + return putFinalHtlc( + finalHtlcsBucket, htlcID, + FinalHtlcInfo{ + Settled: settled, + Offchain: false, + }, + ) + }, func() {}) +} + // MakeTestDB creates a new instance of the ChannelDB for testing purposes. // A callback which cleans up the created temporary directories is also // returned and intended to be executed after the test completes. diff --git a/channeldb/db_test.go b/channeldb/db_test.go index 72a5cb4bd..e2cad1fd1 100644 --- a/channeldb/db_test.go +++ b/channeldb/db_test.go @@ -355,7 +355,7 @@ func TestRestoreChannelShells(t *testing.T) { // Ensure that it isn't possible to modify the commitment state machine // of this restored channel. channel := nodeChans[0] - err = channel.UpdateCommitment(nil, nil) + _, err = channel.UpdateCommitment(nil, nil) if err != ErrNoRestoredChannelMutation { t.Fatalf("able to mutate restored channel") } diff --git a/contractcourt/breacharbiter_test.go b/contractcourt/breacharbiter_test.go index e9d274f41..3dbbca3f9 100644 --- a/contractcourt/breacharbiter_test.go +++ b/contractcourt/breacharbiter_test.go @@ -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 } diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index df99dc48e..fbb873bbd 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -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 diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 6362b36fc..706cd7615 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -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 "" } @@ -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. diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 6b9680e67..7d2590e9f 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -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) diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index 356846329..cf9ecb408 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -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, diff --git a/contractcourt/htlc_incoming_resolver_test.go b/contractcourt/htlc_incoming_resolver_test.go index 3e78f1a29..df4e22519 100644 --- a/contractcourt/htlc_incoming_resolver_test.go +++ b/contractcourt/htlc_incoming_resolver_test.go @@ -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 } diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index e26622021..503059f66 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -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{ diff --git a/contractcourt/htlc_success_resolver_test.go b/contractcourt/htlc_success_resolver_test.go index 7e9adcf81..093840096 100644 --- a/contractcourt/htlc_success_resolver_test.go +++ b/contractcourt/htlc_success_resolver_test.go @@ -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 { diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 14c7c266a..a6f4c46c7 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -1920,7 +1920,8 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) { // As we've just accepted a new state, we'll now // immediately send the remote peer a revocation for our prior // state. - nextRevocation, currentHtlcs, err := l.channel.RevokeCurrentCommitment() + nextRevocation, currentHtlcs, _, err := + l.channel.RevokeCurrentCommitment() if err != nil { l.log.Errorf("unable to revoke commitment: %v", err) return diff --git a/htlcswitch/link_isolated_test.go b/htlcswitch/link_isolated_test.go index 85566f7a7..7204a5873 100644 --- a/htlcswitch/link_isolated_test.go +++ b/htlcswitch/link_isolated_test.go @@ -179,7 +179,7 @@ func (l *linkTestContext) receiveCommitSigAlice(expHtlcs int) *lnwire.CommitSig func (l *linkTestContext) sendRevAndAckBobToAlice() { l.t.Helper() - rev, _, err := l.bobChannel.RevokeCurrentCommitment() + rev, _, _, err := l.bobChannel.RevokeCurrentCommitment() if err != nil { l.t.Fatalf("unable to revoke commitment: %v", err) } diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go index d3f4d1a5f..d2387ff13 100644 --- a/htlcswitch/link_test.go +++ b/htlcswitch/link_test.go @@ -1958,7 +1958,7 @@ func handleStateUpdate(link *channelLink, return err } - remoteRev, _, err := remoteChannel.RevokeCurrentCommitment() + remoteRev, _, _, err := remoteChannel.RevokeCurrentCommitment() if err != nil { return err } @@ -2062,7 +2062,7 @@ func updateState(batchTick chan time.Time, link *channelLink, } // Lastly, send a revocation back to the link. - remoteRev, _, err := remoteChannel.RevokeCurrentCommitment() + remoteRev, _, _, err := remoteChannel.RevokeCurrentCommitment() if err != nil { return err } @@ -3134,7 +3134,7 @@ func TestChannelLinkTrimCircuitsRemoteCommit(t *testing.T) { // Next, revoke Bob's current commitment and send it to Alice so that we // can test that Alice's circuits aren't trimmed. - rev, _, err := bobChan.RevokeCurrentCommitment() + rev, _, _, err := bobChan.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke current commitment") _, _, _, _, err = alice.channel.ReceiveRevocation(rev) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index dd202aac2..acdfdea05 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -4779,15 +4779,17 @@ func (lc *LightningChannel) PendingLocalUpdateCount() uint64 { // chain is advanced by a single commitment. This now lowest unrevoked // commitment becomes our currently accepted state within the channel. This // method also returns the set of HTLC's currently active within the commitment -// transaction. This return value allows callers to act once an HTLC has been -// locked into our commitment transaction. -func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []channeldb.HTLC, error) { +// transaction and the htlcs the were resolved. This return value allows callers +// to act once an HTLC has been locked into our commitment transaction. +func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, + []channeldb.HTLC, map[uint64]bool, error) { + lc.Lock() defer lc.Unlock() revocationMsg, err := lc.generateRevocation(lc.currentHeight) if err != nil { - return nil, nil, err + return nil, nil, nil, err } lc.log.Tracef("revoking height=%v, now at height=%v", @@ -4808,11 +4810,11 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c // is committed locally. unsignedAckedUpdates := lc.getUnsignedAckedUpdates() - err = lc.channelState.UpdateCommitment( + finalHtlcs, err := lc.channelState.UpdateCommitment( newCommitment, unsignedAckedUpdates, ) if err != nil { - return nil, nil, err + return nil, nil, nil, err } lc.log.Tracef("state transition accepted: "+ @@ -4825,7 +4827,7 @@ func (lc *LightningChannel) RevokeCurrentCommitment() (*lnwire.RevokeAndAck, []c &lc.channelState.FundingOutpoint, ) - return revocationMsg, newCommitment.Htlcs, nil + return revocationMsg, newCommitment.Htlcs, finalHtlcs, nil } // ReceiveRevocation processes a revocation sent by the remote party for the diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index f35f4137d..1ed6e6e3e 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -104,7 +104,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { // Bob revokes his prior commitment given to him by Alice, since he now // has a valid signature for a newer commitment. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") // Bob finally send a signature for Alice's commitment transaction. @@ -135,7 +135,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { require.NoError(t, err, "alice unable to process bob's new commitment") // Alice then generates a revocation for bob. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke alice channel") // Finally Bob processes Alice's revocation, at this point the new HTLC @@ -220,7 +220,7 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { err = aliceChannel.ReceiveNewCommitment(bobSig2, bobHtlcSigs2) require.NoError(t, err, "alice unable to process bob's new commitment") - aliceRevocation2, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation2, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to generate revocation") aliceSig2, aliceHtlcSigs2, _, err := aliceChannel.SignNextCommitment() require.NoError(t, err, "alice unable to sign new commitment") @@ -239,8 +239,17 @@ func testAddSettleWorkflow(t *testing.T, tweakless bool) { err = bobChannel.ReceiveNewCommitment(aliceSig2, aliceHtlcSigs2) require.NoError(t, err, "bob unable to process alice's new commitment") - bobRevocation2, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation2, _, finalHtlcs, err := bobChannel. + RevokeCurrentCommitment() + require.NoError(t, err, "bob unable to revoke commitment") + + // Check finalHtlcs for the expected final resolution. + require.Len(t, finalHtlcs, 1, "final htlc expected") + for _, settled := range finalHtlcs { + require.True(t, settled, "final settle expected") + } + fwdPkg, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation2) require.NoError(t, err, "alice unable to process bob's revocation") if len(fwdPkg.Adds) != 0 { @@ -393,7 +402,7 @@ func TestChannelZeroAddLocalHeight(t *testing.T) { // Alice should reply with a revocation. // -----rev-----> - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) @@ -570,7 +579,7 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) { err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "unable to receive alice's commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob's commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive bob's revocation") @@ -2312,7 +2321,7 @@ func TestUpdateFeeConcurrentSig(t *testing.T) { // Bob can revoke the prior commitment he had. This should lock in the // fee update for him. - _, _, err = bobChannel.RevokeCurrentCommitment() + _, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") if chainfee.SatPerKWeight(bobChannel.channelState.LocalCommitment.FeePerKw) != fee { @@ -2378,7 +2387,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Bob can revoke the prior commitment he had. This should lock in the // fee update for him. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") if chainfee.SatPerKWeight( @@ -2413,7 +2422,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Alice can revoke the old commitment, which will lock in the fee // update. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke alice channel") if chainfee.SatPerKWeight( @@ -2480,7 +2489,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Alice can revoke the prior commitment she had, this will ack // everything received before last commitment signature, but in this // case that is nothing. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") // Bob receives the revocation of the old commitment @@ -2508,7 +2517,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Bob can revoke the old commitment. This will ack what he has // received, including the HTLC and fee update. This will lock in the // fee update for bob. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke alice channel") if chainfee.SatPerKWeight( @@ -2542,7 +2551,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // After Alice now revokes her old commitment, the fee update should // lock in. - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") if chainfee.SatPerKWeight( @@ -2644,7 +2653,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Bob can revoke the prior commitment he had. This should lock in the // fee update for him. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to generate bob revocation") if chainfee.SatPerKWeight( @@ -2680,7 +2689,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Alice can revoke the old commitment, which will lock in the fee // update. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke alice channel") if chainfee.SatPerKWeight( @@ -3074,7 +3083,7 @@ func TestChanSyncOweCommitment(t *testing.T) { // adding one of her own. err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "bob unable to process alice's commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") @@ -3082,7 +3091,7 @@ func TestChanSyncOweCommitment(t *testing.T) { require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "alice unable to rev bob's commitment") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -3234,7 +3243,7 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { // completes, the htlc is settled on the local commitment // transaction. Bob still owes Alice a signature to also settle // the htlc on her local commitment transaction. - bobRevoke, _, err := bobChannel.RevokeCurrentCommitment() + bobRevoke, _, _, err := bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("unable to revoke commitment: %v", err) } @@ -3264,7 +3273,7 @@ func TestChanSyncOweCommitmentPendingRemote(t *testing.T) { if err != nil { t.Fatal(err) } - aliceRevoke, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevoke, _, _, err := aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -3330,7 +3339,7 @@ func TestChanSyncOweRevocation(t *testing.T) { err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "bob unable to process alice's commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") @@ -3342,7 +3351,7 @@ func TestChanSyncOweRevocation(t *testing.T) { // At this point, we'll simulate the connection breaking down by Bob's // lack of knowledge of the revocation message that Alice just sent. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") // If we fetch the channel sync messages at this state, then Alice @@ -3482,7 +3491,7 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // Bob generates the revoke and sig message, but the messages don't // reach Alice before the connection dies. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") @@ -3559,7 +3568,7 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "alice unable to rev bob's commitment") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -3630,7 +3639,7 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // signature for Bob's updated state. Instead she will issue a new // update before sending a new CommitSig. This will lead to Alice's // local commit chain getting height > remote commit chain. - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -3652,7 +3661,7 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // Bob then sends his revocation message, but before Alice can process // it (and before he scan send his CommitSig message), then connection // dies. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") // Now if we attempt to synchronize states at this point, Alice should @@ -3746,7 +3755,7 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { bobSigMsg.CommitSig, bobSigMsg.HtlcSigs, ) require.NoError(t, err, "alice unable to rev bob's commitment") - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -4132,7 +4141,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "bob unable to process alice's commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") @@ -4140,7 +4149,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "alice unable to rev bob's commitment") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -4330,7 +4339,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { // transition. err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "bob unable to process alice's commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSig, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "bob unable to sign commitment") @@ -4338,7 +4347,7 @@ func TestFeeUpdateOldDiskFormat(t *testing.T) { require.NoError(t, err, "alice unable to recv revocation") err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "alice unable to rev bob's commitment") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "alice unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to recv revocation") @@ -5013,7 +5022,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { if err != nil { t.Fatal(err) } - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5043,7 +5052,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { if err != nil { t.Fatal(err) } - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5087,7 +5096,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { if err != nil { t.Fatal(err) } - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5130,11 +5139,18 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { err = aliceChannel.ReceiveFailHTLC(htlc2.ID, []byte("bad")) require.NoError(t, err, "unable to recv htlc cancel") - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, finalHtlcs, err := bobChannel. + RevokeCurrentCommitment() if err != nil { t.Fatal(err) } + // Check finalHtlcs for the expected final resolution. + require.Len(t, finalHtlcs, 1, "final htlc expected") + for _, settled := range finalHtlcs { + require.False(t, settled, "final fail expected") + } + // Alice should detect that she doesn't need to forward any Adds's, but // that the Fail has been locked in an can be forwarded. _, adds, settleFails, _, err := aliceChannel.ReceiveRevocation(bobRevocation) @@ -5179,7 +5195,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { if err != nil { t.Fatal(err) } - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5210,7 +5226,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { t.Fatal(err) } - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5240,7 +5256,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { if err != nil { t.Fatal(err) } - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatal(err) } @@ -5852,13 +5868,13 @@ func TestMaxAsynchronousHtlcs(t *testing.T) { require.NoError(t, err, "unable to receive new commitment") // Both sides exchange revocations as in step 4 & 5. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke revocation") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke revocation") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) @@ -6549,7 +6565,7 @@ func TestChannelRestoreUpdateLogs(t *testing.T) { // Bob receives this commitment signature, and revokes his old state. err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "unable to receive commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") // When Alice now receives this revocation, she will advance her remote @@ -6728,7 +6744,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { assertInLogs(t, aliceChannel, 1, 0, 0, 1) restoreAndAssert(t, aliceChannel, 1, 0, 0, 0) - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err, "bob unable to process alice's revocation") @@ -6758,7 +6774,7 @@ func TestChannelRestoreUpdateLogsFailedHTLC(t *testing.T) { // When Alice receives Bob's revocation, the Fail is irrevocably locked // in on both sides. She should compact the logs, removing the HTLC and // the corresponding Fail from the local update log. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err, "unable to receive revocation") @@ -6974,7 +6990,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Bob receives this commitment signature, and revokes his old state. err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "unable to receive commitment") - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") // Now the HTLC is locked into Bob's commitment, a restoration should @@ -7004,7 +7020,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "unable to receive commitment") - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") // Now both the local and remote add heights should be properly set. @@ -7046,7 +7062,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { err = bobChannel.ReceiveNewCommitment(aliceSig, aliceHtlcSigs) require.NoError(t, err, "unable to receive commitment") - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") // Since Bob just revoked another commitment, a restoration should @@ -7082,7 +7098,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Alice should receive the commitment and send over a revocation. err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "unable to receive commitment") - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") // Both heights should be 2 and they are on both commitments. @@ -7119,7 +7135,7 @@ func TestChannelRestoreCommitHeight(t *testing.T) { // Alice receives commitment, sends revocation. err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err, "unable to receive commitment") - _, _, err = aliceChannel.RevokeCurrentCommitment() + _, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke commitment") aliceChannel = restoreAndAssertCommitHeights( @@ -7173,7 +7189,7 @@ func TestForceCloseBorkedState(t *testing.T) { require.NoError(t, err, "unable to sign commit") err = bobChannel.ReceiveNewCommitment(aliceSigs, aliceHtlcSigs) require.NoError(t, err, "unable to receive commitment") - revokeMsg, _, err := bobChannel.RevokeCurrentCommitment() + revokeMsg, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err, "unable to revoke bob commitment") bobSigs, bobHtlcSigs, _, err := bobChannel.SignNextCommitment() require.NoError(t, err, "unable to sign commit") @@ -7213,7 +7229,7 @@ func TestForceCloseBorkedState(t *testing.T) { if err != channeldb.ErrChanBorked { t.Fatalf("sign commitment should have failed: %v", err) } - _, _, err = aliceChannel.RevokeCurrentCommitment() + _, _, _, err = aliceChannel.RevokeCurrentCommitment() if err != channeldb.ErrChanBorked { t.Fatalf("append remove chain tail should have failed") } @@ -8792,7 +8808,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Alice should reply with a revocation. // -----rev-----> - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) @@ -8817,7 +8833,7 @@ func TestChannelUnsignedAckedFailure(t *testing.T) { // Bob revokes his current commitment and sends a revocation // to Alice. // <----rev------ - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = newAliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -8903,7 +8919,7 @@ func TestChannelLocalUnsignedUpdatesFailure(t *testing.T) { // Bob should reply with a revocation and Alice should save the fail as // an unsigned local update. // <----rev----- - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -8987,7 +9003,7 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // <----rev----- - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9014,7 +9030,7 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // <----rev----- - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9027,7 +9043,7 @@ func TestChannelSignedAckRegression(t *testing.T) { require.NoError(t, err) // -----rev----> - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) fwdPkg, _, _, _, err := newBobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) @@ -9119,7 +9135,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---rev--- - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9133,7 +9149,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---rev---> - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) @@ -9154,7 +9170,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // ---rev---> - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) @@ -9168,7 +9184,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---rev--- - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9192,7 +9208,7 @@ func TestIsChannelClean(t *testing.T) { assertCleanOrDirty(false, aliceChannel, bobChannel, t) // <---rev--- - bobRevocation, _, err = bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err = bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9207,7 +9223,7 @@ func TestIsChannelClean(t *testing.T) { // The state should finally be clean after alice sends her revocation. // ---rev---> - aliceRevocation, _, err = aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err = aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) @@ -9346,7 +9362,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { // Bob now sends a revocation for his prior commitment, and this should // change Alice's perspective to no longer include the first HTLC as // dust. - bobRevocation, _, err := bobChannel.RevokeCurrentCommitment() + bobRevocation, _, _, err := bobChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = aliceChannel.ReceiveRevocation(bobRevocation) require.NoError(t, err) @@ -9359,7 +9375,7 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) { require.NoError(t, err) err = aliceChannel.ReceiveNewCommitment(bobSig, bobHtlcSigs) require.NoError(t, err) - aliceRevocation, _, err := aliceChannel.RevokeCurrentCommitment() + aliceRevocation, _, _, err := aliceChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = bobChannel.ReceiveRevocation(aliceRevocation) require.NoError(t, err) diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go index e8fe5d62a..068e6ab7c 100644 --- a/lnwallet/test_utils.go +++ b/lnwallet/test_utils.go @@ -502,7 +502,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { return err } - bobRevocation, _, err := chanB.RevokeCurrentCommitment() + bobRevocation, _, _, err := chanB.RevokeCurrentCommitment() if err != nil { return err } @@ -518,7 +518,7 @@ func ForceStateTransition(chanA, chanB *LightningChannel) error { return err } - aliceRevocation, _, err := chanA.RevokeCurrentCommitment() + aliceRevocation, _, _, err := chanA.RevokeCurrentCommitment() if err != nil { return err } diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index b525d8a59..b51a3f903 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -291,7 +291,7 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { err = remoteChannel.ReceiveNewCommitment(localSig, localHtlcSigs) require.NoError(t, err) - revMsg, _, err := remoteChannel.RevokeCurrentCommitment() + revMsg, _, _, err := remoteChannel.RevokeCurrentCommitment() require.NoError(t, err) _, _, _, _, err = localChannel.ReceiveRevocation(revMsg) @@ -309,7 +309,7 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { err = localChannel.ReceiveNewCommitment(remoteSig, remoteHtlcSigs) require.NoError(t, err) - _, _, err = localChannel.RevokeCurrentCommitment() + _, _, _, err = localChannel.RevokeCurrentCommitment() require.NoError(t, err) // Now the local node force closes the channel so that we can inspect diff --git a/server.go b/server.go index 56d01c6e7..723c0f245 100644 --- a/server.go +++ b/server.go @@ -1165,6 +1165,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr, IsForwardedHTLC: s.htlcSwitch.IsForwardedHTLC, Clock: clock.NewDefaultClock(), SubscribeBreachComplete: s.breachArbiter.SubscribeBreachComplete, + PutFinalHtlcOutcome: s.chanStateDB.PutOnchainFinalHtlcOutcome, // nolint: lll }, dbs.ChanStateDB) // Select the configuration and furnding parameters for Bitcoin or