diff --git a/channeldb/migration30/migration.go b/channeldb/migration30/migration.go index 4cf24bfac..6c743b4b3 100644 --- a/channeldb/migration30/migration.go +++ b/channeldb/migration30/migration.go @@ -560,6 +560,11 @@ func convertRevocationLog(commit *mig.ChannelCommitment, HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), } + if !noAmtData { + rl.TheirBalance = &commit.RemoteBalance + rl.OurBalance = &commit.LocalBalance + } + for _, htlc := range commit.Htlcs { // Skip dust HTLCs. if htlc.OutputIndex < 0 { diff --git a/channeldb/migration30/migration_test.go b/channeldb/migration30/migration_test.go index a83a4f451..01773292c 100644 --- a/channeldb/migration30/migration_test.go +++ b/channeldb/migration30/migration_test.go @@ -512,6 +512,10 @@ func assertRevocationLog(t testing.TB, want, got RevocationLog) { "wrong TheirOutputIndex") require.Equal(t, want.CommitTxHash, got.CommitTxHash, "wrong CommitTxHash") + require.Equal(t, want.TheirBalance, got.TheirBalance, + "wrong TheirBalance") + require.Equal(t, want.OurBalance, got.OurBalance, + "wrong OurBalance") require.Equal(t, len(want.HTLCEntries), len(got.HTLCEntries), "wrong HTLCEntries length") diff --git a/channeldb/migration30/revocation_log.go b/channeldb/migration30/revocation_log.go index 99539736e..c4f3f1626 100644 --- a/channeldb/migration30/revocation_log.go +++ b/channeldb/migration30/revocation_log.go @@ -7,6 +7,7 @@ import ( "math" "github.com/btcsuite/btcd/btcutil" + lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21" mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" @@ -27,6 +28,8 @@ const ( revLogOurOutputIndexType tlv.Type = 0 revLogTheirOutputIndexType tlv.Type = 1 revLogCommitTxHashType tlv.Type = 2 + revLogOurBalanceType tlv.Type = 3 + revLogTheirBalanceType tlv.Type = 4 ) var ( @@ -197,9 +200,6 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { // fields can be viewed as a subset of a ChannelCommitment's. In the database, // all historical versions of the RevocationLog are saved using the // CommitHeight as the key. -// -// NOTE: all the fields use the primitive go types so they can be made into tlv -// records without further conversion. type RevocationLog struct { // OurOutputIndex specifies our output index in this commitment. In a // remote commitment transaction, this is the to remote output index. @@ -216,6 +216,26 @@ type RevocationLog struct { // HTLCEntries is the set of HTLCEntry's that are pending at this // particular commitment height. HTLCEntries []*HTLCEntry + + // OurBalance is the current available balance within the channel + // directly spendable by us. In other words, it is the value of the + // to_remote output on the remote parties' commitment transaction. + // + // NOTE: this is a pointer so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + OurBalance *lnwire.MilliSatoshi + + // TheirBalance is the current available balance within the channel + // directly spendable by the remote node. In other words, it is the + // value of the to_local output on the remote parties' commitment. + // + // NOTE: this is a pointer so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + TheirBalance *lnwire.MilliSatoshi } // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a @@ -240,6 +260,11 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *mig.ChannelCommitment, HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), } + if !noAmtData { + rl.OurBalance = &commit.LocalBalance + rl.TheirBalance = &commit.RemoteBalance + } + for _, htlc := range commit.Htlcs { // Skip dust HTLCs. if htlc.OutputIndex < 0 { @@ -304,6 +329,21 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { ), } + // Now we add any optional fields that are non-nil. + if rl.OurBalance != nil { + lb := uint64(*rl.OurBalance) + records = append(records, tlv.MakeBigSizeRecord( + revLogOurBalanceType, &lb, + )) + } + + if rl.TheirBalance != nil { + rb := uint64(*rl.TheirBalance) + records = append(records, tlv.MakeBigSizeRecord( + revLogTheirBalanceType, &rb, + )) + } + // Create the tlv stream. tlvStream, err := tlv.NewStream(records...) if err != nil { @@ -348,7 +388,11 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { // deserializeRevocationLog deserializes a RevocationLog based on tlv format. func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { - var rl RevocationLog + var ( + rl RevocationLog + localBalance uint64 + remoteBalance uint64 + ) // Create the tlv stream. tlvStream, err := tlv.NewStream( @@ -361,13 +405,31 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { tlv.MakePrimitiveRecord( revLogCommitTxHashType, &rl.CommitTxHash, ), + tlv.MakeBigSizeRecord(revLogOurBalanceType, &localBalance), + tlv.MakeBigSizeRecord( + revLogTheirBalanceType, &remoteBalance, + ), ) + if err != nil { + return rl, err + } // Read the tlv stream. - if _, err := readTlvStream(r, tlvStream); err != nil { + parsedTypes, err := readTlvStream(r, tlvStream) + if err != nil { return rl, err } + if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil { + lb := lnwire.MilliSatoshi(localBalance) + rl.OurBalance = &lb + } + + if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil { + rb := lnwire.MilliSatoshi(remoteBalance) + rl.TheirBalance = &rb + } + // Read the HTLC entries. rl.HTLCEntries, err = deserializeHTLCEntries(r) diff --git a/channeldb/migration30/test_utils.go b/channeldb/migration30/test_utils.go index 97de53fb3..c66b796b5 100644 --- a/channeldb/migration30/test_utils.go +++ b/channeldb/migration30/test_utils.go @@ -80,6 +80,12 @@ var ( // logHeight1 is the CommitHeight used by oldLog1. logHeight1 = uint64(0) + // localBalance1 is the LocalBalance used in oldLog1. + localBalance1 = lnwire.MilliSatoshi(990_950_000) + + // remoteBalance1 is the RemoteBalance used in oldLog1. + remoteBalance1 = lnwire.MilliSatoshi(0) + // oldLog1 defines an old revocation that has no HTLCs. oldLog1 = mig.ChannelCommitment{ CommitHeight: logHeight1, @@ -87,18 +93,29 @@ var ( LocalHtlcIndex: 0, RemoteLogIndex: 0, RemoteHtlcIndex: 0, - LocalBalance: lnwire.MilliSatoshi(990_950_000), - RemoteBalance: 0, + LocalBalance: localBalance1, + RemoteBalance: remoteBalance1, CommitTx: commitTx1, } - // newLog1 is the new version of oldLog1. + // newLog1 is the new version of oldLog1 in the case were we don't want + // to store any balance data. newLog1 = RevocationLog{ OurOutputIndex: 0, TheirOutputIndex: OutputIndexEmpty, CommitTxHash: commitTx1.TxHash(), } + // newLog1WithAmts is the new version of oldLog1 in the case were we do + // want to store balance data. + newLog1WithAmts = RevocationLog{ + OurOutputIndex: 0, + TheirOutputIndex: OutputIndexEmpty, + CommitTxHash: commitTx1.TxHash(), + OurBalance: &localBalance1, + TheirBalance: &remoteBalance1, + } + // preimage2 defines the second revocation preimage used in the test, // generated from itest. preimage2 = []byte{ @@ -148,6 +165,12 @@ var ( // logHeight2 is the CommitHeight used by oldLog2. logHeight2 = uint64(1) + // localBalance2 is the LocalBalance used in oldLog2. + localBalance2 = lnwire.MilliSatoshi(888_800_000) + + // remoteBalance2 is the RemoteBalance used in oldLog2. + remoteBalance2 = lnwire.MilliSatoshi(0) + // oldLog2 defines an old revocation that has one HTLC. oldLog2 = mig.ChannelCommitment{ CommitHeight: logHeight2, @@ -155,13 +178,14 @@ var ( LocalHtlcIndex: 1, RemoteLogIndex: 0, RemoteHtlcIndex: 0, - LocalBalance: lnwire.MilliSatoshi(888_800_000), - RemoteBalance: 0, + LocalBalance: localBalance2, + RemoteBalance: remoteBalance2, CommitTx: commitTx2, Htlcs: []mig.HTLC{htlc}, } - // newLog2 is the new version of the oldLog2. + // newLog2 is the new version of the oldLog2 in the case were we don't + // want to store any balance data. newLog2 = RevocationLog{ OurOutputIndex: 1, TheirOutputIndex: OutputIndexEmpty, @@ -177,6 +201,25 @@ var ( }, } + // newLog2WithAmts is the new version of oldLog2 in the case were we do + // want to store balance data. + newLog2WithAmts = RevocationLog{ + OurOutputIndex: 1, + TheirOutputIndex: OutputIndexEmpty, + CommitTxHash: commitTx2.TxHash(), + OurBalance: &localBalance2, + TheirBalance: &remoteBalance2, + HTLCEntries: []*HTLCEntry{ + { + RHash: rHash, + RefundTimeout: 489, + OutputIndex: 0, + Incoming: false, + Amt: btcutil.Amount(100_000), + }, + }, + } + // newLog3 defines an revocation log that's been created after v0.15.0. newLog3 = mig.ChannelCommitment{ CommitHeight: logHeight2 + 1, @@ -565,4 +608,9 @@ func init() { if rand.Intn(2) == 0 { withAmtData = true } + + if withAmtData { + newLog1 = newLog1WithAmts + newLog2 = newLog2WithAmts + } } diff --git a/channeldb/revocation_log.go b/channeldb/revocation_log.go index 2df93fd5b..f062ac086 100644 --- a/channeldb/revocation_log.go +++ b/channeldb/revocation_log.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" ) @@ -23,6 +24,8 @@ const ( revLogOurOutputIndexType tlv.Type = 0 revLogTheirOutputIndexType tlv.Type = 1 revLogCommitTxHashType tlv.Type = 2 + revLogOurBalanceType tlv.Type = 3 + revLogTheirBalanceType tlv.Type = 4 ) var ( @@ -185,9 +188,6 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) { // fields can be viewed as a subset of a ChannelCommitment's. In the database, // all historical versions of the RevocationLog are saved using the // CommitHeight as the key. -// -// NOTE: all the fields use the primitive go types so they can be made into tlv -// records without further conversion. type RevocationLog struct { // OurOutputIndex specifies our output index in this commitment. In a // remote commitment transaction, this is the to remote output index. @@ -204,6 +204,26 @@ type RevocationLog struct { // HTLCEntries is the set of HTLCEntry's that are pending at this // particular commitment height. HTLCEntries []*HTLCEntry + + // OurBalance is the current available balance within the channel + // directly spendable by us. In other words, it is the value of the + // to_remote output on the remote parties' commitment transaction. + // + // NOTE: this is a pointer so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + OurBalance *lnwire.MilliSatoshi + + // TheirBalance is the current available balance within the channel + // directly spendable by the remote node. In other words, it is the + // value of the to_local output on the remote parties' commitment. + // + // NOTE: this is a pointer so that it is clear if the value is zero or + // nil. Since migration 30 of the channeldb initially did not include + // this field, it could be the case that the field is not present for + // all revocation logs. + TheirBalance *lnwire.MilliSatoshi } // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a @@ -228,6 +248,11 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment, HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), } + if !noAmtData { + rl.OurBalance = &commit.LocalBalance + rl.TheirBalance = &commit.RemoteBalance + } + for _, htlc := range commit.Htlcs { // Skip dust HTLCs. if htlc.OutputIndex < 0 { @@ -292,6 +317,21 @@ func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { ), } + // Now we add any optional fields that are non-nil. + if rl.OurBalance != nil { + lb := uint64(*rl.OurBalance) + records = append(records, tlv.MakeBigSizeRecord( + revLogOurBalanceType, &lb, + )) + } + + if rl.TheirBalance != nil { + rb := uint64(*rl.TheirBalance) + records = append(records, tlv.MakeBigSizeRecord( + revLogTheirBalanceType, &rb, + )) + } + // Create the tlv stream. tlvStream, err := tlv.NewStream(records...) if err != nil { @@ -336,7 +376,11 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error { // deserializeRevocationLog deserializes a RevocationLog based on tlv format. func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { - var rl RevocationLog + var ( + rl RevocationLog + ourBalance uint64 + theirBalance uint64 + ) // Create the tlv stream. tlvStream, err := tlv.NewStream( @@ -349,13 +393,31 @@ func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { tlv.MakePrimitiveRecord( revLogCommitTxHashType, &rl.CommitTxHash, ), + tlv.MakeBigSizeRecord(revLogOurBalanceType, &ourBalance), + tlv.MakeBigSizeRecord( + revLogTheirBalanceType, &theirBalance, + ), ) + if err != nil { + return rl, err + } // Read the tlv stream. - if _, err := readTlvStream(r, tlvStream); err != nil { + parsedTypes, err := readTlvStream(r, tlvStream) + if err != nil { return rl, err } + if t, ok := parsedTypes[revLogOurBalanceType]; ok && t == nil { + lb := lnwire.MilliSatoshi(ourBalance) + rl.OurBalance = &lb + } + + if t, ok := parsedTypes[revLogTheirBalanceType]; ok && t == nil { + rb := lnwire.MilliSatoshi(theirBalance) + rl.TheirBalance = &rb + } + // Read the HTLC entries. rl.HTLCEntries, err = deserializeHTLCEntries(r) diff --git a/channeldb/revocation_log_test.go b/channeldb/revocation_log_test.go index 1f6d1f813..fc5303a48 100644 --- a/channeldb/revocation_log_test.go +++ b/channeldb/revocation_log_test.go @@ -56,10 +56,13 @@ var ( 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, } + localBalance = lnwire.MilliSatoshi(9000) + remoteBalance = lnwire.MilliSatoshi(3000) + testChannelCommit = ChannelCommitment{ CommitHeight: 999, - LocalBalance: lnwire.MilliSatoshi(9000), - RemoteBalance: lnwire.MilliSatoshi(3000), + LocalBalance: localBalance, + RemoteBalance: remoteBalance, CommitFee: btcutil.Amount(rand.Int63()), FeePerKw: btcutil.Amount(5000), CommitTx: channels.TestFundingTx, @@ -74,13 +77,13 @@ var ( }}, } - testRevocationLog = RevocationLog{ + testRevocationLogNoAmts = RevocationLog{ OurOutputIndex: 0, TheirOutputIndex: 1, CommitTxHash: testChannelCommit.CommitTx.TxHash(), HTLCEntries: []*HTLCEntry{&testHTLCEntry}, } - testRevocationLogBytes = []byte{ + testRevocationLogNoAmtsBytes = []byte{ // Body length 42. 0x2a, // OurOutputIndex tlv. @@ -94,6 +97,33 @@ var ( 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, 0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, } + + testRevocationLogWithAmts = RevocationLog{ + OurOutputIndex: 0, + TheirOutputIndex: 1, + CommitTxHash: testChannelCommit.CommitTx.TxHash(), + HTLCEntries: []*HTLCEntry{&testHTLCEntry}, + OurBalance: &localBalance, + TheirBalance: &remoteBalance, + } + testRevocationLogWithAmtsBytes = []byte{ + // Body length 52. + 0x34, + // OurOutputIndex tlv. + 0x0, 0x2, 0x0, 0x0, + // TheirOutputIndex tlv. + 0x1, 0x2, 0x0, 0x1, + // CommitTxHash tlv. + 0x2, 0x20, + 0x28, 0x76, 0x2, 0x59, 0x1d, 0x9d, 0x64, 0x86, + 0x6e, 0x60, 0x29, 0x23, 0x1d, 0x5e, 0xc5, 0xe6, + 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, + 0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, + // OurBalance. + 0x3, 0x3, 0xfd, 0x23, 0x28, + // Remote Balance. + 0x4, 0x3, 0xfd, 0x0b, 0xb8, + } ) func TestWriteTLVStream(t *testing.T) { @@ -208,22 +238,75 @@ func TestSerializeHTLCEntries(t *testing.T) { require.Equal(t, expectedBytes, buf.Bytes()) } -func TestSerializeRevocationLog(t *testing.T) { +// TestSerializeAndDeserializeRevLog tests the serialization and deserialization +// of various forms of the revocation log. +func TestSerializeAndDeserializeRevLog(t *testing.T) { t.Parallel() - // Copy the testRevocationLog and testHTLCEntry. - rl := testRevocationLog + tests := []struct { + name string + revLog RevocationLog + revLogBytes []byte + }{ + { + name: "with no amount fields", + revLog: testRevocationLogNoAmts, + revLogBytes: testRevocationLogNoAmtsBytes, + }, + { + name: "with amount fields", + revLog: testRevocationLogWithAmts, + revLogBytes: testRevocationLogWithAmtsBytes, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + testSerializeRevocationLog( + t, &test.revLog, test.revLogBytes, + ) + + testDerializeRevocationLog( + t, &test.revLog, test.revLogBytes, + ) + }) + } +} + +func testSerializeRevocationLog(t *testing.T, rl *RevocationLog, + revLogBytes []byte) { + + // Copy the testRevocationLogWithAmts and testHTLCEntry. htlc := testHTLCEntry rl.HTLCEntries = []*HTLCEntry{&htlc} // Write the tlv stream. buf := bytes.NewBuffer([]byte{}) - err := serializeRevocationLog(buf, &rl) + err := serializeRevocationLog(buf, rl) require.NoError(t, err) // Check the expected bytes on the body of the revocation log. bodyIndex := buf.Len() - len(testHTLCEntryBytes) - require.Equal(t, testRevocationLogBytes, buf.Bytes()[:bodyIndex]) + require.Equal(t, revLogBytes, buf.Bytes()[:bodyIndex]) +} + +func testDerializeRevocationLog(t *testing.T, revLog *RevocationLog, + revLogBytes []byte) { + + // Construct the full bytes. + revLogBytes = append(revLogBytes, testHTLCEntryBytes...) + + // Read the tlv stream. + buf := bytes.NewBuffer(revLogBytes) + rl, err := deserializeRevocationLog(buf) + require.NoError(t, err) + + // Check the bytes are read as expected. + require.Len(t, rl.HTLCEntries, 1) + require.Equal(t, *revLog, rl) } func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) { @@ -271,23 +354,6 @@ func TestDerializeHTLCEntries(t *testing.T) { require.Equal(t, &entry, htlcs[0]) } -func TestDerializeRevocationLog(t *testing.T) { - t.Parallel() - - // Construct the full bytes. - b := testRevocationLogBytes - b = append(b, testHTLCEntryBytes...) - - // Read the tlv stream. - buf := bytes.NewBuffer(b) - rl, err := deserializeRevocationLog(buf) - require.NoError(t, err) - - // Check the bytes are read as expected. - require.Len(t, rl.HTLCEntries, 1) - require.Equal(t, testRevocationLog, rl) -} - func TestFetchLogBucket(t *testing.T) { t.Parallel() @@ -374,12 +440,22 @@ func TestPutRevocationLog(t *testing.T) { }{ { // Test a normal put operation. - name: "successful put", + name: "successful put with amount data", commit: testChannelCommit, ourIndex: 0, theirIndex: 1, expectedErr: nil, - expectedLog: testRevocationLog, + expectedLog: testRevocationLogWithAmts, + }, + { + // Test a normal put operation. + name: "successful put with no amount data", + commit: testChannelCommit, + ourIndex: 0, + theirIndex: 1, + noAmtData: true, + expectedErr: nil, + expectedLog: testRevocationLogNoAmts, }, { // Test our index too big. @@ -410,12 +486,22 @@ func TestPutRevocationLog(t *testing.T) { }, { // Test dust htlc is not saved. - name: "dust htlc not saved", + name: "dust htlc not saved with amout data", commit: testCommitDust, ourIndex: 0, theirIndex: 1, expectedErr: nil, - expectedLog: testRevocationLog, + expectedLog: testRevocationLogWithAmts, + }, + { + // Test dust htlc is not saved. + name: "dust htlc not saved with no amount data", + commit: testCommitDust, + ourIndex: 0, + theirIndex: 1, + noAmtData: true, + expectedErr: nil, + expectedLog: testRevocationLogNoAmts, }, }