Skip to content

Commit 42c4597

Browse files
authored
Merge pull request #1937 from halseth/data-loss-protect-resending
Data loss protect resending
2 parents 8924d8f + b1a35fc commit 42c4597

13 files changed

+1180
-581
lines changed

channeldb/channel.go

+87-22
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package channeldb
33
import (
44
"bytes"
55
"encoding/binary"
6+
"errors"
67
"fmt"
78
"io"
89
"net"
@@ -1832,6 +1833,10 @@ type ChannelCloseSummary struct {
18321833

18331834
// LocalChanCfg is the channel configuration for the local node.
18341835
LocalChanConfig ChannelConfig
1836+
1837+
// LastChanSyncMsg is the ChannelReestablish message for this channel
1838+
// for the state at the point where it was closed.
1839+
LastChanSyncMsg *lnwire.ChannelReestablish
18351840
}
18361841

18371842
// CloseChannel closes a previously active Lightning channel. Closing a channel
@@ -2059,7 +2064,12 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
20592064
// If this is a close channel summary created before the addition of
20602065
// the new fields, then we can exit here.
20612066
if cs.RemoteCurrentRevocation == nil {
2062-
return nil
2067+
return WriteElements(w, false)
2068+
}
2069+
2070+
// If fields are present, write boolean to indicate this, and continue.
2071+
if err := WriteElements(w, true); err != nil {
2072+
return err
20632073
}
20642074

20652075
if err := WriteElements(w, cs.RemoteCurrentRevocation); err != nil {
@@ -2070,14 +2080,34 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
20702080
return err
20712081
}
20722082

2073-
// We'll write this field last, as it's possible for a channel to be
2074-
// closed before we learn of the next unrevoked revocation point for
2075-
// the remote party.
2076-
if cs.RemoteNextRevocation == nil {
2077-
return nil
2083+
// The RemoteNextRevocation field is optional, as it's possible for a
2084+
// channel to be closed before we learn of the next unrevoked
2085+
// revocation point for the remote party. Write a boolen indicating
2086+
// whether this field is present or not.
2087+
if err := WriteElements(w, cs.RemoteNextRevocation != nil); err != nil {
2088+
return err
2089+
}
2090+
2091+
// Write the field, if present.
2092+
if cs.RemoteNextRevocation != nil {
2093+
if err = WriteElements(w, cs.RemoteNextRevocation); err != nil {
2094+
return err
2095+
}
2096+
}
2097+
2098+
// Write whether the channel sync message is present.
2099+
if err := WriteElements(w, cs.LastChanSyncMsg != nil); err != nil {
2100+
return err
2101+
}
2102+
2103+
// Write the channel sync message, if present.
2104+
if cs.LastChanSyncMsg != nil {
2105+
if err := WriteElements(w, cs.LastChanSyncMsg); err != nil {
2106+
return err
2107+
}
20782108
}
20792109

2080-
return WriteElements(w, cs.RemoteNextRevocation)
2110+
return nil
20812111
}
20822112

20832113
func fetchChannelCloseSummary(tx *bolt.Tx,
@@ -2111,15 +2141,19 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
21112141

21122142
// We'll now check to see if the channel close summary was encoded with
21132143
// any of the additional optional fields.
2114-
err = ReadElements(r, &c.RemoteCurrentRevocation)
2115-
switch {
2116-
case err == io.EOF:
2144+
var hasNewFields bool
2145+
err = ReadElements(r, &hasNewFields)
2146+
if err != nil {
2147+
return nil, err
2148+
}
2149+
2150+
// If fields are not present, we can return.
2151+
if !hasNewFields {
21172152
return c, nil
2153+
}
21182154

2119-
// If we got a non-eof error, then we know there's an actually issue.
2120-
// Otherwise, it may have been the case that this summary didn't have
2121-
// the set of optional fields.
2122-
case err != nil:
2155+
// Otherwise read the new fields.
2156+
if err := ReadElements(r, &c.RemoteCurrentRevocation); err != nil {
21232157
return nil, err
21242158
}
21252159

@@ -2129,17 +2163,48 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
21292163

21302164
// Finally, we'll attempt to read the next unrevoked commitment point
21312165
// for the remote party. If we closed the channel before receiving a
2132-
// funding locked message, then this can be nil. As a result, we'll use
2133-
// the same technique to read the field, only if there's still data
2134-
// left in the buffer.
2135-
err = ReadElements(r, &c.RemoteNextRevocation)
2136-
if err != nil && err != io.EOF {
2137-
// If we got a non-eof error, then we know there's an actually
2138-
// issue. Otherwise, it may have been the case that this
2139-
// summary didn't have the set of optional fields.
2166+
// funding locked message then this might not be present. A boolean
2167+
// indicating whether the field is present will come first.
2168+
var hasRemoteNextRevocation bool
2169+
err = ReadElements(r, &hasRemoteNextRevocation)
2170+
if err != nil {
2171+
return nil, err
2172+
}
2173+
2174+
// If this field was written, read it.
2175+
if hasRemoteNextRevocation {
2176+
err = ReadElements(r, &c.RemoteNextRevocation)
2177+
if err != nil {
2178+
return nil, err
2179+
}
2180+
}
2181+
2182+
// Check if we have a channel sync message to read.
2183+
var hasChanSyncMsg bool
2184+
err = ReadElements(r, &hasChanSyncMsg)
2185+
if err == io.EOF {
2186+
return c, nil
2187+
} else if err != nil {
21402188
return nil, err
21412189
}
21422190

2191+
// If a chan sync message is present, read it.
2192+
if hasChanSyncMsg {
2193+
// We must pass in reference to a lnwire.Message for the codec
2194+
// to support it.
2195+
var msg lnwire.Message
2196+
if err := ReadElements(r, &msg); err != nil {
2197+
return nil, err
2198+
}
2199+
2200+
chanSync, ok := msg.(*lnwire.ChannelReestablish)
2201+
if !ok {
2202+
return nil, errors.New("unable cast db Message to " +
2203+
"ChannelReestablish")
2204+
}
2205+
c.LastChanSyncMsg = chanSync
2206+
}
2207+
21432208
return c, nil
21442209
}
21452210

channeldb/channel_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -248,10 +248,10 @@ func TestOpenChannelPutGetDelete(t *testing.T) {
248248
t.Parallel()
249249

250250
cdb, cleanUp, err := makeTestDB()
251-
defer cleanUp()
252251
if err != nil {
253252
t.Fatalf("unable to make test database: %v", err)
254253
}
254+
defer cleanUp()
255255

256256
// Create the test channel state, then add an additional fake HTLC
257257
// before syncing to disk.
@@ -368,10 +368,10 @@ func TestChannelStateTransition(t *testing.T) {
368368
t.Parallel()
369369

370370
cdb, cleanUp, err := makeTestDB()
371-
defer cleanUp()
372371
if err != nil {
373372
t.Fatalf("unable to make test database: %v", err)
374373
}
374+
defer cleanUp()
375375

376376
// First create a minimal channel, then perform a full sync in order to
377377
// persist the data.

channeldb/db.go

+56
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/btcsuite/btcd/wire"
1313
"github.com/coreos/bbolt"
1414
"github.com/go-errors/errors"
15+
"github.com/lightningnetwork/lnd/lnwire"
1516
)
1617

1718
const (
@@ -80,6 +81,13 @@ var (
8081
number: 6,
8182
migration: migratePruneEdgeUpdateIndex,
8283
},
84+
{
85+
// The DB version that migrates the ChannelCloseSummary
86+
// to a format where optional fields are indicated with
87+
// boolean flags.
88+
number: 7,
89+
migration: migrateOptionalChannelCloseSummaryFields,
90+
},
8391
}
8492

8593
// Big endian is the preferred byte order, due to cursor scans over
@@ -609,6 +617,54 @@ func (d *DB) FetchClosedChannel(chanID *wire.OutPoint) (*ChannelCloseSummary, er
609617
return chanSummary, nil
610618
}
611619

620+
// FetchClosedChannelForID queries for a channel close summary using the
621+
// channel ID of the channel in question.
622+
func (d *DB) FetchClosedChannelForID(cid lnwire.ChannelID) (
623+
*ChannelCloseSummary, error) {
624+
625+
var chanSummary *ChannelCloseSummary
626+
if err := d.View(func(tx *bolt.Tx) error {
627+
closeBucket := tx.Bucket(closedChannelBucket)
628+
if closeBucket == nil {
629+
return ErrClosedChannelNotFound
630+
}
631+
632+
// The first 30 bytes of the channel ID and outpoint will be
633+
// equal.
634+
cursor := closeBucket.Cursor()
635+
op, c := cursor.Seek(cid[:30])
636+
637+
// We scan over all possible candidates for this channel ID.
638+
for ; op != nil && bytes.Compare(cid[:30], op[:30]) <= 0; op, c = cursor.Next() {
639+
var outPoint wire.OutPoint
640+
err := readOutpoint(bytes.NewReader(op), &outPoint)
641+
if err != nil {
642+
return err
643+
}
644+
645+
// If the found outpoint does not correspond to this
646+
// channel ID, we continue.
647+
if !cid.IsChanPoint(&outPoint) {
648+
continue
649+
}
650+
651+
// Deserialize the close summary and return.
652+
r := bytes.NewReader(c)
653+
chanSummary, err = deserializeCloseChannelSummary(r)
654+
if err != nil {
655+
return err
656+
}
657+
658+
return nil
659+
}
660+
return ErrClosedChannelNotFound
661+
}); err != nil {
662+
return nil, err
663+
}
664+
665+
return chanSummary, nil
666+
}
667+
612668
// MarkChanFullyClosed marks a channel as fully closed within the database. A
613669
// channel should be marked as fully closed if the channel was initially
614670
// cooperatively closed and it's reached a single confirmation, or after all

channeldb/db_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"os"
66
"path/filepath"
77
"testing"
8+
9+
"github.com/btcsuite/btcutil"
10+
"github.com/lightningnetwork/lnd/lnwire"
811
)
912

1013
func TestOpenWithCreate(t *testing.T) {
@@ -71,3 +74,76 @@ func TestWipe(t *testing.T) {
7174
ErrNoClosedChannels, err)
7275
}
7376
}
77+
78+
// TestFetchClosedChannelForID tests that we are able to properly retrieve a
79+
// ChannelCloseSummary from the DB given a ChannelID.
80+
func TestFetchClosedChannelForID(t *testing.T) {
81+
t.Parallel()
82+
83+
const numChans = 101
84+
85+
cdb, cleanUp, err := makeTestDB()
86+
if err != nil {
87+
t.Fatalf("unable to make test database: %v", err)
88+
}
89+
defer cleanUp()
90+
91+
// Create the test channel state, that we will mutate the index of the
92+
// funding point.
93+
state, err := createTestChannelState(cdb)
94+
if err != nil {
95+
t.Fatalf("unable to create channel state: %v", err)
96+
}
97+
98+
// Now run through the number of channels, and modify the outpoint index
99+
// to create new channel IDs.
100+
for i := uint32(0); i < numChans; i++ {
101+
// Save the open channel to disk.
102+
state.FundingOutpoint.Index = i
103+
if err := state.FullSync(); err != nil {
104+
t.Fatalf("unable to save and serialize channel "+
105+
"state: %v", err)
106+
}
107+
108+
// Close the channel. To make sure we retrieve the correct
109+
// summary later, we make them differ in the SettledBalance.
110+
closeSummary := &ChannelCloseSummary{
111+
ChanPoint: state.FundingOutpoint,
112+
RemotePub: state.IdentityPub,
113+
SettledBalance: btcutil.Amount(500 + i),
114+
}
115+
if err := state.CloseChannel(closeSummary); err != nil {
116+
t.Fatalf("unable to close channel: %v", err)
117+
}
118+
}
119+
120+
// Now run though them all again and make sure we are able to retrieve
121+
// summaries from the DB.
122+
for i := uint32(0); i < numChans; i++ {
123+
state.FundingOutpoint.Index = i
124+
125+
// We calculate the ChannelID and use it to fetch the summary.
126+
cid := lnwire.NewChanIDFromOutPoint(&state.FundingOutpoint)
127+
fetchedSummary, err := cdb.FetchClosedChannelForID(cid)
128+
if err != nil {
129+
t.Fatalf("unable to fetch close summary: %v", err)
130+
}
131+
132+
// Make sure we retrieved the correct one by checking the
133+
// SettledBalance.
134+
if fetchedSummary.SettledBalance != btcutil.Amount(500+i) {
135+
t.Fatalf("summaries don't match: expected %v got %v",
136+
btcutil.Amount(500+i),
137+
fetchedSummary.SettledBalance)
138+
}
139+
}
140+
141+
// As a final test we make sure that we get ErrClosedChannelNotFound
142+
// for a ChannelID we didn't add to the DB.
143+
state.FundingOutpoint.Index++
144+
cid := lnwire.NewChanIDFromOutPoint(&state.FundingOutpoint)
145+
_, err = cdb.FetchClosedChannelForID(cid)
146+
if err != ErrClosedChannelNotFound {
147+
t.Fatalf("expected ErrClosedChannelNotFound, instead got: %v", err)
148+
}
149+
}

channeldb/legacy_serialization.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package channeldb
2+
3+
import "io"
4+
5+
// deserializeCloseChannelSummaryV6 reads the v6 database format for
6+
// ChannelCloseSummary.
7+
//
8+
// NOTE: deprecated, only for migration.
9+
func deserializeCloseChannelSummaryV6(r io.Reader) (*ChannelCloseSummary, error) {
10+
c := &ChannelCloseSummary{}
11+
12+
err := ReadElements(r,
13+
&c.ChanPoint, &c.ShortChanID, &c.ChainHash, &c.ClosingTXID,
14+
&c.CloseHeight, &c.RemotePub, &c.Capacity, &c.SettledBalance,
15+
&c.TimeLockedBalance, &c.CloseType, &c.IsPending,
16+
)
17+
if err != nil {
18+
return nil, err
19+
}
20+
21+
// We'll now check to see if the channel close summary was encoded with
22+
// any of the additional optional fields.
23+
err = ReadElements(r, &c.RemoteCurrentRevocation)
24+
switch {
25+
case err == io.EOF:
26+
return c, nil
27+
28+
// If we got a non-eof error, then we know there's an actually issue.
29+
// Otherwise, it may have been the case that this summary didn't have
30+
// the set of optional fields.
31+
case err != nil:
32+
return nil, err
33+
}
34+
35+
if err := readChanConfig(r, &c.LocalChanConfig); err != nil {
36+
return nil, err
37+
}
38+
39+
// Finally, we'll attempt to read the next unrevoked commitment point
40+
// for the remote party. If we closed the channel before receiving a
41+
// funding locked message, then this can be nil. As a result, we'll use
42+
// the same technique to read the field, only if there's still data
43+
// left in the buffer.
44+
err = ReadElements(r, &c.RemoteNextRevocation)
45+
if err != nil && err != io.EOF {
46+
// If we got a non-eof error, then we know there's an actually
47+
// issue. Otherwise, it may have been the case that this
48+
// summary didn't have the set of optional fields.
49+
return nil, err
50+
}
51+
52+
return c, nil
53+
}

0 commit comments

Comments
 (0)