Merge pull request #7379 from ellemouton/addFieldsToRevocationLog

multi: add local and remote amount fields to revocation log
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2023-02-20 17:57:08 -08:00
commit 748461cba4
19 files changed
+681 -166

No files matched your search

+2 -2
View File
@@ -2657,8 +2657,8 @@ func (c *OpenChannel) AdvanceCommitChainTail(fwdPkg *FwdPkg,
// With the commitment pointer swapped, we can now add the // With the commitment pointer swapped, we can now add the
// revoked (prior) state to the revocation log. // revoked (prior) state to the revocation log.
err = putRevocationLog( err = putRevocationLog(
logBucket, &c.RemoteCommitment, logBucket, &c.RemoteCommitment, ourOutputIndex,
ourOutputIndex, theirOutputIndex, theirOutputIndex, c.Db.parent.noRevLogAmtData,
) )
if err != nil { if err != nil {
return err return err
+31 -4
View File
@@ -63,12 +63,24 @@ type mandatoryVersion struct {
migration migration migration migration
} }
// MigrationConfig is an interface combines the config interfaces of all
// optional migrations.
type MigrationConfig interface {
migration30.MigrateRevLogConfig
}
// MigrationConfigImpl is a super set of all the various migration configs and
// an implementation of MigrationConfig.
type MigrationConfigImpl struct {
migration30.MigrateRevLogConfigImpl
}
// optionalMigration defines an optional migration function. When a migration // optionalMigration defines an optional migration function. When a migration
// is optional, it usually involves a large scale of changes that might touch // is optional, it usually involves a large scale of changes that might touch
// millions of keys. Due to OOM concern, the update cannot be safely done // millions of keys. Due to OOM concern, the update cannot be safely done
// within one db transaction. Thus, for optional migrations, they must take the // within one db transaction. Thus, for optional migrations, they must take the
// db backend and construct transactions as needed. // db backend and construct transactions as needed.
type optionalMigration func(db kvdb.Backend) error type optionalMigration func(db kvdb.Backend, cfg MigrationConfig) error
// optionalVersion defines a db version that can be optionally applied. When // optionalVersion defines a db version that can be optionally applied. When
// applying migrations, we must apply all the mandatory migrations first before // applying migrations, we must apply all the mandatory migrations first before
@@ -273,8 +285,12 @@ var (
// to determine its state. // to determine its state.
optionalVersions = []optionalVersion{ optionalVersions = []optionalVersion{
{ {
name: "prune revocation log", name: "prune revocation log",
migration: migration30.MigrateRevocationLog, migration: func(db kvdb.Backend,
cfg MigrationConfig) error {
return migration30.MigrateRevocationLog(db, cfg)
},
}, },
} }
@@ -308,6 +324,10 @@ type DB struct {
dryRun bool dryRun bool
keepFailedPaymentAttempts bool keepFailedPaymentAttempts bool
storeFinalHtlcResolutions bool storeFinalHtlcResolutions bool
// noRevLogAmtData if true, means that commitment transaction amount
// data should not be stored in the revocation log.
noRevLogAmtData bool
} }
// Open opens or creates channeldb. Any necessary schemas migrations due // Open opens or creates channeldb. Any necessary schemas migrations due
@@ -366,6 +386,7 @@ func CreateWithBackend(backend kvdb.Backend,
dryRun: opts.dryRun, dryRun: opts.dryRun,
keepFailedPaymentAttempts: opts.keepFailedPaymentAttempts, keepFailedPaymentAttempts: opts.keepFailedPaymentAttempts,
storeFinalHtlcResolutions: opts.storeFinalHtlcResolutions, storeFinalHtlcResolutions: opts.storeFinalHtlcResolutions,
noRevLogAmtData: opts.NoRevLogAmtData,
} }
// Set the parent pointer (only used in tests). // Set the parent pointer (only used in tests).
@@ -1545,8 +1566,14 @@ func (d *DB) applyOptionalVersions(cfg OptionalMiragtionConfig) error {
version := optionalVersions[0] version := optionalVersions[0]
log.Infof("Performing database optional migration: %s", version.name) log.Infof("Performing database optional migration: %s", version.name)
migrationCfg := &MigrationConfigImpl{
migration30.MigrateRevLogConfigImpl{
NoAmountData: d.noRevLogAmtData,
},
}
// Migrate the data. // Migrate the data.
if err := version.migration(d); err != nil { if err := version.migration(d, migrationCfg); err != nil {
log.Errorf("Unable to apply optional migration: %s, error: %v", log.Errorf("Unable to apply optional migration: %s, error: %v",
version.name, err) version.name, err)
return err return err
+3 -1
View File
@@ -530,7 +530,9 @@ func TestApplyOptionalVersions(t *testing.T) {
// Overwrite the migration function so we can count how many times the // Overwrite the migration function so we can count how many times the
// migration has happened. // migration has happened.
migrateCount := 0 migrateCount := 0
optionalVersions[0].migration = func(_ kvdb.Backend) error { optionalVersions[0].migration = func(_ kvdb.Backend,
_ MigrationConfig) error {
migrateCount++ migrateCount++
return nil return nil
} }
+36 -7
View File
@@ -23,10 +23,31 @@ import (
// indexes. // indexes.
const recordsPerTx = 20_000 const recordsPerTx = 20_000
// MigrateRevLogConfig is an interface that defines the config that should be
// passed to the MigrateRevocationLog function.
type MigrateRevLogConfig interface {
// GetNoAmountData returns true if the amount data of revoked commitment
// transactions should not be stored in the revocation log.
GetNoAmountData() bool
}
// MigrateRevLogConfigImpl implements the MigrationRevLogConfig interface.
type MigrateRevLogConfigImpl struct {
// NoAmountData if set to true will result in the amount data of revoked
// commitment transactions not being stored in the revocation log.
NoAmountData bool
}
// GetNoAmountData returns true if the amount data of revoked commitment
// transactions should not be stored in the revocation log.
func (c *MigrateRevLogConfigImpl) GetNoAmountData() bool {
return c.NoAmountData
}
// MigrateRevocationLog migrates the old revocation logs into the newer format // MigrateRevocationLog migrates the old revocation logs into the newer format
// and deletes them once finished, with the deletion only happens once ALL the // and deletes them once finished, with the deletion only happens once ALL the
// old logs have been migrates. // old logs have been migrates.
func MigrateRevocationLog(db kvdb.Backend) error { func MigrateRevocationLog(db kvdb.Backend, cfg MigrateRevLogConfig) error {
log.Infof("Migrating revocation logs, might take a while...") log.Infof("Migrating revocation logs, might take a while...")
var ( var (
@@ -64,7 +85,7 @@ func MigrateRevocationLog(db kvdb.Backend) error {
// Process the migration. // Process the migration.
err = kvdb.Update(db, func(tx kvdb.RwTx) error { err = kvdb.Update(db, func(tx kvdb.RwTx) error {
finished, err = processMigration(tx) finished, err = processMigration(tx, cfg)
if err != nil { if err != nil {
return err return err
} }
@@ -114,7 +135,7 @@ func MigrateRevocationLog(db kvdb.Backend) error {
// processMigration finds the next un-migrated revocation logs, reads a max // processMigration finds the next un-migrated revocation logs, reads a max
// number of `recordsPerTx` records, converts them into the new revocation logs // number of `recordsPerTx` records, converts them into the new revocation logs
// and save them to disk. // and save them to disk.
func processMigration(tx kvdb.RwTx) (bool, error) { func processMigration(tx kvdb.RwTx, cfg MigrateRevLogConfig) (bool, error) {
openChanBucket := tx.ReadWriteBucket(openChannelBucket) openChanBucket := tx.ReadWriteBucket(openChannelBucket)
// If no bucket is found, we can exit early. // If no bucket is found, we can exit early.
@@ -134,7 +155,7 @@ func processMigration(tx kvdb.RwTx) (bool, error) {
} }
// Read a list of old revocation logs. // Read a list of old revocation logs.
entryMap, err := readOldRevocationLogs(openChanBucket, locator) entryMap, err := readOldRevocationLogs(openChanBucket, locator, cfg)
if err != nil { if err != nil {
return false, fmt.Errorf("read old logs err: %v", err) return false, fmt.Errorf("read old logs err: %v", err)
} }
@@ -368,7 +389,7 @@ type result struct {
// readOldRevocationLogs finds a list of old revocation logs and converts them // readOldRevocationLogs finds a list of old revocation logs and converts them
// into the new revocation logs. // into the new revocation logs.
func readOldRevocationLogs(openChanBucket kvdb.RwBucket, func readOldRevocationLogs(openChanBucket kvdb.RwBucket,
locator *updateLocator) (logEntries, error) { locator *updateLocator, cfg MigrateRevLogConfig) (logEntries, error) {
entries := make(logEntries) entries := make(logEntries)
results := make([]*result, 0) results := make([]*result, 0)
@@ -415,7 +436,9 @@ func readOldRevocationLogs(openChanBucket kvdb.RwBucket,
// Convert the old logs into the new logs. We do this early in // Convert the old logs into the new logs. We do this early in
// the read tx so the old large revocation log can be set to // the read tx so the old large revocation log can be set to
// nil here so save us some memory space. // nil here so save us some memory space.
newLog, err := convertRevocationLog(&c, ourIndex, theirIndex) newLog, err := convertRevocationLog(
&c, ourIndex, theirIndex, cfg.GetNoAmountData(),
)
if err != nil { if err != nil {
r.errChan <- err r.errChan <- err
} }
@@ -519,7 +542,8 @@ func readOldRevocationLogs(openChanBucket kvdb.RwBucket,
// convertRevocationLog uses the fields `CommitTx` and `Htlcs` from a // convertRevocationLog uses the fields `CommitTx` and `Htlcs` from a
// ChannelCommitment to construct a revocation log entry. // ChannelCommitment to construct a revocation log entry.
func convertRevocationLog(commit *mig.ChannelCommitment, func convertRevocationLog(commit *mig.ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32) (*RevocationLog, error) { ourOutputIndex, theirOutputIndex uint32,
noAmtData bool) (*RevocationLog, error) {
// Sanity check that the output indexes can be safely converted. // Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 { if ourOutputIndex > math.MaxUint16 {
@@ -536,6 +560,11 @@ func convertRevocationLog(commit *mig.ChannelCommitment,
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
} }
if !noAmtData {
rl.TheirBalance = &commit.RemoteBalance
rl.OurBalance = &commit.LocalBalance
}
for _, htlc := range commit.Htlcs { for _, htlc := range commit.Htlcs {
// Skip dust HTLCs. // Skip dust HTLCs.
if htlc.OutputIndex < 0 { if htlc.OutputIndex < 0 {
+19 -4
View File
@@ -75,6 +75,7 @@ func TestMigrateRevocationLog(t *testing.T) {
} }
fmt.Printf("Running %d test cases...\n", len(testCases)) fmt.Printf("Running %d test cases...\n", len(testCases))
fmt.Printf("withAmtData is set to: %v\n", withAmtData)
for i, tc := range testCases { for i, tc := range testCases {
tc := tc tc := tc
@@ -103,11 +104,17 @@ func TestMigrateRevocationLog(t *testing.T) {
return nil return nil
} }
migtest.ApplyMigrationWithDb( cfg := &MigrateRevLogConfigImpl{
NoAmountData: !withAmtData,
}
migtest.ApplyMigrationWithDB(
t, t,
beforeMigration, beforeMigration,
afterMigration, afterMigration,
MigrateRevocationLog, func(db kvdb.Backend) error {
return MigrateRevocationLog(db, cfg)
},
false, false,
) )
}) })
@@ -505,6 +512,10 @@ func assertRevocationLog(t testing.TB, want, got RevocationLog) {
"wrong TheirOutputIndex") "wrong TheirOutputIndex")
require.Equal(t, want.CommitTxHash, got.CommitTxHash, require.Equal(t, want.CommitTxHash, got.CommitTxHash,
"wrong 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), require.Equal(t, len(want.HTLCEntries), len(got.HTLCEntries),
"wrong HTLCEntries length") "wrong HTLCEntries length")
@@ -559,8 +570,12 @@ func BenchmarkMigration(b *testing.B) {
return setupTestLogs(db, c, oldLogs, nil) return setupTestLogs(db, c, oldLogs, nil)
} }
cfg := &MigrateRevLogConfigImpl{
NoAmountData: !withAmtData,
}
// Run the migration test. // Run the migration test.
migtest.ApplyMigrationWithDb( migtest.ApplyMigrationWithDB(
b, b,
beforeMigration, beforeMigration,
nil, nil,
@@ -568,7 +583,7 @@ func BenchmarkMigration(b *testing.B) {
b.StartTimer() b.StartTimer()
defer b.StopTimer() defer b.StopTimer()
return MigrateRevocationLog(db) return MigrateRevocationLog(db, cfg)
}, },
false, false,
) )
+106 -36
View File
@@ -7,6 +7,7 @@ import (
"math" "math"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
mig24 "github.com/lightningnetwork/lnd/channeldb/migration24" mig24 "github.com/lightningnetwork/lnd/channeldb/migration24"
mig25 "github.com/lightningnetwork/lnd/channeldb/migration25" mig25 "github.com/lightningnetwork/lnd/channeldb/migration25"
mig26 "github.com/lightningnetwork/lnd/channeldb/migration26" mig26 "github.com/lightningnetwork/lnd/channeldb/migration26"
@@ -16,8 +17,20 @@ import (
"github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tlv"
) )
// OutputIndexEmpty is used when the output index doesn't exist. const (
const OutputIndexEmpty = math.MaxUint16 // OutputIndexEmpty is used when the output index doesn't exist.
OutputIndexEmpty = math.MaxUint16
// A set of tlv type definitions used to serialize the body of
// revocation logs to the database.
//
// NOTE: A migration should be added whenever this list changes.
revLogOurOutputIndexType tlv.Type = 0
revLogTheirOutputIndexType tlv.Type = 1
revLogCommitTxHashType tlv.Type = 2
revLogOurBalanceType tlv.Type = 3
revLogTheirBalanceType tlv.Type = 4
)
var ( var (
// revocationLogBucketDeprecated is dedicated for storing the necessary // revocationLogBucketDeprecated is dedicated for storing the necessary
@@ -187,9 +200,6 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
// fields can be viewed as a subset of a ChannelCommitment's. In the database, // fields can be viewed as a subset of a ChannelCommitment's. In the database,
// all historical versions of the RevocationLog are saved using the // all historical versions of the RevocationLog are saved using the
// CommitHeight as the key. // 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 { type RevocationLog struct {
// OurOutputIndex specifies our output index in this commitment. In a // OurOutputIndex specifies our output index in this commitment. In a
// remote commitment transaction, this is the to remote output index. // remote commitment transaction, this is the to remote output index.
@@ -206,29 +216,26 @@ type RevocationLog struct {
// HTLCEntries is the set of HTLCEntry's that are pending at this // HTLCEntries is the set of HTLCEntry's that are pending at this
// particular commitment height. // particular commitment height.
HTLCEntries []*HTLCEntry HTLCEntries []*HTLCEntry
}
// toTlvStream converts an RevocationLog record into a tlv representation. // OurBalance is the current available balance within the channel
func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) { // directly spendable by us. In other words, it is the value of the
const ( // to_remote output on the remote parties' commitment transaction.
// A set of tlv type definitions used to serialize the body of //
// revocation logs to the database. We define it here instead // NOTE: this is a pointer so that it is clear if the value is zero or
// of the head of the file to avoid naming conflicts. // 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
// NOTE: A migration should be added whenever this list // all revocation logs.
// changes. OurBalance *lnwire.MilliSatoshi
ourOutputIndexType tlv.Type = 0
theirOutputIndexType tlv.Type = 1
commitTxHashType tlv.Type = 2
)
return tlv.NewStream( // TheirBalance is the current available balance within the channel
tlv.MakePrimitiveRecord(ourOutputIndexType, &rl.OurOutputIndex), // directly spendable by the remote node. In other words, it is the
tlv.MakePrimitiveRecord( // value of the to_local output on the remote parties' commitment.
theirOutputIndexType, &rl.TheirOutputIndex, //
), // NOTE: this is a pointer so that it is clear if the value is zero or
tlv.MakePrimitiveRecord(commitTxHashType, &rl.CommitTxHash), // 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 // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
@@ -236,7 +243,7 @@ func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) {
// disk. It also saves our output index and their output index, which are // disk. It also saves our output index and their output index, which are
// useful when creating breach retribution. // useful when creating breach retribution.
func putRevocationLog(bucket kvdb.RwBucket, commit *mig.ChannelCommitment, func putRevocationLog(bucket kvdb.RwBucket, commit *mig.ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32) error { ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
// Sanity check that the output indexes can be safely converted. // Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 { if ourOutputIndex > math.MaxUint16 {
@@ -253,6 +260,11 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *mig.ChannelCommitment,
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
} }
if !noAmtData {
rl.OurBalance = &commit.LocalBalance
rl.TheirBalance = &commit.RemoteBalance
}
for _, htlc := range commit.Htlcs { for _, htlc := range commit.Htlcs {
// Skip dust HTLCs. // Skip dust HTLCs.
if htlc.OutputIndex < 0 { if htlc.OutputIndex < 0 {
@@ -304,8 +316,36 @@ func fetchRevocationLog(log kvdb.RBucket,
// serializeRevocationLog serializes a RevocationLog record based on tlv // serializeRevocationLog serializes a RevocationLog record based on tlv
// format. // format.
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
// Add the tlv records for all non-optional fields.
records := []tlv.Record{
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
}
// 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. // Create the tlv stream.
tlvStream, err := rl.toTlvStream() tlvStream, err := tlv.NewStream(records...)
if err != nil { if err != nil {
return err return err
} }
@@ -348,19 +388,48 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
// deserializeRevocationLog deserializes a RevocationLog based on tlv format. // deserializeRevocationLog deserializes a RevocationLog based on tlv format.
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
var rl RevocationLog var (
rl RevocationLog
localBalance uint64
remoteBalance uint64
)
// Create the tlv stream. // Create the tlv stream.
tlvStream, err := rl.toTlvStream() tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
tlv.MakeBigSizeRecord(revLogOurBalanceType, &localBalance),
tlv.MakeBigSizeRecord(
revLogTheirBalanceType, &remoteBalance,
),
)
if err != nil { if err != nil {
return rl, err return rl, err
} }
// Read the tlv stream. // Read the tlv stream.
if err := readTlvStream(r, tlvStream); err != nil { parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
return rl, err 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. // Read the HTLC entries.
rl.HTLCEntries, err = deserializeHTLCEntries(r) rl.HTLCEntries, err = deserializeHTLCEntries(r)
@@ -382,7 +451,7 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
} }
// Read the HTLC entry. // Read the HTLC entry.
if err := readTlvStream(r, tlvStream); err != nil { if _, err := readTlvStream(r, tlvStream); err != nil {
// We've reached the end when hitting an EOF. // We've reached the end when hitting an EOF.
if err == io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF {
break break
@@ -427,7 +496,7 @@ func writeTlvStream(w io.Writer, s *tlv.Stream) error {
// readTlvStream is a helper function that decodes the tlv stream from the // readTlvStream is a helper function that decodes the tlv stream from the
// reader. // reader.
func readTlvStream(r io.Reader, s *tlv.Stream) error { func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
var bodyLen uint64 var bodyLen uint64
// Read the stream's length. // Read the stream's length.
@@ -436,16 +505,17 @@ func readTlvStream(r io.Reader, s *tlv.Stream) error {
// We'll convert any EOFs to ErrUnexpectedEOF, since this results in an // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
// invalid record. // invalid record.
case err == io.EOF: case err == io.EOF:
return io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
// Other unexpected errors. // Other unexpected errors.
case err != nil: case err != nil:
return err return nil, err
} }
// TODO(yy): add overflow check. // TODO(yy): add overflow check.
lr := io.LimitReader(r, int64(bodyLen)) lr := io.LimitReader(r, int64(bodyLen))
return s.Decode(lr)
return s.DecodeWithParsedTypes(lr)
} }
// fetchLogBucket returns a read bucket by visiting both the new and the old // fetchLogBucket returns a read bucket by visiting both the new and the old
+72 -9
View File
@@ -3,6 +3,8 @@ package migration30
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"math/rand"
"time"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
@@ -78,6 +80,12 @@ var (
// logHeight1 is the CommitHeight used by oldLog1. // logHeight1 is the CommitHeight used by oldLog1.
logHeight1 = uint64(0) 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 defines an old revocation that has no HTLCs.
oldLog1 = mig.ChannelCommitment{ oldLog1 = mig.ChannelCommitment{
CommitHeight: logHeight1, CommitHeight: logHeight1,
@@ -85,18 +93,29 @@ var (
LocalHtlcIndex: 0, LocalHtlcIndex: 0,
RemoteLogIndex: 0, RemoteLogIndex: 0,
RemoteHtlcIndex: 0, RemoteHtlcIndex: 0,
LocalBalance: lnwire.MilliSatoshi(990_950_000), LocalBalance: localBalance1,
RemoteBalance: 0, RemoteBalance: remoteBalance1,
CommitTx: commitTx1, 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{ newLog1 = RevocationLog{
OurOutputIndex: 0, OurOutputIndex: 0,
TheirOutputIndex: OutputIndexEmpty, TheirOutputIndex: OutputIndexEmpty,
CommitTxHash: commitTx1.TxHash(), 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, // preimage2 defines the second revocation preimage used in the test,
// generated from itest. // generated from itest.
preimage2 = []byte{ preimage2 = []byte{
@@ -146,6 +165,12 @@ var (
// logHeight2 is the CommitHeight used by oldLog2. // logHeight2 is the CommitHeight used by oldLog2.
logHeight2 = uint64(1) 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 defines an old revocation that has one HTLC.
oldLog2 = mig.ChannelCommitment{ oldLog2 = mig.ChannelCommitment{
CommitHeight: logHeight2, CommitHeight: logHeight2,
@@ -153,13 +178,14 @@ var (
LocalHtlcIndex: 1, LocalHtlcIndex: 1,
RemoteLogIndex: 0, RemoteLogIndex: 0,
RemoteHtlcIndex: 0, RemoteHtlcIndex: 0,
LocalBalance: lnwire.MilliSatoshi(888_800_000), LocalBalance: localBalance2,
RemoteBalance: 0, RemoteBalance: remoteBalance2,
CommitTx: commitTx2, CommitTx: commitTx2,
Htlcs: []mig.HTLC{htlc}, 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{ newLog2 = RevocationLog{
OurOutputIndex: 1, OurOutputIndex: 1,
TheirOutputIndex: OutputIndexEmpty, TheirOutputIndex: OutputIndexEmpty,
@@ -175,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 defines an revocation log that's been created after v0.15.0.
newLog3 = mig.ChannelCommitment{ newLog3 = mig.ChannelCommitment{
CommitHeight: logHeight2 + 1, CommitHeight: logHeight2 + 1,
@@ -260,6 +305,12 @@ var (
0xf8, 0xc3, 0xfc, 0x7, 0x2d, 0x15, 0x99, 0x55, 0xf8, 0xc3, 0xfc, 0x7, 0x2d, 0x15, 0x99, 0x55,
0x8, 0x69, 0xf6, 0x1, 0xa2, 0xcd, 0x6b, 0xa7, 0x8, 0x69, 0xf6, 0x1, 0xa2, 0xcd, 0x6b, 0xa7,
}) })
// withAmtData if set, will result in the amount data of the revoked
// commitment transactions also being stored in the new revocation log.
// The value of this variable is set randomly in the init function of
// this package.
withAmtData bool
) )
// setupTestLogs takes care of creating the related buckets and inserts testing // setupTestLogs takes care of creating the related buckets and inserts testing
@@ -298,7 +349,7 @@ func setupTestLogs(db kvdb.Backend, c *mig26.OpenChannel,
} }
// Create new logs. // Create new logs.
return writeNewRevocationLogs(chanBucket, newLogs) return writeNewRevocationLogs(chanBucket, newLogs, !withAmtData)
}, func() {}) }, func() {})
} }
@@ -452,7 +503,7 @@ func writeOldRevocationLogs(chanBucket kvdb.RwBucket,
// writeNewRevocationLogs saves a new revocation log to db. // writeNewRevocationLogs saves a new revocation log to db.
func writeNewRevocationLogs(chanBucket kvdb.RwBucket, func writeNewRevocationLogs(chanBucket kvdb.RwBucket,
oldLogs []mig.ChannelCommitment) error { oldLogs []mig.ChannelCommitment, noAmtData bool) error {
// Don't bother continue if the logs are empty. // Don't bother continue if the logs are empty.
if len(oldLogs) == 0 { if len(oldLogs) == 0 {
@@ -472,7 +523,7 @@ func writeNewRevocationLogs(chanBucket kvdb.RwBucket,
// old commit tx. We do this intentionally so we can // old commit tx. We do this intentionally so we can
// distinguish a newly created log from an already saved one. // distinguish a newly created log from an already saved one.
err := putRevocationLog( err := putRevocationLog(
logBucket, &c, testOurIndex, testTheirIndex, logBucket, &c, testOurIndex, testTheirIndex, noAmtData,
) )
if err != nil { if err != nil {
return err return err
@@ -551,3 +602,15 @@ func createFinished(cdb kvdb.Backend, c *mig26.OpenChannel,
} }
return setupTestLogs(cdb, c, oldLogs, newLogs) return setupTestLogs(cdb, c, oldLogs, newLogs)
} }
func init() {
rand.Seed(time.Now().Unix())
if rand.Intn(2) == 0 {
withAmtData = true
}
if withAmtData {
newLog1 = newLog1WithAmts
newLog2 = newLog2WithAmts
}
}
+3 -2
View File
@@ -84,12 +84,13 @@ func ApplyMigration(t *testing.T,
} }
} }
// ApplyMigrationWithDb is a helper test function that encapsulates the general // ApplyMigrationWithDB is a helper test function that encapsulates the general
// steps which are needed to properly check the result of applying migration // steps which are needed to properly check the result of applying migration
// function. This function differs from ApplyMigration as it requires the // function. This function differs from ApplyMigration as it requires the
// supplied migration functions to take a db instance and construct their own // supplied migration functions to take a db instance and construct their own
// database transactions. // database transactions.
func ApplyMigrationWithDb(t testing.TB, beforeMigration, afterMigration, func ApplyMigrationWithDB(t testing.TB, beforeMigration,
afterMigration func(db kvdb.Backend) error,
migrationFunc func(db kvdb.Backend) error, shouldFail bool) { migrationFunc func(db kvdb.Backend) error, shouldFail bool) {
t.Helper() t.Helper()
+12
View File
@@ -64,6 +64,10 @@ type Options struct {
// applications that use the channeldb package as a library. // applications that use the channeldb package as a library.
NoMigration bool NoMigration bool
// NoRevLogAmtData when set to true, indicates that amount data should
// not be stored in the revocation log.
NoRevLogAmtData bool
// clock is the time source used by the database. // clock is the time source used by the database.
clock clock.Clock clock clock.Clock
@@ -130,6 +134,14 @@ func OptionSetUseGraphCache(use bool) OptionModifier {
} }
} }
// OptionNoRevLogAmtData sets the NoRevLogAmtData option to the given value. If
// it is set to true then amount data will not be stored in the revocation log.
func OptionNoRevLogAmtData(noAmtData bool) OptionModifier {
return func(o *Options) {
o.NoRevLogAmtData = noAmtData
}
}
// OptionSetSyncFreelist allows the database to sync its freelist. // OptionSetSyncFreelist allows the database to sync its freelist.
func OptionSetSyncFreelist(b bool) OptionModifier { func OptionSetSyncFreelist(b bool) OptionModifier {
return func(o *Options) { return func(o *Options) {
+106 -36
View File
@@ -9,11 +9,24 @@ import (
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/tlv"
) )
// OutputIndexEmpty is used when the output index doesn't exist. const (
const OutputIndexEmpty = math.MaxUint16 // OutputIndexEmpty is used when the output index doesn't exist.
OutputIndexEmpty = math.MaxUint16
// A set of tlv type definitions used to serialize the body of
// revocation logs to the database.
//
// NOTE: A migration should be added whenever this list changes.
revLogOurOutputIndexType tlv.Type = 0
revLogTheirOutputIndexType tlv.Type = 1
revLogCommitTxHashType tlv.Type = 2
revLogOurBalanceType tlv.Type = 3
revLogTheirBalanceType tlv.Type = 4
)
var ( var (
// revocationLogBucketDeprecated is dedicated for storing the necessary // revocationLogBucketDeprecated is dedicated for storing the necessary
@@ -175,9 +188,6 @@ func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
// fields can be viewed as a subset of a ChannelCommitment's. In the database, // fields can be viewed as a subset of a ChannelCommitment's. In the database,
// all historical versions of the RevocationLog are saved using the // all historical versions of the RevocationLog are saved using the
// CommitHeight as the key. // 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 { type RevocationLog struct {
// OurOutputIndex specifies our output index in this commitment. In a // OurOutputIndex specifies our output index in this commitment. In a
// remote commitment transaction, this is the to remote output index. // remote commitment transaction, this is the to remote output index.
@@ -194,29 +204,26 @@ type RevocationLog struct {
// HTLCEntries is the set of HTLCEntry's that are pending at this // HTLCEntries is the set of HTLCEntry's that are pending at this
// particular commitment height. // particular commitment height.
HTLCEntries []*HTLCEntry HTLCEntries []*HTLCEntry
}
// toTlvStream converts an RevocationLog record into a tlv representation. // OurBalance is the current available balance within the channel
func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) { // directly spendable by us. In other words, it is the value of the
const ( // to_remote output on the remote parties' commitment transaction.
// A set of tlv type definitions used to serialize the body of //
// revocation logs to the database. We define it here instead // NOTE: this is a pointer so that it is clear if the value is zero or
// of the head of the file to avoid naming conflicts. // 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
// NOTE: A migration should be added whenever this list // all revocation logs.
// changes. OurBalance *lnwire.MilliSatoshi
ourOutputIndexType tlv.Type = 0
theirOutputIndexType tlv.Type = 1
commitTxHashType tlv.Type = 2
)
return tlv.NewStream( // TheirBalance is the current available balance within the channel
tlv.MakePrimitiveRecord(ourOutputIndexType, &rl.OurOutputIndex), // directly spendable by the remote node. In other words, it is the
tlv.MakePrimitiveRecord( // value of the to_local output on the remote parties' commitment.
theirOutputIndexType, &rl.TheirOutputIndex, //
), // NOTE: this is a pointer so that it is clear if the value is zero or
tlv.MakePrimitiveRecord(commitTxHashType, &rl.CommitTxHash), // 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 // putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
@@ -224,7 +231,7 @@ func (rl *RevocationLog) toTlvStream() (*tlv.Stream, error) {
// disk. It also saves our output index and their output index, which are // disk. It also saves our output index and their output index, which are
// useful when creating breach retribution. // useful when creating breach retribution.
func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment, func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
ourOutputIndex, theirOutputIndex uint32) error { ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
// Sanity check that the output indexes can be safely converted. // Sanity check that the output indexes can be safely converted.
if ourOutputIndex > math.MaxUint16 { if ourOutputIndex > math.MaxUint16 {
@@ -241,6 +248,11 @@ func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)), HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
} }
if !noAmtData {
rl.OurBalance = &commit.LocalBalance
rl.TheirBalance = &commit.RemoteBalance
}
for _, htlc := range commit.Htlcs { for _, htlc := range commit.Htlcs {
// Skip dust HTLCs. // Skip dust HTLCs.
if htlc.OutputIndex < 0 { if htlc.OutputIndex < 0 {
@@ -292,8 +304,36 @@ func fetchRevocationLog(log kvdb.RBucket,
// serializeRevocationLog serializes a RevocationLog record based on tlv // serializeRevocationLog serializes a RevocationLog record based on tlv
// format. // format.
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error { func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
// Add the tlv records for all non-optional fields.
records := []tlv.Record{
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
}
// 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. // Create the tlv stream.
tlvStream, err := rl.toTlvStream() tlvStream, err := tlv.NewStream(records...)
if err != nil { if err != nil {
return err return err
} }
@@ -336,19 +376,48 @@ func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
// deserializeRevocationLog deserializes a RevocationLog based on tlv format. // deserializeRevocationLog deserializes a RevocationLog based on tlv format.
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) { func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
var rl RevocationLog var (
rl RevocationLog
ourBalance uint64
theirBalance uint64
)
// Create the tlv stream. // Create the tlv stream.
tlvStream, err := rl.toTlvStream() tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
revLogOurOutputIndexType, &rl.OurOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogTheirOutputIndexType, &rl.TheirOutputIndex,
),
tlv.MakePrimitiveRecord(
revLogCommitTxHashType, &rl.CommitTxHash,
),
tlv.MakeBigSizeRecord(revLogOurBalanceType, &ourBalance),
tlv.MakeBigSizeRecord(
revLogTheirBalanceType, &theirBalance,
),
)
if err != nil { if err != nil {
return rl, err return rl, err
} }
// Read the tlv stream. // Read the tlv stream.
if err := readTlvStream(r, tlvStream); err != nil { parsedTypes, err := readTlvStream(r, tlvStream)
if err != nil {
return rl, err 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. // Read the HTLC entries.
rl.HTLCEntries, err = deserializeHTLCEntries(r) rl.HTLCEntries, err = deserializeHTLCEntries(r)
@@ -370,7 +439,7 @@ func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
} }
// Read the HTLC entry. // Read the HTLC entry.
if err := readTlvStream(r, tlvStream); err != nil { if _, err := readTlvStream(r, tlvStream); err != nil {
// We've reached the end when hitting an EOF. // We've reached the end when hitting an EOF.
if err == io.ErrUnexpectedEOF { if err == io.ErrUnexpectedEOF {
break break
@@ -415,7 +484,7 @@ func writeTlvStream(w io.Writer, s *tlv.Stream) error {
// readTlvStream is a helper function that decodes the tlv stream from the // readTlvStream is a helper function that decodes the tlv stream from the
// reader. // reader.
func readTlvStream(r io.Reader, s *tlv.Stream) error { func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
var bodyLen uint64 var bodyLen uint64
// Read the stream's length. // Read the stream's length.
@@ -424,16 +493,17 @@ func readTlvStream(r io.Reader, s *tlv.Stream) error {
// We'll convert any EOFs to ErrUnexpectedEOF, since this results in an // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
// invalid record. // invalid record.
case err == io.EOF: case err == io.EOF:
return io.ErrUnexpectedEOF return nil, io.ErrUnexpectedEOF
// Other unexpected errors. // Other unexpected errors.
case err != nil: case err != nil:
return err return nil, err
} }
// TODO(yy): add overflow check. // TODO(yy): add overflow check.
lr := io.LimitReader(r, int64(bodyLen)) lr := io.LimitReader(r, int64(bodyLen))
return s.Decode(lr)
return s.DecodeWithParsedTypes(lr)
} }
// fetchOldRevocationLog finds the revocation log from the deprecated // fetchOldRevocationLog finds the revocation log from the deprecated
+121 -33
View File
@@ -56,10 +56,13 @@ var (
0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40, 0x4, 0x5, 0xfe, 0x0, 0xf, 0x42, 0x40,
} }
localBalance = lnwire.MilliSatoshi(9000)
remoteBalance = lnwire.MilliSatoshi(3000)
testChannelCommit = ChannelCommitment{ testChannelCommit = ChannelCommitment{
CommitHeight: 999, CommitHeight: 999,
LocalBalance: lnwire.MilliSatoshi(9000), LocalBalance: localBalance,
RemoteBalance: lnwire.MilliSatoshi(3000), RemoteBalance: remoteBalance,
CommitFee: btcutil.Amount(rand.Int63()), CommitFee: btcutil.Amount(rand.Int63()),
FeePerKw: btcutil.Amount(5000), FeePerKw: btcutil.Amount(5000),
CommitTx: channels.TestFundingTx, CommitTx: channels.TestFundingTx,
@@ -74,13 +77,13 @@ var (
}}, }},
} }
testRevocationLog = RevocationLog{ testRevocationLogNoAmts = RevocationLog{
OurOutputIndex: 0, OurOutputIndex: 0,
TheirOutputIndex: 1, TheirOutputIndex: 1,
CommitTxHash: testChannelCommit.CommitTx.TxHash(), CommitTxHash: testChannelCommit.CommitTx.TxHash(),
HTLCEntries: []*HTLCEntry{&testHTLCEntry}, HTLCEntries: []*HTLCEntry{&testHTLCEntry},
} }
testRevocationLogBytes = []byte{ testRevocationLogNoAmtsBytes = []byte{
// Body length 42. // Body length 42.
0x2a, 0x2a,
// OurOutputIndex tlv. // OurOutputIndex tlv.
@@ -94,6 +97,33 @@ var (
0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff, 0xbd, 0xf7, 0xd3, 0x9b, 0x16, 0x7d, 0x0, 0xff,
0xc8, 0x22, 0x51, 0xb1, 0x5b, 0xa0, 0xbf, 0xd, 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) { func TestWriteTLVStream(t *testing.T) {
@@ -127,7 +157,7 @@ func TestReadTLVStream(t *testing.T) {
// Read the tlv stream. // Read the tlv stream.
buf := bytes.NewBuffer(testValueBytes) buf := bytes.NewBuffer(testValueBytes)
err = readTlvStream(buf, ts) _, err = readTlvStream(buf, ts)
require.NoError(t, err) require.NoError(t, err)
// Check the bytes are read as expected. // Check the bytes are read as expected.
@@ -150,7 +180,7 @@ func TestReadTLVStreamErr(t *testing.T) {
// Read the tlv stream. // Read the tlv stream.
buf := bytes.NewBuffer(b) buf := bytes.NewBuffer(b)
err = readTlvStream(buf, ts) _, err = readTlvStream(buf, ts)
require.ErrorIs(t, err, io.ErrUnexpectedEOF) require.ErrorIs(t, err, io.ErrUnexpectedEOF)
// Check the bytes are not read. // Check the bytes are not read.
@@ -208,22 +238,75 @@ func TestSerializeHTLCEntries(t *testing.T) {
require.Equal(t, expectedBytes, buf.Bytes()) 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() t.Parallel()
// Copy the testRevocationLog and testHTLCEntry. tests := []struct {
rl := testRevocationLog 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 htlc := testHTLCEntry
rl.HTLCEntries = []*HTLCEntry{&htlc} rl.HTLCEntries = []*HTLCEntry{&htlc}
// Write the tlv stream. // Write the tlv stream.
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
err := serializeRevocationLog(buf, &rl) err := serializeRevocationLog(buf, rl)
require.NoError(t, err) require.NoError(t, err)
// Check the expected bytes on the body of the revocation log. // Check the expected bytes on the body of the revocation log.
bodyIndex := buf.Len() - len(testHTLCEntryBytes) 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) { func TestDerializeHTLCEntriesEmptyRHash(t *testing.T) {
@@ -271,23 +354,6 @@ func TestDerializeHTLCEntries(t *testing.T) {
require.Equal(t, &entry, htlcs[0]) 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) { func TestFetchLogBucket(t *testing.T) {
t.Parallel() t.Parallel()
@@ -368,17 +434,28 @@ func TestPutRevocationLog(t *testing.T) {
commit ChannelCommitment commit ChannelCommitment
ourIndex uint32 ourIndex uint32
theirIndex uint32 theirIndex uint32
noAmtData bool
expectedErr error expectedErr error
expectedLog RevocationLog expectedLog RevocationLog
}{ }{
{ {
// Test a normal put operation. // Test a normal put operation.
name: "successful put", name: "successful put with amount data",
commit: testChannelCommit, commit: testChannelCommit,
ourIndex: 0, ourIndex: 0,
theirIndex: 1, theirIndex: 1,
expectedErr: nil, 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. // Test our index too big.
@@ -409,12 +486,22 @@ func TestPutRevocationLog(t *testing.T) {
}, },
{ {
// Test dust htlc is not saved. // Test dust htlc is not saved.
name: "dust htlc not saved", name: "dust htlc not saved with amout data",
commit: testCommitDust, commit: testCommitDust,
ourIndex: 0, ourIndex: 0,
theirIndex: 1, theirIndex: 1,
expectedErr: nil, 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,
}, },
} }
@@ -435,6 +522,7 @@ func TestPutRevocationLog(t *testing.T) {
// Save the log. // Save the log.
err = putRevocationLog( err = putRevocationLog(
bucket, &tc.commit, tc.ourIndex, tc.theirIndex, bucket, &tc.commit, tc.ourIndex, tc.theirIndex,
tc.noAmtData,
) )
if err != nil { if err != nil {
return RevocationLog{}, err return RevocationLog{}, err
@@ -542,7 +630,7 @@ func TestFetchRevocationLogCompatible(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
err = putRevocationLog( err = putRevocationLog(
lb, &testChannelCommit, 0, 1, lb, &testChannelCommit, 0, 1, false,
) )
require.NoError(t, err) require.NoError(t, err)
} }
+7
View File
@@ -981,6 +981,13 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
) )
} }
// Ensure that the amount data for revoked commitment transactions is
// stored if the watchtower client is active.
if cfg.DB.NoRevLogAmtData && cfg.WtClient.Active {
return nil, mkErr("revocation log amount data must be stored " +
"if the watchtower client is active")
}
// Ensure a valid max channel fee allocation was set. // Ensure a valid max channel fee allocation was set.
if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 { if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
return nil, mkErr("invalid max channel fee allocation: %v, "+ return nil, mkErr("invalid max channel fee allocation: %v, "+
+10 -3
View File
@@ -867,15 +867,22 @@ func (d *DefaultDatabaseBuilder) BuildDatabase(
dbOptions := []channeldb.OptionModifier{ dbOptions := []channeldb.OptionModifier{
channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize), channeldb.OptionSetRejectCacheSize(cfg.Caches.RejectCacheSize),
channeldb.OptionSetChannelCacheSize(cfg.Caches.ChannelCacheSize), channeldb.OptionSetChannelCacheSize(
channeldb.OptionSetBatchCommitInterval(cfg.DB.BatchCommitInterval), cfg.Caches.ChannelCacheSize,
),
channeldb.OptionSetBatchCommitInterval(
cfg.DB.BatchCommitInterval,
),
channeldb.OptionDryRunMigration(cfg.DryRunMigration), channeldb.OptionDryRunMigration(cfg.DryRunMigration),
channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache), channeldb.OptionSetUseGraphCache(!cfg.DB.NoGraphCache),
channeldb.OptionKeepFailedPaymentAttempts(cfg.KeepFailedPaymentAttempts), channeldb.OptionKeepFailedPaymentAttempts(
cfg.KeepFailedPaymentAttempts,
),
channeldb.OptionStoreFinalHtlcResolutions( channeldb.OptionStoreFinalHtlcResolutions(
cfg.StoreFinalHtlcResolutions, cfg.StoreFinalHtlcResolutions,
), ),
channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation), channeldb.OptionPruneRevocationLog(cfg.DB.PruneRevocation),
channeldb.OptionNoRevLogAmtData(cfg.DB.NoRevLogAmtData),
} }
// We want to pre-allocate the channel graph cache according to what we // We want to pre-allocate the channel graph cache according to what we
@@ -408,6 +408,14 @@ in the lnwire package](https://github.com/lightningnetwork/lnd/pull/7303)
3.5.7](https://github.com/lightningnetwork/lnd/pull/7353) to resolve linking 3.5.7](https://github.com/lightningnetwork/lnd/pull/7353) to resolve linking
issues with outdated dependencies. issues with outdated dependencies.
* [Re-add local and remote output amounts to the revocation
log](https://github.com/lightningnetwork/lnd/pull/7379) and add a new
`--db.no-rev-log-amt-data` flag that can be used to explicitly opt out of
storing this extra data. It should be noted that setting this flag is not
recommended unless the user is sure that they will never activate their
watchtower client (`--wtclient.active`) in the future. The new flag can not
be set at all if the `--wtclient.active` flag has been set.
## Pathfinding ## Pathfinding
* [Pathfinding takes capacity of edges into account to improve success * [Pathfinding takes capacity of edges into account to improve success
+2
View File
@@ -85,6 +85,8 @@ type DB struct {
NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."` NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."`
PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."` PruneRevocation bool `long:"prune-revocation" description:"Run the optional migration that prunes the revocation logs to save disk space."`
NoRevLogAmtData bool `long:"no-rev-log-amt-data" description:"If set, the to-local and to-remote output amounts of revoked commitment transactions will not be stored in the revocation log. Note that once this data is lost, a watchtower client will not be able to back up the revoked state."`
} }
// DefaultDB creates and returns a new default DB config. // DefaultDB creates and returns a new default DB config.
+69 -23
View File
@@ -108,6 +108,10 @@ var (
// ErrOutputIndexOutOfRange is returned when an output index is greater // ErrOutputIndexOutOfRange is returned when an output index is greater
// than or equal to the length of a given transaction's outputs. // than or equal to the length of a given transaction's outputs.
ErrOutputIndexOutOfRange = errors.New("output index is out of range") ErrOutputIndexOutOfRange = errors.New("output index is out of range")
// ErrRevLogDataMissing is returned when a certain wanted optional field
// in a revocation log entry is missing.
ErrRevLogDataMissing = errors.New("revocation log data missing")
) )
// ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid // ErrCommitSyncLocalDataLoss is returned in the case that we receive a valid
@@ -2287,8 +2291,12 @@ type BreachRetribution struct {
} }
// NewBreachRetribution creates a new fully populated BreachRetribution for the // NewBreachRetribution creates a new fully populated BreachRetribution for the
// passed channel, at a particular revoked state number, and one which targets // passed channel, at a particular revoked state number. If the spend
// the passed commitment transaction. // transaction that the breach retribution should target is known, then it can
// be provided via the spendTx parameter. Otherwise, if the spendTx parameter is
// nil, then the revocation log will be checked to see if it contains the info
// required to construct the BreachRetribution. If the revocation log is missing
// the required fields then ErrRevLogDataMissing will be returned.
func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) { breachHeight uint32, spendTx *wire.MsgTx) (*BreachRetribution, error) {
@@ -2493,7 +2501,11 @@ func createHtlcRetribution(chanState *channeldb.OpenChannel,
// createBreachRetribution creates a partially initiated BreachRetribution // createBreachRetribution creates a partially initiated BreachRetribution
// using a RevocationLog. Returns the constructed retribution, our amount, // using a RevocationLog. Returns the constructed retribution, our amount,
// their amount, and a possible non-nil error. // their amount, and a possible non-nil error. If the spendTx parameter is
// non-nil, then it will be used to glean the breach transaction's to-local and
// to-remote output amounts. Otherwise, the RevocationLog will be checked to
// see if these fields are present there. If they are not, then
// ErrRevLogDataMissing is returned.
func createBreachRetribution(revokedLog *channeldb.RevocationLog, func createBreachRetribution(revokedLog *channeldb.RevocationLog,
spendTx *wire.MsgTx, chanState *channeldb.OpenChannel, spendTx *wire.MsgTx, chanState *channeldb.OpenChannel,
keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey, keyRing *CommitmentKeyRing, commitmentSecret *btcec.PrivateKey,
@@ -2523,18 +2535,33 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog,
if revokedLog.OurOutputIndex != channeldb.OutputIndexEmpty { if revokedLog.OurOutputIndex != channeldb.OutputIndexEmpty {
ourOutpoint.Index = uint32(revokedLog.OurOutputIndex) ourOutpoint.Index = uint32(revokedLog.OurOutputIndex)
// Sanity check that OurOutputIndex is within range. // If the spend transaction is provided, then we use it to get
if int(ourOutpoint.Index) >= len(spendTx.TxOut) { // the value of our output.
return nil, 0, 0, fmt.Errorf("%w: ours=%v, "+ if spendTx != nil {
"len(TxOut)=%v", ErrOutputIndexOutOfRange, // Sanity check that OurOutputIndex is within range.
ourOutpoint.Index, len(spendTx.TxOut), if int(ourOutpoint.Index) >= len(spendTx.TxOut) {
) return nil, 0, 0, fmt.Errorf("%w: ours=%v, "+
"len(TxOut)=%v",
ErrOutputIndexOutOfRange,
ourOutpoint.Index, len(spendTx.TxOut),
)
}
// Read the amounts from the breach transaction.
//
// NOTE: ourAmt here includes commit fee and anchor
// amount (if enabled).
ourAmt = spendTx.TxOut[ourOutpoint.Index].Value
} else {
// Otherwise, we check to see if the revocation log
// contains our output amount. Due to a previous
// migration, this field may be empty in which case an
// error will be returned.
if revokedLog.OurBalance == nil {
return nil, 0, 0, ErrRevLogDataMissing
}
ourAmt = int64(revokedLog.OurBalance.ToSatoshis())
} }
// Read the amounts from the breach transaction.
//
// NOTE: ourAmt here includes commit fee and anchor amount(if
// enabled).
ourAmt = spendTx.TxOut[ourOutpoint.Index].Value
} }
// Construct the their outpoint. // Construct the their outpoint.
@@ -2544,16 +2571,35 @@ func createBreachRetribution(revokedLog *channeldb.RevocationLog,
if revokedLog.TheirOutputIndex != channeldb.OutputIndexEmpty { if revokedLog.TheirOutputIndex != channeldb.OutputIndexEmpty {
theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex) theirOutpoint.Index = uint32(revokedLog.TheirOutputIndex)
// Sanity check that TheirOutputIndex is within range. // If the spend transaction is provided, then we use it to get
if int(revokedLog.TheirOutputIndex) >= len(spendTx.TxOut) { // the value of the remote parties' output.
return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+ if spendTx != nil {
"len(TxOut)=%v", ErrOutputIndexOutOfRange, // Sanity check that TheirOutputIndex is within range.
revokedLog.TheirOutputIndex, len(spendTx.TxOut), if int(revokedLog.TheirOutputIndex) >=
) len(spendTx.TxOut) {
}
// Read the amounts from the breach transaction. return nil, 0, 0, fmt.Errorf("%w: theirs=%v, "+
theirAmt = spendTx.TxOut[theirOutpoint.Index].Value "len(TxOut)=%v",
ErrOutputIndexOutOfRange,
revokedLog.TheirOutputIndex,
len(spendTx.TxOut),
)
}
// Read the amounts from the breach transaction.
theirAmt = spendTx.TxOut[theirOutpoint.Index].Value
} else {
// Otherwise, we check to see if the revocation log
// contains remote parties' output amount. Due to a
// previous migration, this field may be empty in which
// case an error will be returned.
if revokedLog.TheirBalance == nil {
return nil, 0, 0, ErrRevLogDataMissing
}
theirAmt = int64(revokedLog.TheirBalance.ToSatoshis())
}
} }
return &BreachRetribution{ return &BreachRetribution{
+67 -5
View File
@@ -9482,7 +9482,7 @@ func TestCreateHtlcRetribution(t *testing.T) {
} }
// TestCreateBreachRetribution checks that `createBreachRetribution` behaves as // TestCreateBreachRetribution checks that `createBreachRetribution` behaves as
// epxected. // expected.
func TestCreateBreachRetribution(t *testing.T) { func TestCreateBreachRetribution(t *testing.T) {
t.Parallel() t.Parallel()
@@ -9524,11 +9524,15 @@ func TestCreateBreachRetribution(t *testing.T) {
} }
// Create a dummy revocation log. // Create a dummy revocation log.
ourAmtMsat := lnwire.MilliSatoshi(ourAmt * 1000)
theirAmtMsat := lnwire.MilliSatoshi(theirAmt * 1000)
revokedLog := channeldb.RevocationLog{ revokedLog := channeldb.RevocationLog{
CommitTxHash: commitHash, CommitTxHash: commitHash,
OurOutputIndex: uint16(localIndex), OurOutputIndex: uint16(localIndex),
TheirOutputIndex: uint16(remoteIndex), TheirOutputIndex: uint16(remoteIndex),
HTLCEntries: []*channeldb.HTLCEntry{htlc}, HTLCEntries: []*channeldb.HTLCEntry{htlc},
TheirBalance: &theirAmtMsat,
OurBalance: &ourAmtMsat,
} }
// Create a log with an empty local output index. // Create a log with an empty local output index.
@@ -9545,14 +9549,25 @@ func TestCreateBreachRetribution(t *testing.T) {
expectedErr error expectedErr error
expectedOurAmt int64 expectedOurAmt int64
expectedTheirAmt int64 expectedTheirAmt int64
noSpendTx bool
}{ }{
{ {
name: "create retribution successfully", name: "create retribution successfully " +
"with spend tx",
revocationLog: &revokedLog, revocationLog: &revokedLog,
expectedErr: nil, expectedErr: nil,
expectedOurAmt: ourAmt, expectedOurAmt: ourAmt,
expectedTheirAmt: theirAmt, expectedTheirAmt: theirAmt,
}, },
{
name: "create retribution successfully " +
"without spend tx",
revocationLog: &revokedLog,
expectedErr: nil,
expectedOurAmt: ourAmt,
expectedTheirAmt: theirAmt,
noSpendTx: true,
},
{ {
name: "fail due to our index too big", name: "fail due to our index too big",
revocationLog: &channeldb.RevocationLog{ revocationLog: &channeldb.RevocationLog{
@@ -9568,19 +9583,39 @@ func TestCreateBreachRetribution(t *testing.T) {
expectedErr: ErrOutputIndexOutOfRange, expectedErr: ErrOutputIndexOutOfRange,
}, },
{ {
name: "empty local output index", name: "empty local output index with spend " +
"tx",
revocationLog: &revokedLogNoLocal, revocationLog: &revokedLogNoLocal,
expectedErr: nil, expectedErr: nil,
expectedOurAmt: 0, expectedOurAmt: 0,
expectedTheirAmt: theirAmt, expectedTheirAmt: theirAmt,
}, },
{ {
name: "empty remote output index", name: "empty local output index without spend " +
"tx",
revocationLog: &revokedLogNoLocal,
expectedErr: nil,
expectedOurAmt: 0,
expectedTheirAmt: theirAmt,
noSpendTx: true,
},
{
name: "empty remote output index with spend " +
"tx",
revocationLog: &revokedLogNoRemote, revocationLog: &revokedLogNoRemote,
expectedErr: nil, expectedErr: nil,
expectedOurAmt: ourAmt, expectedOurAmt: ourAmt,
expectedTheirAmt: 0, expectedTheirAmt: 0,
}, },
{
name: "empty remote output index without spend " +
"tx",
revocationLog: &revokedLogNoRemote,
expectedErr: nil,
expectedOurAmt: ourAmt,
expectedTheirAmt: 0,
noSpendTx: true,
},
} }
// assertRetribution is a helper closure that checks a given breach // assertRetribution is a helper closure that checks a given breach
@@ -9623,8 +9658,13 @@ func TestCreateBreachRetribution(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
tx := spendTx
if tc.noSpendTx {
tx = nil
}
br, our, their, err := createBreachRetribution( br, our, their, err := createBreachRetribution(
tc.revocationLog, spendTx, tc.revocationLog, tx,
aliceChannel.channelState, keyRing, aliceChannel.channelState, keyRing,
dummyPrivate, leaseExpiry, dummyPrivate, leaseExpiry,
) )
@@ -9748,6 +9788,13 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
) )
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas) require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
// We also check that the same error is returned if no breach tx is
// provided.
_, err = NewBreachRetribution(
aliceChannel.channelState, stateNum, breachHeight, nil,
)
require.ErrorIs(t, err, channeldb.ErrNoPastDeltas)
// We now force a state transition which will give us a revocation log // We now force a state transition which will give us a revocation log
// at height 0. // at height 0.
txid := aliceChannel.channelState.RemoteCommitment.CommitTx.TxHash() txid := aliceChannel.channelState.RemoteCommitment.CommitTx.TxHash()
@@ -9797,10 +9844,25 @@ func testNewBreachRetribution(t *testing.T, chanType channeldb.ChannelType) {
t.Log(spew.Sdump(breachTx)) t.Log(spew.Sdump(breachTx))
assertRetribution(br, 1, 0) assertRetribution(br, 1, 0)
// Repeat the check but with the breach tx set to nil. This should work
// since the necessary info should now be found in the revocation log.
br, err = NewBreachRetribution(
aliceChannel.channelState, stateNum, breachHeight, nil,
)
require.NoError(t, err)
assertRetribution(br, 1, 0)
// Create the retribution using a stateNum+1 and we should expect an // Create the retribution using a stateNum+1 and we should expect an
// error. // error.
_, err = NewBreachRetribution( _, err = NewBreachRetribution(
aliceChannel.channelState, stateNum+1, breachHeight, breachTx, aliceChannel.channelState, stateNum+1, breachHeight, breachTx,
) )
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound) require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
// Once again, repeat the check for the case when no breach tx is
// provided.
_, err = NewBreachRetribution(
aliceChannel.channelState, stateNum+1, breachHeight, nil,
)
require.ErrorIs(t, err, channeldb.ErrLogEntryNotFound)
} }
+6
View File
@@ -1241,6 +1241,12 @@ litecoin.node=ltcd
; channels prior to lnd@v0.15.0. ; channels prior to lnd@v0.15.0.
; db.prune-revocation=false ; db.prune-revocation=false
; If set to true, then the to-local and to-remote output amount data of revoked
; commitment transactions will not be stored in the revocation log. Note that
; this flag can only be set if --wtclient.active is not set. It is not
; recommended to set this flag if you plan on ever setting wtclient.active in
; the future.
; db.no-rev-log-amt-data=false
[etcd] [etcd]
+1 -1
View File
@@ -237,7 +237,7 @@ func TestMigrateAckedUpdates(t *testing.T) {
// summary bucket and a new index bucket. // summary bucket and a new index bucket.
after := after(test.shouldFail, test.pre, test.post) after := after(test.shouldFail, test.pre, test.post)
migtest.ApplyMigrationWithDb( migtest.ApplyMigrationWithDB(
t, before, after, MigrateAckedUpdates(2), t, before, after, MigrateAckedUpdates(2),
test.shouldFail, test.shouldFail,
) )