d89f51d1d0
Similarly as with kvdb.View this commits adds a reset closure to the kvdb.Update call in order to be able to reset external state if the underlying db backend needs to retry the transaction.
818 lines
27 KiB
Go
818 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)
|
|
if err != nil {
|
|
t.Fatalf("unable to open boltdb: %v", err)
|
|
}
|
|
|
|
return bdb
|
|
}
|