lnd.xprv/channeldb/forwarding_package_test.go
Yong 582b164c46
kvdb: add timeout options for bbolt (#4787)
* mod: bump btcwallet version to accept db timeout

* btcwallet: add DBTimeOut in config

* kvdb: add database timeout option for bbolt

This commit adds a DBTimeout option in bbolt config. The relevant
functions walletdb.Open/Create are updated to use this config. In
addition, the bolt compacter also applies the new timeout option.

* channeldb: add DBTimeout in db options

This commit adds the DBTimeout option for channeldb. A new unit
test file is created to test the default options. In addition,
the params used in kvdb.Create inside channeldb_test is updated
with a DefaultDBTimeout value.

* contractcourt+routing: use DBTimeout in kvdb

This commit touches multiple test files in contractcourt and routing.
The call of function kvdb.Create and kvdb.Open are now updated with
the new param DBTimeout, using the default value kvdb.DefaultDBTimeout.

* lncfg: add DBTimeout option in db config

The DBTimeout option is added to db config. A new unit test is
added to check the default DB config is created as expected.

* migration: add DBTimeout param in kvdb.Create/kvdb.Open

* keychain: update tests to use DBTimeout param

* htlcswitch+chainreg: add DBTimeout option

* macaroons: support DBTimeout config in creation

This commit adds the DBTimeout during the creation of macaroons.db.
The usage of kvdb.Create and kvdb.Open in its tests are updated with
a timeout value using kvdb.DefaultDBTimeout.

* walletunlocker: add dbTimeout option in UnlockerService

This commit adds a new param, dbTimeout, during the creation of
UnlockerService. This param is then passed to wallet.NewLoader
inside various service calls, specifying a timeout value to be
used when opening the bbolt. In addition, the macaroonService
is also called with this dbTimeout param.

* watchtower/wtdb: add dbTimeout param during creation

This commit adds the dbTimeout param for the creation of both
watchtower.db and wtclient.db.

* multi: add db timeout param for walletdb.Create

This commit adds the db timeout param for the function call
walletdb.Create. It touches only the test files found in chainntnfs,
lnwallet, and routing.

* lnd: pass DBTimeout config to relevant services

This commit enables lnd to pass the DBTimeout config to the following
services/config/functions,
  - chainControlConfig
  - walletunlocker
  - wallet.NewLoader
  - macaroons
  - watchtower
In addition, the usage of wallet.Create is updated too.

* sample-config: add dbtimeout option
2020-12-07 15:31:49 -08:00

820 lines
27 KiB
Go

package channeldb_test
import (
"bytes"
"io/ioutil"
"path/filepath"
"runtime"
"testing"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
)
// TestPkgFilterBruteForce tests the behavior of a pkg filter up to size 1000,
// which is greater than the number of HTLCs we permit on a commitment txn.
// This should encapsulate every potential filter used in practice.
func TestPkgFilterBruteForce(t *testing.T) {
t.Parallel()
checkPkgFilterRange(t, 1000)
}
// checkPkgFilterRange verifies the behavior of a pkg filter when doing a linear
// insertion of `high` elements. This is primarily to test that IsFull functions
// properly for all relevant sizes of `high`.
func checkPkgFilterRange(t *testing.T, high int) {
for i := uint16(0); i < uint16(high); i++ {
f := channeldb.NewPkgFilter(i)
if f.Count() != i {
t.Fatalf("pkg filter count=%d is actually %d",
i, f.Count())
}
checkPkgFilterEncodeDecode(t, i, f)
for j := uint16(0); j < i; j++ {
if f.Contains(j) {
t.Fatalf("pkg filter count=%d contains %d "+
"before being added", i, j)
}
f.Set(j)
checkPkgFilterEncodeDecode(t, i, f)
if !f.Contains(j) {
t.Fatalf("pkg filter count=%d missing %d "+
"after being added", i, j)
}
if j < i-1 && f.IsFull() {
t.Fatalf("pkg filter count=%d already full", i)
}
}
if !f.IsFull() {
t.Fatalf("pkg filter count=%d not full", i)
}
checkPkgFilterEncodeDecode(t, i, f)
}
}
// TestPkgFilterRand uses a random permutation to verify the proper behavior of
// the pkg filter if the entries are not inserted in-order.
func TestPkgFilterRand(t *testing.T) {
t.Parallel()
checkPkgFilterRand(t, 3, 17)
}
// checkPkgFilterRand checks the behavior of a pkg filter by randomly inserting
// indices and asserting the invariants. The order in which indices are inserted
// is parameterized by a base `b` coprime to `p`, and using modular
// exponentiation to generate all elements in [1,p).
func checkPkgFilterRand(t *testing.T, b, p uint16) {
f := channeldb.NewPkgFilter(p)
var j = b
for i := uint16(1); i < p; i++ {
if f.Contains(j) {
t.Fatalf("pkg filter contains %d-%d "+
"before being added", i, j)
}
f.Set(j)
checkPkgFilterEncodeDecode(t, i, f)
if !f.Contains(j) {
t.Fatalf("pkg filter missing %d-%d "+
"after being added", i, j)
}
if i < p-1 && f.IsFull() {
t.Fatalf("pkg filter %d already full", i)
}
checkPkgFilterEncodeDecode(t, i, f)
j = (b * j) % p
}
// Set 0 independently, since it will never be emitted by the generator.
f.Set(0)
checkPkgFilterEncodeDecode(t, p, f)
if !f.IsFull() {
t.Fatalf("pkg filter count=%d not full", p)
}
checkPkgFilterEncodeDecode(t, p, f)
}
// checkPkgFilterEncodeDecode tests the serialization of a pkg filter by:
// 1) writing it to a buffer
// 2) verifying the number of bytes written matches the filter's Size()
// 3) reconstructing the filter decoding the bytes
// 4) checking that the two filters are the same according to Equal
func checkPkgFilterEncodeDecode(t *testing.T, i uint16, f *channeldb.PkgFilter) {
var b bytes.Buffer
if err := f.Encode(&b); err != nil {
t.Fatalf("unable to serialize pkg filter: %v", err)
}
// +2 for uint16 length
size := uint16(len(b.Bytes()))
if size != f.Size() {
t.Fatalf("pkg filter count=%d serialized size differs, "+
"Size(): %d, len(bytes): %v", i, f.Size(), size)
}
reader := bytes.NewReader(b.Bytes())
f2 := &channeldb.PkgFilter{}
if err := f2.Decode(reader); err != nil {
t.Fatalf("unable to deserialize pkg filter: %v", err)
}
if !f.Equal(f2) {
t.Fatalf("pkg filter count=%v does is not equal "+
"after deserialization, want: %v, got %v",
i, f, f2)
}
}
var (
chanID = lnwire.NewChanIDFromOutPoint(&wire.OutPoint{})
adds = []channeldb.LogUpdate{
{
LogIndex: 0,
UpdateMsg: &lnwire.UpdateAddHTLC{
ChanID: chanID,
ID: 1,
Amount: 100,
Expiry: 1000,
PaymentHash: [32]byte{0},
},
},
{
LogIndex: 1,
UpdateMsg: &lnwire.UpdateAddHTLC{
ChanID: chanID,
ID: 1,
Amount: 101,
Expiry: 1001,
PaymentHash: [32]byte{1},
},
},
}
settleFails = []channeldb.LogUpdate{
{
LogIndex: 2,
UpdateMsg: &lnwire.UpdateFulfillHTLC{
ChanID: chanID,
ID: 0,
PaymentPreimage: [32]byte{0},
},
},
{
LogIndex: 3,
UpdateMsg: &lnwire.UpdateFailHTLC{
ChanID: chanID,
ID: 1,
Reason: []byte{},
},
},
}
)
// TestPackagerEmptyFwdPkg checks that the state transitions exhibited by a
// forwarding package that contains no adds, fails or settles. We expect that
// the fwdpkg reaches FwdStateCompleted immediately after writing the forwarding
// decision via SetFwdFilter.
func TestPackagerEmptyFwdPkg(t *testing.T) {
t.Parallel()
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
packager := channeldb.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
// Next, create and write a new forwarding package with no htlcs.
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, nil, nil)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
}, func() {}); err != nil {
t.Fatalf("unable to add fwd pkg: %v", err)
}
// There should now be one fwdpkg on disk. Since no forwarding decision
// has been written, we expect it to be FwdStateLockedIn. With no HTLCs,
// the ack filter will have no elements, and should always return true.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Now, write the forwarding decision. In this case, its just an empty
// fwd filter.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.SetFwdFilter(tx, fwdPkg.Height, fwdPkg.FwdFilter)
}, func() {}); err != nil {
t.Fatalf("unable to set fwdfiter: %v", err)
}
// We should still have one package on disk. Since the forwarding
// decision has been written, it will minimally be in FwdStateProcessed.
// However with no htlcs, it should leap frog to FwdStateCompleted.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Lastly, remove the completed forwarding package from disk.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.RemovePkg(tx, fwdPkg.Height)
}, func() {}); err != nil {
t.Fatalf("unable to remove fwdpkg: %v", err)
}
// Check that the fwd package was actually removed.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
}
// TestPackagerOnlyAdds checks that the fwdpkg does not reach FwdStateCompleted
// as soon as all the adds in the package have been acked using AckAddHtlcs.
func TestPackagerOnlyAdds(t *testing.T) {
t.Parallel()
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
packager := channeldb.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
// Next, create and write a new forwarding package that only has add
// htlcs.
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, nil)
nAdds := len(adds)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
}, func() {}); err != nil {
t.Fatalf("unable to add fwd pkg: %v", err)
}
// There should now be one fwdpkg on disk. Since no forwarding decision
// has been written, we expect it to be FwdStateLockedIn. The package
// has unacked add HTLCs, so the ack filter should not be full.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], false)
// Now, write the forwarding decision. Since we have not explicitly
// added any adds to the fwdfilter, this would indicate that all of the
// adds were 1) settled locally by this link (exit hop), or 2) the htlc
// was failed locally.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.SetFwdFilter(tx, fwdPkg.Height, fwdPkg.FwdFilter)
}, func() {}); err != nil {
t.Fatalf("unable to set fwdfiter: %v", err)
}
for i := range adds {
// We should still have one package on disk. Since the forwarding
// decision has been written, it will minimally be in FwdStateProcessed.
// However not allf of the HTLCs have been acked, so should not
// have advanced further.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], false)
addRef := channeldb.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckAddHtlcs(tx, addRef)
}, func() {}); err != nil {
t.Fatalf("unable to ack add htlc: %v", err)
}
}
// We should still have one package on disk. Now that all adds have been
// acked, the ack filter should return true and the package should be
// FwdStateCompleted since there are no other settle/fail packets.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, 0)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Lastly, remove the completed forwarding package from disk.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.RemovePkg(tx, fwdPkg.Height)
}, func() {}); err != nil {
t.Fatalf("unable to remove fwdpkg: %v", err)
}
// Check that the fwd package was actually removed.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
}
// TestPackagerOnlySettleFails asserts that the fwdpkg remains in
// FwdStateProcessed after writing the forwarding decision when there are no
// adds in the fwdpkg. We expect this because an empty FwdFilter will always
// return true, but we are still waiting for the remaining fails and settles to
// be deleted.
func TestPackagerOnlySettleFails(t *testing.T) {
t.Parallel()
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
packager := channeldb.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
// Next, create and write a new forwarding package that only has add
// htlcs.
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, nil, settleFails)
nSettleFails := len(settleFails)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
}, func() {}); err != nil {
t.Fatalf("unable to add fwd pkg: %v", err)
}
// There should now be one fwdpkg on disk. Since no forwarding decision
// has been written, we expect it to be FwdStateLockedIn. The package
// has unacked add HTLCs, so the ack filter should not be full.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Now, write the forwarding decision. Since we have not explicitly
// added any adds to the fwdfilter, this would indicate that all of the
// adds were 1) settled locally by this link (exit hop), or 2) the htlc
// was failed locally.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.SetFwdFilter(tx, fwdPkg.Height, fwdPkg.FwdFilter)
}, func() {}); err != nil {
t.Fatalf("unable to set fwdfiter: %v", err)
}
for i := range settleFails {
// We should still have one package on disk. Since the
// forwarding decision has been written, it will minimally be in
// FwdStateProcessed. However, not all of the HTLCs have been
// acked, so should not have advanced further.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], true)
failSettleRef := channeldb.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckSettleFails(tx, failSettleRef)
}, func() {}); err != nil {
t.Fatalf("unable to ack add htlc: %v", err)
}
}
// We should still have one package on disk. Now that all settles and
// fails have been removed, package should be FwdStateCompleted since
// there are no other add packets.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], 0, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Lastly, remove the completed forwarding package from disk.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.RemovePkg(tx, fwdPkg.Height)
}, func() {}); err != nil {
t.Fatalf("unable to remove fwdpkg: %v", err)
}
// Check that the fwd package was actually removed.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
}
// TestPackagerAddsThenSettleFails writes a fwdpkg containing both adds and
// settle/fails, then checks the behavior when the adds are acked before any of
// the settle fails. Here we expect pkg to remain in FwdStateProcessed while the
// remainder of the fail/settles are being deleted.
func TestPackagerAddsThenSettleFails(t *testing.T) {
t.Parallel()
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
packager := channeldb.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
// Next, create and write a new forwarding package that only has add
// htlcs.
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
nAdds := len(adds)
nSettleFails := len(settleFails)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
}, func() {}); err != nil {
t.Fatalf("unable to add fwd pkg: %v", err)
}
// There should now be one fwdpkg on disk. Since no forwarding decision
// has been written, we expect it to be FwdStateLockedIn. The package
// has unacked add HTLCs, so the ack filter should not be full.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], false)
// Now, write the forwarding decision. Since we have not explicitly
// added any adds to the fwdfilter, this would indicate that all of the
// adds were 1) settled locally by this link (exit hop), or 2) the htlc
// was failed locally.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.SetFwdFilter(tx, fwdPkg.Height, fwdPkg.FwdFilter)
}, func() {}); err != nil {
t.Fatalf("unable to set fwdfiter: %v", err)
}
for i := range adds {
// We should still have one package on disk. Since the forwarding
// decision has been written, it will minimally be in FwdStateProcessed.
// However not allf of the HTLCs have been acked, so should not
// have advanced further.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], false)
addRef := channeldb.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckAddHtlcs(tx, addRef)
}, func() {}); err != nil {
t.Fatalf("unable to ack add htlc: %v", err)
}
}
for i := range settleFails {
// We should still have one package on disk. Since the
// forwarding decision has been written, it will minimally be in
// FwdStateProcessed. However not allf of the HTLCs have been
// acked, so should not have advanced further.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], true)
failSettleRef := channeldb.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckSettleFails(tx, failSettleRef)
}, func() {}); err != nil {
t.Fatalf("unable to remove settle/fail htlc: %v", err)
}
}
// We should still have one package on disk. Now that all settles and
// fails have been removed, package should be FwdStateCompleted since
// there are no other add packets.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Lastly, remove the completed forwarding package from disk.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.RemovePkg(tx, fwdPkg.Height)
}, func() {}); err != nil {
t.Fatalf("unable to remove fwdpkg: %v", err)
}
// Check that the fwd package was actually removed.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
}
// TestPackagerSettleFailsThenAdds writes a fwdpkg with both adds and
// settle/fails, then checks the behavior when the settle/fails are removed
// before any of the adds have been acked. This should cause the fwdpkg to
// remain in FwdStateProcessed until the final ack is recorded, at which point
// it should be promoted directly to FwdStateCompleted.since all adds have been
// removed.
func TestPackagerSettleFailsThenAdds(t *testing.T) {
t.Parallel()
db := makeFwdPkgDB(t, "")
shortChanID := lnwire.NewShortChanIDFromInt(1)
packager := channeldb.NewChannelPackager(shortChanID)
// To begin, there should be no forwarding packages on disk.
fwdPkgs := loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
// Next, create and write a new forwarding package that has both add
// and settle/fail htlcs.
fwdPkg := channeldb.NewFwdPkg(shortChanID, 0, adds, settleFails)
nAdds := len(adds)
nSettleFails := len(settleFails)
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AddFwdPkg(tx, fwdPkg)
}, func() {}); err != nil {
t.Fatalf("unable to add fwd pkg: %v", err)
}
// There should now be one fwdpkg on disk. Since no forwarding decision
// has been written, we expect it to be FwdStateLockedIn. The package
// has unacked add HTLCs, so the ack filter should not be full.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateLockedIn)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertAckFilterIsFull(t, fwdPkgs[0], false)
// Now, write the forwarding decision. Since we have not explicitly
// added any adds to the fwdfilter, this would indicate that all of the
// adds were 1) settled locally by this link (exit hop), or 2) the htlc
// was failed locally.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.SetFwdFilter(tx, fwdPkg.Height, fwdPkg.FwdFilter)
}, func() {}); err != nil {
t.Fatalf("unable to set fwdfiter: %v", err)
}
// Simulate another channel deleting the settle/fails it received from
// the original fwd pkg.
// TODO(conner): use different packager/s?
for i := range settleFails {
// We should still have one package on disk. Since the
// forwarding decision has been written, it will minimally be in
// FwdStateProcessed. However none all of the add HTLCs have
// been acked, so should not have advanced further.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], false)
assertAckFilterIsFull(t, fwdPkgs[0], false)
failSettleRef := channeldb.SettleFailRef{
Source: shortChanID,
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckSettleFails(tx, failSettleRef)
}, func() {}); err != nil {
t.Fatalf("unable to remove settle/fail htlc: %v", err)
}
}
// Now simulate this channel receiving a fail/settle for the adds in the
// fwdpkg.
for i := range adds {
// Again, we should still have one package on disk and be in
// FwdStateProcessed. This should not change until all of the
// add htlcs have been acked.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateProcessed)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], false)
addRef := channeldb.AddRef{
Height: fwdPkg.Height,
Index: uint16(i),
}
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.AckAddHtlcs(tx, addRef)
}, func() {}); err != nil {
t.Fatalf("unable to ack add htlc: %v", err)
}
}
// We should still have one package on disk. Now that all settles and
// fails have been removed, package should be FwdStateCompleted since
// there are no other add packets.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 1 {
t.Fatalf("expected 1 fwdpkg, instead found %d", len(fwdPkgs))
}
assertFwdPkgState(t, fwdPkgs[0], channeldb.FwdStateCompleted)
assertFwdPkgNumAddsSettleFails(t, fwdPkgs[0], nAdds, nSettleFails)
assertSettleFailFilterIsFull(t, fwdPkgs[0], true)
assertAckFilterIsFull(t, fwdPkgs[0], true)
// Lastly, remove the completed forwarding package from disk.
if err := kvdb.Update(db, func(tx kvdb.RwTx) error {
return packager.RemovePkg(tx, fwdPkg.Height)
}, func() {}); err != nil {
t.Fatalf("unable to remove fwdpkg: %v", err)
}
// Check that the fwd package was actually removed.
fwdPkgs = loadFwdPkgs(t, db, packager)
if len(fwdPkgs) != 0 {
t.Fatalf("no forwarding packages should exist, found %d", len(fwdPkgs))
}
}
// assertFwdPkgState checks the current state of a fwdpkg meets our
// expectations.
func assertFwdPkgState(t *testing.T, fwdPkg *channeldb.FwdPkg,
state channeldb.FwdState) {
_, _, line, _ := runtime.Caller(1)
if fwdPkg.State != state {
t.Fatalf("line %d: expected fwdpkg in state %v, found %v",
line, state, fwdPkg.State)
}
}
// assertFwdPkgNumAddsSettleFails checks that the number of adds and
// settle/fail log updates are correct.
func assertFwdPkgNumAddsSettleFails(t *testing.T, fwdPkg *channeldb.FwdPkg,
expectedNumAdds, expectedNumSettleFails int) {
_, _, line, _ := runtime.Caller(1)
if len(fwdPkg.Adds) != expectedNumAdds {
t.Fatalf("line %d: expected fwdpkg to have %d adds, found %d",
line, expectedNumAdds, len(fwdPkg.Adds))
}
if len(fwdPkg.SettleFails) != expectedNumSettleFails {
t.Fatalf("line %d: expected fwdpkg to have %d settle/fails, found %d",
line, expectedNumSettleFails, len(fwdPkg.SettleFails))
}
}
// assertAckFilterIsFull checks whether or not a fwdpkg's ack filter matches our
// expected full-ness.
func assertAckFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expected bool) {
_, _, line, _ := runtime.Caller(1)
if fwdPkg.AckFilter.IsFull() != expected {
t.Fatalf("line %d: expected fwdpkg ack filter IsFull to be %v, "+
"found %v", line, expected, fwdPkg.AckFilter.IsFull())
}
}
// assertSettleFailFilterIsFull checks whether or not a fwdpkg's settle fail
// filter matches our expected full-ness.
func assertSettleFailFilterIsFull(t *testing.T, fwdPkg *channeldb.FwdPkg, expected bool) {
_, _, line, _ := runtime.Caller(1)
if fwdPkg.SettleFailFilter.IsFull() != expected {
t.Fatalf("line %d: expected fwdpkg settle/fail filter IsFull to be %v, "+
"found %v", line, expected, fwdPkg.SettleFailFilter.IsFull())
}
}
// loadFwdPkgs is a helper method that reads all forwarding packages for a
// particular packager.
func loadFwdPkgs(t *testing.T, db kvdb.Backend,
packager channeldb.FwdPackager) []*channeldb.FwdPkg {
var fwdPkgs []*channeldb.FwdPkg
if err := kvdb.View(db, func(tx kvdb.RTx) error {
var err error
fwdPkgs, err = packager.LoadFwdPkgs(tx)
return err
}, func() {
fwdPkgs = nil
}); err != nil {
t.Fatalf("unable to load fwd pkgs: %v", err)
}
return fwdPkgs
}
// makeFwdPkgDB initializes a test database for forwarding packages. If the
// provided path is an empty, it will create a temp dir/file to use.
func makeFwdPkgDB(t *testing.T, path string) kvdb.Backend { // nolint:unparam
if path == "" {
var err error
path, err = ioutil.TempDir("", "fwdpkgdb")
if err != nil {
t.Fatalf("unable to create temp path: %v", err)
}
path = filepath.Join(path, "fwdpkg.db")
}
bdb, err := kvdb.Create(
kvdb.BoltBackendName, path, true, kvdb.DefaultDBTimeout,
)
if err != nil {
t.Fatalf("unable to open boltdb: %v", err)
}
return bdb
}