channeldb: add local and remote balance to revocation log

This commit re-adds the LocalBalance and RemoteBalance fields to the
RevocationLog. The channeldb/migration30 is also adjusted so that anyone
who has not yet run the optional migration will not lose these fields if
they run the migration after this commit.

The reason for re-adding these fields is that they are needed if we want
to reconstruct all the info of the lnwallet.BreachRetribution without
having access to the breach spend transaction. In most cases we would
have access to the spend tx since we would see it on-chain at which time
we would want to reconstruct the retribution info. However, for the
watchtower subsystem, we sometimes want to construct the retribution
info withouth having access to the spend transaction.

A user can use the `--no-rev-log-amt-data` flag to opt-out of storing
these amount fields.
This commit is contained in:
Elle Mouton 2023-02-02 09:41:56 +02:00
parent 0730337cc7
commit 2c786ec66f
No known key found for this signature in database
GPG Key ID: D7D916376026F177
6 changed files with 313 additions and 46 deletions

View File

@ -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 {

View File

@ -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")

View File

@ -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)

View File

@ -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
}
}

View File

@ -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)

View File

@ -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,
},
}