Merge pull request #4148 from carlaKC/lnwallet-evaluatehtlccoverage

lnwallet/test: add coverage for evaluateHtlcView
This commit is contained in:
Olaoluwa Osuntokun 2020-05-04 17:30:31 -07:00 committed by GitHub
commit 3769f74795
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 1289 additions and 54 deletions

@ -2494,10 +2494,106 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
skipUs := make(map[uint64]struct{})
skipThem := make(map[uint64]struct{})
// fetchParentEntry is a helper method that will fetch the parent of
// entry from the corresponding update log.
fetchParentEntry := func(entry *PaymentDescriptor,
remoteLog bool) (*PaymentDescriptor, error) {
// First we run through non-add entries in both logs, populating the
// skip sets and mutating the current chain state (crediting balances,
// etc) to reflect the settle/timeout entry encountered.
for _, entry := range view.ourUpdates {
switch entry.EntryType {
// Skip adds for now. They will be processed below.
case Add:
continue
// Process fee updates, updating the current feePerKw.
case FeeUpdate:
processFeeUpdate(
entry, nextHeight, remoteChain, mutateState,
newView,
)
continue
}
// If we're settling an inbound HTLC, and it hasn't been
// processed yet, then increment our state tracking the total
// number of satoshis we've received within the channel.
if mutateState && entry.EntryType == Settle && !remoteChain &&
entry.removeCommitHeightLocal == 0 {
lc.channelState.TotalMSatReceived += entry.Amount
}
addEntry, err := lc.fetchParent(entry, remoteChain, true)
if err != nil {
return nil, err
}
skipThem[addEntry.HtlcIndex] = struct{}{}
processRemoveEntry(entry, ourBalance, theirBalance,
nextHeight, remoteChain, true, mutateState)
}
for _, entry := range view.theirUpdates {
switch entry.EntryType {
// Skip adds for now. They will be processed below.
case Add:
continue
// Process fee updates, updating the current feePerKw.
case FeeUpdate:
processFeeUpdate(
entry, nextHeight, remoteChain, mutateState,
newView,
)
continue
}
// If the remote party is settling one of our outbound HTLC's,
// and it hasn't been processed, yet, the increment our state
// tracking the total number of satoshis we've sent within the
// channel.
if mutateState && entry.EntryType == Settle && !remoteChain &&
entry.removeCommitHeightLocal == 0 {
lc.channelState.TotalMSatSent += entry.Amount
}
addEntry, err := lc.fetchParent(entry, remoteChain, false)
if err != nil {
return nil, err
}
skipUs[addEntry.HtlcIndex] = struct{}{}
processRemoveEntry(entry, ourBalance, theirBalance,
nextHeight, remoteChain, false, mutateState)
}
// Next we take a second pass through all the log entries, skipping any
// settled HTLCs, and debiting the chain state balance due to any newly
// added HTLCs.
for _, entry := range view.ourUpdates {
isAdd := entry.EntryType == Add
if _, ok := skipUs[entry.HtlcIndex]; !isAdd || ok {
continue
}
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
remoteChain, false, mutateState)
newView.ourUpdates = append(newView.ourUpdates, entry)
}
for _, entry := range view.theirUpdates {
isAdd := entry.EntryType == Add
if _, ok := skipThem[entry.HtlcIndex]; !isAdd || ok {
continue
}
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
remoteChain, true, mutateState)
newView.theirUpdates = append(newView.theirUpdates, entry)
}
return newView, nil
}
// getFetchParent is a helper that looks up update log parent entries in the
// appropriate log.
func (lc *LightningChannel) fetchParent(entry *PaymentDescriptor,
remoteChain, remoteLog bool) (*PaymentDescriptor, error) {
var (
updateLog *updateLog
@ -2543,102 +2639,6 @@ func (lc *LightningChannel) evaluateHTLCView(view *htlcView, ourBalance,
}
return addEntry, nil
}
// First we run through non-add entries in both logs, populating the
// skip sets and mutating the current chain state (crediting balances,
// etc) to reflect the settle/timeout entry encountered.
for _, entry := range view.ourUpdates {
switch entry.EntryType {
// Skip adds for now. They will be processed below.
case Add:
continue
// Process fee updates, updating the current feePerKw.
case FeeUpdate:
processFeeUpdate(
entry, nextHeight, remoteChain, mutateState,
newView,
)
continue
}
// If we're settling an inbound HTLC, and it hasn't been
// processed yet, then increment our state tracking the total
// number of satoshis we've received within the channel.
if mutateState && entry.EntryType == Settle && !remoteChain &&
entry.removeCommitHeightLocal == 0 {
lc.channelState.TotalMSatReceived += entry.Amount
}
addEntry, err := fetchParentEntry(entry, true)
if err != nil {
return nil, err
}
skipThem[addEntry.HtlcIndex] = struct{}{}
processRemoveEntry(entry, ourBalance, theirBalance,
nextHeight, remoteChain, true, mutateState)
}
for _, entry := range view.theirUpdates {
switch entry.EntryType {
// Skip adds for now. They will be processed below.
case Add:
continue
// Process fee updates, updating the current feePerKw.
case FeeUpdate:
processFeeUpdate(
entry, nextHeight, remoteChain, mutateState,
newView,
)
continue
}
// If the remote party is settling one of our outbound HTLC's,
// and it hasn't been processed, yet, the increment our state
// tracking the total number of satoshis we've sent within the
// channel.
if mutateState && entry.EntryType == Settle && !remoteChain &&
entry.removeCommitHeightLocal == 0 {
lc.channelState.TotalMSatSent += entry.Amount
}
addEntry, err := fetchParentEntry(entry, false)
if err != nil {
return nil, err
}
skipUs[addEntry.HtlcIndex] = struct{}{}
processRemoveEntry(entry, ourBalance, theirBalance,
nextHeight, remoteChain, false, mutateState)
}
// Next we take a second pass through all the log entries, skipping any
// settled HTLCs, and debiting the chain state balance due to any newly
// added HTLCs.
for _, entry := range view.ourUpdates {
isAdd := entry.EntryType == Add
if _, ok := skipUs[entry.HtlcIndex]; !isAdd || ok {
continue
}
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
remoteChain, false, mutateState)
newView.ourUpdates = append(newView.ourUpdates, entry)
}
for _, entry := range view.theirUpdates {
isAdd := entry.EntryType == Add
if _, ok := skipThem[entry.HtlcIndex]; !isAdd || ok {
continue
}
processAddEntry(entry, ourBalance, theirBalance, nextHeight,
remoteChain, true, mutateState)
newView.theirUpdates = append(newView.theirUpdates, entry)
}
return newView, nil
}
// processAddEntry evaluates the effect of an add entry within the HTLC log.

@ -5,7 +5,6 @@ import (
"container/list"
"crypto/sha256"
"fmt"
"reflect"
"runtime"
"testing"
@ -7676,3 +7675,1239 @@ func TestChannelFeeRateFloor(t *testing.T) {
err)
}
}
// TestFetchParent tests lookup of an entry's parent in the appropriate log.
func TestFetchParent(t *testing.T) {
tests := []struct {
name string
remoteChain bool
remoteLog bool
localEntries []*PaymentDescriptor
remoteEntries []*PaymentDescriptor
// parentIndex is the parent index of the entry that we will
// lookup with fetch parent.
parentIndex uint64
// expectErr indicates that we expect fetch parent to fail.
expectErr bool
// expectedIndex is the htlc index that we expect the parent
// to have.
expectedIndex uint64
}{
{
name: "not found in remote log",
localEntries: nil,
remoteEntries: nil,
remoteChain: true,
remoteLog: true,
parentIndex: 0,
expectErr: true,
},
{
name: "not found in local log",
localEntries: nil,
remoteEntries: nil,
remoteChain: false,
remoteLog: false,
parentIndex: 0,
expectErr: true,
},
{
name: "remote log + chain, remote add height 0",
localEntries: nil,
remoteEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 100,
addCommitHeightRemote: 0,
},
},
remoteChain: true,
remoteLog: true,
parentIndex: 1,
expectErr: true,
},
{
name: "remote log, local chain, local add height 0",
remoteEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 0,
addCommitHeightRemote: 100,
},
},
localEntries: nil,
remoteChain: false,
remoteLog: true,
parentIndex: 1,
expectErr: true,
},
{
name: "local log + chain, local add height 0",
localEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 0,
addCommitHeightRemote: 100,
},
},
remoteEntries: nil,
remoteChain: false,
remoteLog: false,
parentIndex: 1,
expectErr: true,
},
{
name: "local log + remote chain, remote add height 0",
localEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 100,
addCommitHeightRemote: 0,
},
},
remoteEntries: nil,
remoteChain: true,
remoteLog: false,
parentIndex: 1,
expectErr: true,
},
{
name: "remote log found",
localEntries: nil,
remoteEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 100,
addCommitHeightRemote: 0,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
},
remoteChain: true,
remoteLog: true,
parentIndex: 1,
expectErr: false,
expectedIndex: 2,
},
{
name: "local log found",
localEntries: []*PaymentDescriptor{
// This entry will be added at log index =0.
{
HtlcIndex: 1,
addCommitHeightLocal: 0,
addCommitHeightRemote: 100,
},
// This entry will be added at log index =1, it
// is the parent entry we are looking for.
{
HtlcIndex: 2,
addCommitHeightLocal: 100,
addCommitHeightRemote: 100,
},
},
remoteEntries: nil,
remoteChain: false,
remoteLog: false,
parentIndex: 1,
expectErr: false,
expectedIndex: 2,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
// Create a lightning channel with newly initialized
// local and remote logs.
lc := LightningChannel{
localUpdateLog: newUpdateLog(0, 0),
remoteUpdateLog: newUpdateLog(0, 0),
}
// Add the local and remote entries to update logs.
for _, entry := range test.localEntries {
lc.localUpdateLog.appendHtlc(entry)
}
for _, entry := range test.remoteEntries {
lc.remoteUpdateLog.appendHtlc(entry)
}
parent, err := lc.fetchParent(
&PaymentDescriptor{
ParentIndex: test.parentIndex,
},
test.remoteChain,
test.remoteLog,
)
gotErr := err != nil
if test.expectErr != gotErr {
t.Fatalf("expected error: %v, got: %v, "+
"error:%v", test.expectErr, gotErr, err)
}
// If our lookup failed, we do not need to check parent
// index.
if err != nil {
return
}
if parent.HtlcIndex != test.expectedIndex {
t.Fatalf("expected parent index: %v, got: %v",
test.parentIndex, parent.HtlcIndex)
}
})
}
}
// TestEvaluateView tests the creation of a htlc view and the opt in mutation of
// send and receive balances. This test does not check htlc mutation on a htlc
// level.
func TestEvaluateView(t *testing.T) {
const (
// addHeight is a non-zero height that is used for htlc adds.
addHeight = 200
// nextHeight is a constant that we use for the next height in
// all unit tests.
nextHeight = 400
// feePerKw is the fee we start all of our unit tests with.
feePerKw = 1
// htlcAddAmount is the amount for htlc adds in tests.
htlcAddAmount = 15
// ourFeeUpdateAmt is an amount that we update fees to
// expressed in msat.
ourFeeUpdateAmt = 20000
// ourFeeUpdatePerSat is the fee rate *in satoshis* that we
// expect if we update to ourFeeUpdateAmt.
ourFeeUpdatePerSat = chainfee.SatPerKWeight(20)
// theirFeeUpdateAmt iis an amount that they update fees to
// expressed in msat.
theirFeeUpdateAmt = 10000
// theirFeeUpdatePerSat is the fee rate *in satoshis* that we
// expect if we update to ourFeeUpdateAmt.
theirFeeUpdatePerSat = chainfee.SatPerKWeight(10)
)
tests := []struct {
name string
ourHtlcs []*PaymentDescriptor
theirHtlcs []*PaymentDescriptor
remoteChain bool
mutateState bool
// ourExpectedHtlcs is the set of our htlcs that we expect in
// the htlc view once it has been evaluated. We just store
// htlc index -> bool for brevity, because we only check the
// presence of the htlc in the returned set.
ourExpectedHtlcs map[uint64]bool
// theirExpectedHtlcs is the set of their htlcs that we expect
// in the htlc view once it has been evaluated. We just store
// htlc index -> bool for brevity, because we only check the
// presence of the htlc in the returned set.
theirExpectedHtlcs map[uint64]bool
// expectedFee is the fee we expect to be set after evaluating
// the htlc view.
expectedFee chainfee.SatPerKWeight
// expectReceived is the amount we expect the channel to have
// tracked as our receive total.
expectReceived lnwire.MilliSatoshi
// expectSent is the amount we expect the channel to have
// tracked as our send total.
expectSent lnwire.MilliSatoshi
}{
{
name: "our fee update is applied",
remoteChain: false,
mutateState: false,
ourHtlcs: []*PaymentDescriptor{
{
Amount: ourFeeUpdateAmt,
EntryType: FeeUpdate,
},
},
theirHtlcs: nil,
expectedFee: ourFeeUpdatePerSat,
ourExpectedHtlcs: nil,
theirExpectedHtlcs: nil,
expectReceived: 0,
expectSent: 0,
},
{
name: "their fee update is applied",
remoteChain: false,
mutateState: false,
ourHtlcs: []*PaymentDescriptor{},
theirHtlcs: []*PaymentDescriptor{
{
Amount: theirFeeUpdateAmt,
EntryType: FeeUpdate,
},
},
expectedFee: theirFeeUpdatePerSat,
ourExpectedHtlcs: nil,
theirExpectedHtlcs: nil,
expectReceived: 0,
expectSent: 0,
},
{
// We expect unresolved htlcs to to remain in the view.
name: "htlcs adds without settles",
remoteChain: false,
mutateState: false,
ourHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
},
theirHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Add,
},
},
expectedFee: feePerKw,
ourExpectedHtlcs: map[uint64]bool{
0: true,
},
theirExpectedHtlcs: map[uint64]bool{
0: true,
1: true,
},
expectReceived: 0,
expectSent: 0,
},
{
name: "our htlc settled, state mutated",
remoteChain: false,
mutateState: true,
ourHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
addCommitHeightLocal: addHeight,
},
},
theirHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Settle,
// Map their htlc settle update to our
// htlc add (0).
ParentIndex: 0,
},
},
expectedFee: feePerKw,
ourExpectedHtlcs: nil,
theirExpectedHtlcs: map[uint64]bool{
0: true,
},
expectReceived: 0,
expectSent: htlcAddAmount,
},
{
name: "our htlc settled, state not mutated",
remoteChain: false,
mutateState: false,
ourHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
addCommitHeightLocal: addHeight,
},
},
theirHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Settle,
// Map their htlc settle update to our
// htlc add (0).
ParentIndex: 0,
},
},
expectedFee: feePerKw,
ourExpectedHtlcs: nil,
theirExpectedHtlcs: map[uint64]bool{
0: true,
},
expectReceived: 0,
expectSent: 0,
},
{
name: "their htlc settled, state mutated",
remoteChain: false,
mutateState: true,
ourHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Settle,
// Map our htlc settle update to their
// htlc add (1).
ParentIndex: 1,
},
},
theirHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
addCommitHeightLocal: addHeight,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Add,
addCommitHeightLocal: addHeight,
},
},
expectedFee: feePerKw,
ourExpectedHtlcs: map[uint64]bool{
0: true,
},
theirExpectedHtlcs: map[uint64]bool{
0: true,
},
expectReceived: htlcAddAmount,
expectSent: 0,
},
{
name: "their htlc settled, state not mutated",
remoteChain: false,
mutateState: false,
ourHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
},
{
HtlcIndex: 1,
Amount: htlcAddAmount,
EntryType: Settle,
// Map our htlc settle update to their
// htlc add (0).
ParentIndex: 0,
},
},
theirHtlcs: []*PaymentDescriptor{
{
HtlcIndex: 0,
Amount: htlcAddAmount,
EntryType: Add,
addCommitHeightLocal: addHeight,
},
},
expectedFee: feePerKw,
ourExpectedHtlcs: map[uint64]bool{
0: true,
},
theirExpectedHtlcs: nil,
expectReceived: 0,
expectSent: 0,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
lc := LightningChannel{
channelState: &channeldb.OpenChannel{
TotalMSatSent: 0,
TotalMSatReceived: 0,
},
// Create update logs for local and remote.
localUpdateLog: newUpdateLog(0, 0),
remoteUpdateLog: newUpdateLog(0, 0),
}
for _, htlc := range test.ourHtlcs {
if htlc.EntryType == Add {
lc.localUpdateLog.appendHtlc(htlc)
} else {
lc.localUpdateLog.appendUpdate(htlc)
}
}
for _, htlc := range test.theirHtlcs {
if htlc.EntryType == Add {
lc.remoteUpdateLog.appendHtlc(htlc)
} else {
lc.remoteUpdateLog.appendUpdate(htlc)
}
}
view := &htlcView{
ourUpdates: test.ourHtlcs,
theirUpdates: test.theirHtlcs,
feePerKw: feePerKw,
}
var (
// Create vars to store balance changes. We do
// not check these values in this test because
// balance modification happens on the htlc
// processing level.
ourBalance lnwire.MilliSatoshi
theirBalance lnwire.MilliSatoshi
)
// Evaluate the htlc view, mutate as test expects.
result, err := lc.evaluateHTLCView(
view, &ourBalance, &theirBalance, nextHeight,
test.remoteChain, test.mutateState,
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result.feePerKw != test.expectedFee {
t.Fatalf("expected fee: %v, got: %v",
test.expectedFee, result.feePerKw)
}
checkExpectedHtlcs(
t, result.ourUpdates, test.ourExpectedHtlcs,
)
checkExpectedHtlcs(
t, result.theirUpdates, test.theirExpectedHtlcs,
)
if lc.channelState.TotalMSatSent != test.expectSent {
t.Fatalf("expected sent: %v, got: %v",
test.expectSent,
lc.channelState.TotalMSatSent)
}
if lc.channelState.TotalMSatReceived !=
test.expectReceived {
t.Fatalf("expected received: %v, got: %v",
test.expectReceived,
lc.channelState.TotalMSatReceived)
}
})
}
}
// checkExpectedHtlcs checks that a set of htlcs that we have contains all the
// htlcs we expect.
func checkExpectedHtlcs(t *testing.T, actual []*PaymentDescriptor,
expected map[uint64]bool) {
if len(expected) != len(actual) {
t.Fatalf("expected: %v htlcs, got: %v",
len(expected), len(actual))
}
for _, htlc := range actual {
_, ok := expected[htlc.HtlcIndex]
if !ok {
t.Fatalf("htlc with index: %v not "+
"expected in set", htlc.HtlcIndex)
}
}
}
// heights represents the heights on a payment descriptor.
type heights struct {
localAdd uint64
localRemove uint64
remoteAdd uint64
remoteRemove uint64
}
// TestProcessFeeUpdate tests the applying of fee updates and mutation of
// local and remote add and remove heights on update messages.
func TestProcessFeeUpdate(t *testing.T) {
const (
// height is a non-zero height that can be used for htlcs
// heights.
height = 200
// nextHeight is a constant that we use for the next height in
// all unit tests.
nextHeight = 400
// feePerKw is the fee we start all of our unit tests with.
feePerKw = 1
// ourFeeUpdateAmt is an amount that we update fees to expressed
// in msat.
ourFeeUpdateAmt = 20000
// ourFeeUpdatePerSat is the fee rate *in satoshis* that we
// expect if we update to ourFeeUpdateAmt.
ourFeeUpdatePerSat = chainfee.SatPerKWeight(20)
)
tests := []struct {
name string
startHeights heights
expectedHeights heights
remoteChain bool
mutate bool
expectedFee chainfee.SatPerKWeight
}{
{
// Looking at local chain, local add is non-zero so
// the update has been applied already; no fee change.
name: "non-zero local height, fee unchanged",
startHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: height,
},
expectedHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: height,
},
remoteChain: false,
mutate: false,
expectedFee: feePerKw,
},
{
// Looking at local chain, local add is zero so the
// update has not been applied yet; we expect a fee
// update.
name: "zero local height, fee changed",
startHeights: heights{
localAdd: 0,
localRemove: 0,
remoteAdd: height,
remoteRemove: 0,
},
expectedHeights: heights{
localAdd: 0,
localRemove: 0,
remoteAdd: height,
remoteRemove: 0,
},
remoteChain: false,
mutate: false,
expectedFee: ourFeeUpdatePerSat,
},
{
// Looking at remote chain, the remote add height is
// zero, so the update has not been applied so we expect
// a fee change.
name: "zero remote height, fee changed",
startHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: 0,
},
expectedHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: 0,
},
remoteChain: true,
mutate: false,
expectedFee: ourFeeUpdatePerSat,
},
{
// Looking at remote chain, the remote add height is
// non-zero, so the update has been applied so we expect
// no fee change.
name: "non-zero remote height, no fee change",
startHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: height,
remoteRemove: 0,
},
expectedHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: height,
remoteRemove: 0,
},
remoteChain: true,
mutate: false,
expectedFee: feePerKw,
},
{
// Local add height is non-zero, so the update has
// already been applied; we do not expect fee to
// change or any mutations to be applied.
name: "non-zero local height, mutation not applied",
startHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: height,
},
expectedHeights: heights{
localAdd: height,
localRemove: 0,
remoteAdd: 0,
remoteRemove: height,
},
remoteChain: false,
mutate: true,
expectedFee: feePerKw,
},
{
// Local add is zero and we are looking at our local
// chain, so the update has not been applied yet. We
// expect the local add and remote heights to be
// mutated.
name: "zero height, fee changed, mutation applied",
startHeights: heights{
localAdd: 0,
localRemove: 0,
remoteAdd: 0,
remoteRemove: 0,
},
expectedHeights: heights{
localAdd: nextHeight,
localRemove: nextHeight,
remoteAdd: 0,
remoteRemove: 0,
},
remoteChain: false,
mutate: true,
expectedFee: ourFeeUpdatePerSat,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
// Create a fee update with add and remove heights as
// set in the test.
heights := test.startHeights
update := &PaymentDescriptor{
Amount: ourFeeUpdateAmt,
addCommitHeightRemote: heights.remoteAdd,
addCommitHeightLocal: heights.localAdd,
removeCommitHeightRemote: heights.remoteRemove,
removeCommitHeightLocal: heights.localRemove,
EntryType: FeeUpdate,
}
view := &htlcView{
feePerKw: chainfee.SatPerKWeight(feePerKw),
}
processFeeUpdate(
update, nextHeight, test.remoteChain,
test.mutate, view,
)
if view.feePerKw != test.expectedFee {
t.Fatalf("expected fee: %v, got: %v",
test.expectedFee, feePerKw)
}
checkHeights(t, update, test.expectedHeights)
})
}
}
func checkHeights(t *testing.T, update *PaymentDescriptor, expected heights) {
updateHeights := heights{
localAdd: update.addCommitHeightLocal,
localRemove: update.removeCommitHeightLocal,
remoteAdd: update.addCommitHeightRemote,
remoteRemove: update.removeCommitHeightRemote,
}
if !reflect.DeepEqual(updateHeights, expected) {
t.Fatalf("expected: %v, got: %v", expected, updateHeights)
}
}
// TestProcessAddRemoveEntry tests the updating of our and their balances when
// we process adds, settles and fails. It also tests the mutating of add and
// remove heights.
func TestProcessAddRemoveEntry(t *testing.T) {
const (
// addHeight is a non-zero addHeight that is used for htlc
// add heights.
addHeight = 100
// removeHeight is a non-zero removeHeight that is used for
// htlc remove heights.
removeHeight = 200
// nextHeight is a constant that we use for the nextHeight in
// all unit tests.
nextHeight = 400
// updateAmount is the amount that the update is set to.
updateAmount = lnwire.MilliSatoshi(10)
// startBalance is a balance we start both sides out with
// so that balances can be incremented.
startBalance = lnwire.MilliSatoshi(100)
)
tests := []struct {
name string
startHeights heights
remoteChain bool
isIncoming bool
mutateState bool
ourExpectedBalance lnwire.MilliSatoshi
theirExpectedBalance lnwire.MilliSatoshi
expectedHeights heights
updateType updateType
}{
{
name: "add, remote chain, already processed",
startHeights: heights{
localAdd: 0,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: 0,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "add, local chain, already processed",
startHeights: heights{
localAdd: addHeight,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: false,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "incoming add, local chain, not mutated",
startHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: false,
isIncoming: true,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance - updateAmount,
expectedHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "incoming add, local chain, mutated",
startHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: false,
isIncoming: true,
mutateState: true,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance - updateAmount,
expectedHeights: heights{
localAdd: nextHeight,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "outgoing add, remote chain, not mutated",
startHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance - updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "outgoing add, remote chain, mutated",
startHeights: heights{
localAdd: 0,
remoteAdd: 0,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: false,
mutateState: true,
ourExpectedBalance: startBalance - updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: 0,
remoteAdd: nextHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Add,
},
{
name: "settle, remote chain, already processed",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: removeHeight,
},
remoteChain: true,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: removeHeight,
},
updateType: Settle,
},
{
name: "settle, local chain, already processed",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: removeHeight,
remoteRemove: 0,
},
remoteChain: false,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: removeHeight,
remoteRemove: 0,
},
updateType: Settle,
},
{
// Remote chain, and not processed yet. Incoming settle,
// so we expect our balance to increase.
name: "incoming settle",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: true,
mutateState: false,
ourExpectedBalance: startBalance + updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Settle,
},
{
// Remote chain, and not processed yet. Incoming settle,
// so we expect our balance to increase.
name: "outgoing settle",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance + updateAmount,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Settle,
},
{
// Remote chain, and not processed yet. Incoming fail,
// so we expect their balance to increase.
name: "incoming fail",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: true,
mutateState: false,
ourExpectedBalance: startBalance,
theirExpectedBalance: startBalance + updateAmount,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Fail,
},
{
// Remote chain, and not processed yet. Outgoing fail,
// so we expect our balance to increase.
name: "outgoing fail",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: false,
mutateState: false,
ourExpectedBalance: startBalance + updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
updateType: Fail,
},
{
// Local chain, and not processed yet. Incoming settle,
// so we expect our balance to increase. Mutate is
// true, so we expect our remove removeHeight to have
// changed.
name: "fail, our remove height mutated",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: false,
isIncoming: true,
mutateState: true,
ourExpectedBalance: startBalance + updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: nextHeight,
remoteRemove: 0,
},
updateType: Settle,
},
{
// Remote chain, and not processed yet. Incoming settle,
// so we expect our balance to increase. Mutate is
// true, so we expect their remove removeHeight to have
// changed.
name: "fail, their remove height mutated",
startHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: 0,
},
remoteChain: true,
isIncoming: true,
mutateState: true,
ourExpectedBalance: startBalance + updateAmount,
theirExpectedBalance: startBalance,
expectedHeights: heights{
localAdd: addHeight,
remoteAdd: addHeight,
localRemove: 0,
remoteRemove: nextHeight,
},
updateType: Settle,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
heights := test.startHeights
update := &PaymentDescriptor{
Amount: updateAmount,
addCommitHeightLocal: heights.localAdd,
addCommitHeightRemote: heights.remoteAdd,
removeCommitHeightLocal: heights.localRemove,
removeCommitHeightRemote: heights.remoteRemove,
EntryType: test.updateType,
}
var (
// Start both parties off with an initial
// balance. Copy by value here so that we do
// not mutate the startBalance constant.
ourBalance, theirBalance = startBalance,
startBalance
)
// Choose the processing function we need based on the
// update type. Process remove is used for settles,
// fails and malformed htlcs.
process := processRemoveEntry
if test.updateType == Add {
process = processAddEntry
}
process(
update, &ourBalance, &theirBalance, nextHeight,
test.remoteChain, test.isIncoming,
test.mutateState,
)
// Check that balances were updated as expected.
if ourBalance != test.ourExpectedBalance {
t.Fatalf("expected our balance: %v, got: %v",
test.ourExpectedBalance, ourBalance)
}
if theirBalance != test.theirExpectedBalance {
t.Fatalf("expected their balance: %v, got: %v",
test.theirExpectedBalance, theirBalance)
}
// Check that heights on the update are as expected.
checkHeights(t, update, test.expectedHeights)
})
}
}