package htlcswitch import ( "bytes" "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "io" "net" "reflect" "runtime" "sync" "testing" "time" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/htlcswitch/hodl" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/ticker" "github.com/stretchr/testify/require" ) const ( testStartingHeight = 100 testDefaultDelta = 6 ) // concurrentTester is a thread-safe wrapper around the Fatalf method of a // *testing.T instance. With this wrapper multiple goroutines can safely // attempt to fail a test concurrently. type concurrentTester struct { mtx sync.Mutex *testing.T } func newConcurrentTester(t *testing.T) *concurrentTester { return &concurrentTester{ T: t, } } func (c *concurrentTester) Fatalf(format string, args ...interface{}) { c.T.Helper() c.mtx.Lock() defer c.mtx.Unlock() c.T.Fatalf(format, args...) } // messageToString is used to produce less spammy log messages in trace mode by // setting the 'Curve" parameter to nil. Doing this avoids printing out each of // the field elements in the curve parameters for secp256k1. func messageToString(msg lnwire.Message) string { switch m := msg.(type) { case *lnwire.RevokeAndAck: m.NextRevocationKey.Curve = nil case *lnwire.AcceptChannel: m.FundingKey.Curve = nil m.RevocationPoint.Curve = nil m.PaymentPoint.Curve = nil m.DelayedPaymentPoint.Curve = nil m.FirstCommitmentPoint.Curve = nil case *lnwire.OpenChannel: m.FundingKey.Curve = nil m.RevocationPoint.Curve = nil m.PaymentPoint.Curve = nil m.DelayedPaymentPoint.Curve = nil m.FirstCommitmentPoint.Curve = nil case *lnwire.FundingLocked: m.NextPerCommitmentPoint.Curve = nil } return spew.Sdump(msg) } // expectedMessage struct holds the message which travels from one peer to // another, and additional information like, should this message we skipped for // handling. type expectedMessage struct { from string to string message lnwire.Message skip bool } // createLogFunc is a helper function which returns the function which will be // used for logging message are received from another peer. func createLogFunc(name string, channelID lnwire.ChannelID) messageInterceptor { return func(m lnwire.Message) (bool, error) { chanID, err := getChanID(m) if err != nil { return false, err } if chanID == channelID { fmt.Printf("---------------------- \n %v received: "+ "%v", name, messageToString(m)) } return false, nil } } // createInterceptorFunc creates the function by the given set of messages // which, checks the order of the messages and skip the ones which were // indicated to be intercepted. func createInterceptorFunc(prefix, receiver string, messages []expectedMessage, chanID lnwire.ChannelID, debug bool) messageInterceptor { // Filter message which should be received with given peer name. var expectToReceive []expectedMessage for _, message := range messages { if message.to == receiver { expectToReceive = append(expectToReceive, message) } } // Return function which checks the message order and skip the // messages. return func(m lnwire.Message) (bool, error) { messageChanID, err := getChanID(m) if err != nil { return false, err } if messageChanID == chanID { if len(expectToReceive) == 0 { return false, errors.Errorf("%v received "+ "unexpected message out of range: %v", receiver, m.MsgType()) } expectedMessage := expectToReceive[0] expectToReceive = expectToReceive[1:] if expectedMessage.message.MsgType() != m.MsgType() { return false, errors.Errorf("%v received wrong message: \n"+ "real: %v\nexpected: %v", receiver, m.MsgType(), expectedMessage.message.MsgType()) } if debug { var postfix string if revocation, ok := m.(*lnwire.RevokeAndAck); ok { var zeroHash chainhash.Hash if bytes.Equal(zeroHash[:], revocation.Revocation[:]) { postfix = "- empty revocation" } } if expectedMessage.skip { fmt.Printf("skipped: %v: %v %v \n", prefix, m.MsgType(), postfix) } else { fmt.Printf("%v: %v %v \n", prefix, m.MsgType(), postfix) } } return expectedMessage.skip, nil } return false, nil } } // TestChannelLinkRevThenSig tests that if a link owes both a revocation and a // signature to the counterparty (in this order), that they are sent as rev and // then sig. // // Specifically, this tests the following scenario: // // A B // <----add----- // -----add----> // <----sig----- // -----rev----x // -----sig----x func TestChannelLinkRevThenSig(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, chanReserve) require.NoError(t, err) defer cleanUp() err = start() require.NoError(t, err) defer aliceLink.Stop() alice := newPersistentLinkHarness( t, aliceLink, batchTicker, restore, ) var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } bobHtlc1 := generateHtlc(t, coreLink, 0) // <-----add----- // Send an htlc from Bob to Alice. ctx.sendHtlcBobToAlice(bobHtlc1) aliceHtlc1, _ := generateHtlcAndInvoice(t, 0) // ------add----> ctx.sendHtlcAliceToBob(0, aliceHtlc1) ctx.receiveHtlcAliceToBob() // <-----sig----- ctx.sendCommitSigBobToAlice(1) // ------rev----x var msg lnwire.Message select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } _, ok := msg.(*lnwire.RevokeAndAck) require.True(t, ok) // ------sig----x // Trigger a commitsig from Alice->Bob. select { case batchTicker <- time.Now(): case <-time.After(5 * time.Second): t.Fatalf("could not force commit sig") } select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } comSig, ok := msg.(*lnwire.CommitSig) require.True(t, ok) if len(comSig.HtlcSigs) != 2 { t.Fatalf("expected 2 htlc sigs, got %d", len(comSig.HtlcSigs)) } // Restart Alice so she sends and accepts ChannelReestablish. cleanUp = alice.restart(false, true) defer cleanUp() ctx.aliceLink = alice.link ctx.aliceMsgs = alice.msgs // Restart Bob as well by calling NewLightningChannel. bobSigner := bobChannel.Signer bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner) bobChannel, err = lnwallet.NewLightningChannel( bobSigner, bobChannel.State(), bobPool, ) require.NoError(t, err) err = bobPool.Start() require.NoError(t, err) ctx.bobChannel = bobChannel // --reestablish-> select { case msg = <-ctx.aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } _, ok = msg.(*lnwire.ChannelReestablish) require.True(t, ok) // <-reestablish-- bobReest, err := bobChannel.State().ChanSyncMsg() require.NoError(t, err) ctx.aliceLink.HandleChannelUpdate(bobReest) // ------rev----> ctx.receiveRevAndAckAliceToBob() // ------add----> ctx.receiveHtlcAliceToBob() // ------sig----> ctx.receiveCommitSigAliceToBob(2) } // TestChannelLinkSigThenRev tests that if a link owes both a signature and a // revocation to the counterparty (in this order), that they are sent as sig // and then rev. // // Specifically, this tests the following scenario: // // A B // <----add----- // -----add----> // -----sig----x // <----sig----- // -----rev----x func TestChannelLinkSigThenRev(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, chanReserve) require.NoError(t, err) defer cleanUp() err = start() require.NoError(t, err) defer aliceLink.Stop() alice := newPersistentLinkHarness( t, aliceLink, batchTicker, restore, ) var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } bobHtlc1 := generateHtlc(t, coreLink, 0) // <-----add----- // Send an htlc from Bob to Alice. ctx.sendHtlcBobToAlice(bobHtlc1) aliceHtlc1, _ := generateHtlcAndInvoice(t, 0) // ------add----> ctx.sendHtlcAliceToBob(0, aliceHtlc1) ctx.receiveHtlcAliceToBob() // ------sig----x // Trigger a commitsig from Alice->Bob. select { case batchTicker <- time.Now(): case <-time.After(5 * time.Second): t.Fatalf("could not force commit sig") } var msg lnwire.Message select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } comSig, ok := msg.(*lnwire.CommitSig) require.True(t, ok) if len(comSig.HtlcSigs) != 1 { t.Fatalf("expected 1 htlc sig, got %d", len(comSig.HtlcSigs)) } // <-----sig----- ctx.sendCommitSigBobToAlice(1) // ------rev----x select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } _, ok = msg.(*lnwire.RevokeAndAck) require.True(t, ok) // Restart Alice so she sends and accepts ChannelReestablish. cleanUp = alice.restart(false, true) defer cleanUp() ctx.aliceLink = alice.link ctx.aliceMsgs = alice.msgs // Restart Bob as well by calling NewLightningChannel. bobSigner := bobChannel.Signer bobPool := lnwallet.NewSigPool(runtime.NumCPU(), bobSigner) bobChannel, err = lnwallet.NewLightningChannel( bobSigner, bobChannel.State(), bobPool, ) require.NoError(t, err) err = bobPool.Start() require.NoError(t, err) ctx.bobChannel = bobChannel // --reestablish-> select { case msg = <-ctx.aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } _, ok = msg.(*lnwire.ChannelReestablish) require.True(t, ok) // <-reestablish-- bobReest, err := bobChannel.State().ChanSyncMsg() require.NoError(t, err) ctx.aliceLink.HandleChannelUpdate(bobReest) // ------add----> ctx.receiveHtlcAliceToBob() // ------sig----> ctx.receiveCommitSigAliceToBob(1) // ------rev----> ctx.receiveRevAndAckAliceToBob() } // TestChannelLinkSingleHopPayment in this test we checks the interaction // between Alice and Bob within scope of one channel. func TestChannelLinkSingleHopPayment(t *testing.T) { t.Parallel() // Setup a alice-bob network. alice, bob, cleanUp, err := createTwoClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newTwoHopNetwork( t, alice.channel, bob.channel, testStartingHeight, ) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() bobBandwidthBefore := n.bobChannelLink.Bandwidth() debug := false if debug { // Log message that alice receives. n.aliceServer.intersect(createLogFunc("alice", n.aliceChannelLink.ChanID())) // Log message that bob receives. n.bobServer.intersect(createLogFunc("bob", n.bobChannelLink.ChanID())) } amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.bobChannelLink) // Wait for: // * HTLC add request to be sent to bob. // * alice<->bob commitment state to be updated. // * settle request to be sent back from bob to alice. // * alice<->bob commitment state to be updated. // * user notification to be sent. receiver := n.bobServer firstHop := n.bobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, receiver, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to make the payment: %v", err) } // Wait for Alice to receive the revocation. // // TODO(roasbeef); replace with select over returned err chan time.Sleep(2 * time.Second) // Check that alice invoice was settled and bandwidth of HTLC // links was changed. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State != channeldb.ContractSettled { t.Fatal("alice invoice wasn't settled") } if aliceBandwidthBefore-amount != n.aliceChannelLink.Bandwidth() { t.Fatal("alice bandwidth should have decrease on payment " + "amount") } if bobBandwidthBefore+amount != n.bobChannelLink.Bandwidth() { t.Fatalf("bob bandwidth isn't match: expected %v, got %v", bobBandwidthBefore+amount, n.bobChannelLink.Bandwidth()) } } // TestChannelLinkMultiHopPayment checks the ability to send payment over two // hops. In this test we send the payment from Carol to Alice over Bob peer. // (Carol -> Bob -> Alice) and checking that HTLC was settled properly and // balances were changed in two channels. // // The test is executed with two different OutgoingCltvRejectDelta values for // bob. In addition to a normal positive value, we also test the zero case // because this is currently the configured value in lnd // (defaultOutgoingCltvRejectDelta). func TestChannelLinkMultiHopPayment(t *testing.T) { t.Run( "bobOutgoingCltvRejectDelta 3", func(t *testing.T) { testChannelLinkMultiHopPayment(t, 3) }, ) t.Run( "bobOutgoingCltvRejectDelta 0", func(t *testing.T) { testChannelLinkMultiHopPayment(t, 0) }, ) } func testChannelLinkMultiHopPayment(t *testing.T, bobOutgoingCltvRejectDelta uint32) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) n.firstBobChannelLink.cfg.OutgoingCltvRejectDelta = bobOutgoingCltvRejectDelta n.secondBobChannelLink.cfg.OutgoingCltvRejectDelta = bobOutgoingCltvRejectDelta if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() debug := false if debug { // Log messages that alice receives from bob. n.aliceServer.intersect(createLogFunc("[alice]<-bob<-carol: ", n.aliceChannelLink.ChanID())) // Log messages that bob receives from alice. n.bobServer.intersect(createLogFunc("alice->[bob]->carol: ", n.firstBobChannelLink.ChanID())) // Log messages that bob receives from carol. n.bobServer.intersect(createLogFunc("alice<-[bob]<-carol: ", n.secondBobChannelLink.ChanID())) // Log messages that carol receives from bob. n.carolServer.intersect(createLogFunc("alice->bob->[carol]", n.carolChannelLink.ChanID())) } amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // Wait for: // * HTLC add request to be sent from Alice to Bob. // * Alice<->Bob commitment states to be updated. // * HTLC add request to be propagated to Carol. // * Bob<->Carol commitment state to be updated. // * settle request to be sent back from Carol to Bob. // * Alice<->Bob commitment state to be updated. // * settle request to be sent back from Bob to Alice. // * Alice<->Bob commitment states to be updated. // * user notification to be sent. receiver := n.carolServer firstHop := n.firstBobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to send payment: %v", err) } // Wait for Alice and Bob's second link to receive the revocation. time.Sleep(2 * time.Second) // Check that Carol invoice was settled and bandwidth of HTLC // links were changed. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State != channeldb.ContractSettled { t.Fatal("carol invoice haven't been settled") } expectedAliceBandwidth := aliceBandwidthBefore - htlcAmt if expectedAliceBandwidth != n.aliceChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedAliceBandwidth, n.aliceChannelLink.Bandwidth()) } expectedBobBandwidth1 := firstBobBandwidthBefore + htlcAmt if expectedBobBandwidth1 != n.firstBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth()) } expectedBobBandwidth2 := secondBobBandwidthBefore - amount if expectedBobBandwidth2 != n.secondBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth()) } expectedCarolBandwidth := carolBandwidthBefore + amount if expectedCarolBandwidth != n.carolChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedCarolBandwidth, n.carolChannelLink.Bandwidth()) } } // TestChannelLinkCancelFullCommitment tests the ability for links to cancel // forwarded HTLCs once all of their commitment slots are full. func TestChannelLinkCancelFullCommitment(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newTwoHopNetwork( t, channels.aliceToBob, channels.bobToAlice, testStartingHeight, ) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // Fill up the commitment from Alice's side with 20 sat payments. count := (input.MaxHTLCNumber / 2) amt := lnwire.NewMSatFromSatoshis(20000) htlcAmt, totalTimelock, hopsForwards := generateHops(amt, testStartingHeight, n.bobChannelLink) firstHop := n.aliceChannelLink.ShortChanID() // Create channels to buffer the preimage and error channels used in // making the preliminary payments. preimages := make([]lntypes.Preimage, count) aliceErrChan := make(chan chan error, count) var wg sync.WaitGroup for i := 0; i < count; i++ { // Deterministically generate preimages. Avoid the all-zeroes // preimage because that will be rejected by the database. preimages[i] = lntypes.Preimage{byte(i >> 8), byte(i), 1} wg.Add(1) go func(i int) { defer wg.Done() errChan := n.makeHoldPayment( n.aliceServer, n.bobServer, firstHop, hopsForwards, amt, htlcAmt, totalTimelock, preimages[i], ) aliceErrChan <- errChan }(i) } // Wait for Alice to finish filling her commitment. wg.Wait() close(aliceErrChan) // Now make an additional payment from Alice to Bob, this should be // canceled because the commitment in this direction is full. err = <-makePayment( n.aliceServer, n.bobServer, firstHop, hopsForwards, amt, htlcAmt, totalTimelock, ).err if err == nil { t.Fatalf("overflow payment should have failed") } lerr, ok := err.(*LinkError) if !ok { t.Fatalf("expected LinkError, got: %T", err) } msg := lerr.WireMessage() if _, ok := msg.(*lnwire.FailTemporaryChannelFailure); !ok { t.Fatalf("expected TemporaryChannelFailure, got: %T", msg) } // Now, settle all htlcs held by bob and clear the commitment of htlcs. for _, preimage := range preimages { preimage := preimage // It's possible that the HTLCs have not been delivered to the // invoice registry at this point, so we poll until we are able // to settle. err = wait.NoError(func() error { return n.bobServer.registry.SettleHodlInvoice(preimage) }, time.Minute) if err != nil { t.Fatal(err) } } // Ensure that all of the payments sent by alice eventually succeed. for errChan := range aliceErrChan { err := <-errChan if err != nil { t.Fatalf("alice payment failed: %v", err) } } } // TestExitNodeTimelockPayloadMismatch tests that when an exit node receives an // incoming HTLC, if the time lock encoded in the payload of the forwarded HTLC // doesn't match the expected payment value, then the HTLC will be rejected // with the appropriate error. func TestExitNodeTimelockPayloadMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() const amount = btcutil.SatoshiPerBitcoin htlcAmt, htlcExpiry, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink) // In order to exercise this case, we'll now _manually_ modify the // per-hop payload for outgoing time lock to be the incorrect value. // The proper value of the outgoing CLTV should be the policy set by // the receiving node, instead we set it to be a random value. hops[0].FwdInfo.OutgoingCTLV = 500 firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err == nil { t.Fatalf("payment should have failed but didn't") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailFinalIncorrectCltvExpiry: default: t.Fatalf("incorrect error, expected incorrect cltv expiry, "+ "instead have: %v", err) } } // TestExitNodeAmountPayloadMismatch tests that when an exit node receives an // incoming HTLC, if the amount encoded in the onion payload of the forwarded // HTLC doesn't match the expected payment value, then the HTLC will be // rejected. func TestExitNodeAmountPayloadMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() const amount = btcutil.SatoshiPerBitcoin htlcAmt, htlcExpiry, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink) // In order to exercise this case, we'll now _manually_ modify the // per-hop payload for amount to be the incorrect value. The proper // value of the amount to forward should be the amount that the // receiving node expects to receive. hops[0].FwdInfo.AmountToForward = 1 firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err == nil { t.Fatalf("payment should have failed but didn't") } assertFailureCode(t, err, lnwire.CodeFinalIncorrectHtlcAmount) } // TestLinkForwardTimelockPolicyMismatch tests that if a node is an // intermediate node in a multi-hop payment, and receives an HTLC which // violates its specified multi-hop policy, then the HTLC is rejected. func TestLinkForwardTimelockPolicyMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // We'll be sending 1 BTC over a 2-hop (3 vertex) route. amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) // Generate the route over two hops, ignoring the total time lock that // we'll need to use for the first HTLC in order to have a sufficient // time-lock value to account for the decrements over the entire route. htlcAmt, htlcExpiry, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) htlcExpiry -= 2 // Next, we'll make the payment which'll send an HTLC with our // specified parameters to the first hop in the route. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) // We should get an error, and that error should indicate that the HTLC // should be rejected due to a policy violation. if err == nil { t.Fatalf("payment should have failed but didn't") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailIncorrectCltvExpiry: default: t.Fatalf("incorrect error, expected incorrect cltv expiry, "+ "instead have: %v", err) } } // TestLinkForwardFeePolicyMismatch tests that if a node is an intermediate // node in a multi-hop payment and receives an HTLC that violates its current // fee policy, then the HTLC is rejected with the proper error. func TestLinkForwardFeePolicyMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // We'll be sending 1 BTC over a 2-hop (3 vertex) route. Given the // current default fee of 1 SAT, if we just send a single BTC over in // an HTLC, it should be rejected. amountNoFee := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) // Generate the route over two hops, ignoring the amount we _should_ // actually send in order to be able to cover fees. _, htlcExpiry, hops := generateHops(amountNoFee, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // Next, we'll make the payment which'll send an HTLC with our // specified parameters to the first hop in the route. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amountNoFee, amountNoFee, htlcExpiry, ).Wait(30 * time.Second) // We should get an error, and that error should indicate that the HTLC // should be rejected due to a policy violation. if err == nil { t.Fatalf("payment should have failed but didn't") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailFeeInsufficient: default: t.Fatalf("incorrect error, expected fee insufficient, "+ "instead have: %T", err) } } // TestLinkForwardFeePolicyMismatch tests that if a node is an intermediate // node and receives an HTLC which is _below_ its min HTLC policy, then the // HTLC will be rejected. func TestLinkForwardMinHTLCPolicyMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // The current default global min HTLC policy set in the default config // for the three-hop-network is 5 SAT. So in order to trigger this // failure mode, we'll create an HTLC with 1 satoshi. amountNoFee := lnwire.NewMSatFromSatoshis(1) // With the amount set, we'll generate a route over 2 hops within the // network that attempts to pay out our specified amount. htlcAmt, htlcExpiry, hops := generateHops(amountNoFee, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // Next, we'll make the payment which'll send an HTLC with our // specified parameters to the first hop in the route. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) // We should get an error, and that error should indicate that the HTLC // should be rejected due to a policy violation (below min HTLC). if err == nil { t.Fatalf("payment should have failed but didn't") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailAmountBelowMinimum: default: t.Fatalf("incorrect error, expected amount below minimum, "+ "instead have: %v", err) } } // TestLinkForwardMaxHTLCPolicyMismatch tests that if a node is an intermediate // node and receives an HTLC which is _above_ its max HTLC policy then the // HTLC will be rejected. func TestLinkForwardMaxHTLCPolicyMismatch(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5, ) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork( t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight, ) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // In order to trigger this failure mode, we'll update our policy to have // a new max HTLC of 10 satoshis. maxHtlc := lnwire.NewMSatFromSatoshis(10) // First we'll generate a route over 2 hops within the network that // attempts to pay out an amount greater than the max HTLC we're about to // set. amountNoFee := maxHtlc + 1 htlcAmt, htlcExpiry, hops := generateHops( amountNoFee, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink, ) // We'll now update Bob's policy to set the max HTLC we chose earlier. n.secondBobChannelLink.cfg.FwrdingPolicy.MaxHTLC = maxHtlc // Finally, we'll make the payment which'll send an HTLC with our // specified parameters. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) // We should get an error indicating a temporary channel failure, The // failure is temporary because this payment would be allowed if Bob // updated his policy to increase the max HTLC. if err == nil { t.Fatalf("payment should have failed but didn't") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailTemporaryChannelFailure: default: t.Fatalf("incorrect error, expected temporary channel failure, "+ "instead have: %v", err) } } // TestUpdateForwardingPolicy tests that the forwarding policy for a link is // able to be updated properly. We'll first create an HTLC that meets the // specified policy, assert that it succeeds, update the policy (to invalidate // the prior HTLC), and then ensure that the HTLC is rejected. func TestUpdateForwardingPolicy(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() amountNoFee := lnwire.NewMSatFromSatoshis(10) htlcAmt, htlcExpiry, hops := generateHops(amountNoFee, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // First, send this 10 mSAT payment over the three hops, the payment // should succeed, and all balances should be updated accordingly. firstHop := n.firstBobChannelLink.ShortChanID() payResp, err := makePayment( n.aliceServer, n.carolServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to send payment: %v", err) } // Carol's invoice should now be shown as settled as the payment // succeeded. invoice, err := n.carolServer.registry.LookupInvoice(payResp) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State != channeldb.ContractSettled { t.Fatal("carol invoice haven't been settled") } expectedAliceBandwidth := aliceBandwidthBefore - htlcAmt if expectedAliceBandwidth != n.aliceChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedAliceBandwidth, n.aliceChannelLink.Bandwidth()) } expectedBobBandwidth1 := firstBobBandwidthBefore + htlcAmt if expectedBobBandwidth1 != n.firstBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth()) } expectedBobBandwidth2 := secondBobBandwidthBefore - amountNoFee if expectedBobBandwidth2 != n.secondBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth()) } expectedCarolBandwidth := carolBandwidthBefore + amountNoFee if expectedCarolBandwidth != n.carolChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedCarolBandwidth, n.carolChannelLink.Bandwidth()) } // Now we'll update Bob's policy to jack up his free rate to an extent // that'll cause him to reject the same HTLC that we just sent. // // TODO(roasbeef): should implement grace period within link policy // update logic newPolicy := n.globalPolicy newPolicy.BaseFee = lnwire.NewMSatFromSatoshis(1000) n.secondBobChannelLink.UpdateForwardingPolicy(newPolicy) // Next, we'll send the payment again, using the exact same per-hop // payload for each node. This payment should fail as it won't factor // in Bob's new fee policy. _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err == nil { t.Fatalf("payment should've been rejected") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got (%T): %v", err, err) } switch rtErr.WireMessage().(type) { case *lnwire.FailFeeInsufficient: default: t.Fatalf("expected FailFeeInsufficient instead got: %v", err) } // Reset the policy so we can then test updating the max HTLC policy. n.secondBobChannelLink.UpdateForwardingPolicy(n.globalPolicy) // As a sanity check, ensure the original payment now succeeds again. _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to send payment: %v", err) } // Now we'll update Bob's policy to lower his max HTLC to an extent // that'll cause him to reject the same HTLC that we just sent. newPolicy = n.globalPolicy newPolicy.MaxHTLC = amountNoFee - 1 n.secondBobChannelLink.UpdateForwardingPolicy(newPolicy) // Next, we'll send the payment again, using the exact same per-hop // payload for each node. This payment should fail as it won't factor // in Bob's new max HTLC policy. _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amountNoFee, htlcAmt, htlcExpiry, ).Wait(30 * time.Second) if err == nil { t.Fatalf("payment should've been rejected") } rtErr, ok = err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got (%T): %v", err, err) } switch rtErr.WireMessage().(type) { case *lnwire.FailTemporaryChannelFailure: default: t.Fatalf("expected TemporaryChannelFailure, instead got: %v", err) } } // TestChannelLinkMultiHopInsufficientPayment checks that we receive error if // bob<->alice channel has insufficient BTC capacity/bandwidth. In this test we // send the payment from Carol to Alice over Bob peer. (Carol -> Bob -> Alice) func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() // We'll attempt to send 4 BTC although the alice-to-bob channel only // has 3 BTC total capacity. As a result, this payment should be // rejected. amount := lnwire.NewMSatFromSatoshis(4 * btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // Wait for: // * HTLC add request to be sent to from Alice to Bob. // * Alice<->Bob commitment states to be updated. // * Bob trying to add HTLC add request in Bob<->Carol channel. // * Cancel HTLC request to be sent back from Bob to Alice. // * user notification to be sent. receiver := n.carolServer firstHop := n.firstBobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err == nil { t.Fatal("error haven't been received") } assertFailureCode(t, err, lnwire.CodeTemporaryChannelFailure) // Wait for Alice to receive the revocation. // // TODO(roasbeef): add in ntfn hook for state transition completion time.Sleep(100 * time.Millisecond) // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State == channeldb.ContractSettled { t.Fatal("carol invoice have been settled") } if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore { t.Fatal("the bandwidth of alice channel link which handles " + "alice->bob channel should be the same") } if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "alice->bob channel should be the same") } if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "bob->carol channel should be the same") } if n.carolChannelLink.Bandwidth() != carolBandwidthBefore { t.Fatal("the bandwidth of carol channel link which handles " + "bob->carol channel should be the same") } } // TestChannelLinkMultiHopUnknownPaymentHash checks that we receive remote error // from Alice if she received not suitable payment hash for htlc. func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) blob, err := generateRoute(hops...) if err != nil { t.Fatal(err) } // Generate payment invoice and htlc, but don't add this invoice to the // receiver registry. This should trigger an unknown payment hash // failure. _, htlc, pid, err := generatePayment( amount, htlcAmt, totalTimelock, blob, ) if err != nil { t.Fatal(err) } // Send payment and expose err channel. err = n.aliceServer.htlcSwitch.SendHTLC( n.firstBobChannelLink.ShortChanID(), pid, htlc, ) if err != nil { t.Fatalf("unable to get send payment: %v", err) } resultChan, err := n.aliceServer.htlcSwitch.GetPaymentResult( pid, htlc.PaymentHash, newMockDeobfuscator(), ) if err != nil { t.Fatalf("unable to get payment result: %v", err) } var result *PaymentResult var ok bool select { case result, ok = <-resultChan: if !ok { t.Fatalf("unexpected shutdown") } case <-time.After(10 * time.Second): t.Fatalf("no result arrive") } assertFailureCode( t, result.Error, lnwire.CodeIncorrectOrUnknownPaymentDetails, ) // Wait for Alice to receive the revocation. require.Eventually(t, func() bool { if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore { return false } if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore { return false } if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore { return false } if n.carolChannelLink.Bandwidth() != carolBandwidthBefore { return false } return true }, 10*time.Second, 100*time.Millisecond) } // TestChannelLinkMultiHopUnknownNextHop construct the chain of hops // Carol<->Bob<->Alice and checks that we receive remote error from Bob if he // has no idea about next hop (hop might goes down and routing info not updated // yet). func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) // Remove bob's outgoing link with Carol. This will cause him to fail // back the payment to Alice since he is unaware of Carol when the // payment comes across. bobChanID := lnwire.NewChanIDFromOutPoint( &channels.bobToCarol.State().FundingOutpoint, ) n.bobServer.htlcSwitch.RemoveLink(bobChanID) firstHop := n.firstBobChannelLink.ShortChanID() receiver := n.carolServer rhash, err := makePayment( n.aliceServer, receiver, firstHop, hops, amount, htlcAmt, totalTimelock).Wait(30 * time.Second) if err == nil { t.Fatal("error haven't been received") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected ClearTextError") } if _, ok = rtErr.WireMessage().(*lnwire.FailUnknownNextPeer); !ok { t.Fatalf("wrong error has been received: %T", rtErr.WireMessage()) } // Wait for Alice to receive the revocation. // // TODO(roasbeef): add in ntfn hook for state transition completion time.Sleep(100 * time.Millisecond) // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State == channeldb.ContractSettled { t.Fatal("carol invoice have been settled") } if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore { t.Fatal("the bandwidth of alice channel link which handles " + "alice->bob channel should be the same") } if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "alice->bob channel should be the same") } if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "bob->carol channel should be the same") } if n.carolChannelLink.Bandwidth() != carolBandwidthBefore { t.Fatal("the bandwidth of carol channel link which handles " + "bob->carol channel should be the same") } // Load the forwarding packages for Bob's incoming link. The payment // should have been rejected by the switch, and the AddRef in this link // should be acked by the failed payment. bobInFwdPkgs, err := channels.bobToAlice.State().LoadFwdPkgs() if err != nil { t.Fatalf("unable to load bob's fwd pkgs: %v", err) } // There should be exactly two forward packages, as a full state // transition requires two commitment dances. if len(bobInFwdPkgs) != 2 { t.Fatalf("bob should have exactly 2 fwdpkgs, has %d", len(bobInFwdPkgs)) } // Only one of the forwarding package should have an Add in it, the // other will be empty. Either way, both AckFilters should be fully // acked. for _, fwdPkg := range bobInFwdPkgs { if !fwdPkg.AckFilter.IsFull() { t.Fatalf("fwdpkg chanid=%v height=%d AckFilter is not "+ "fully acked", fwdPkg.Source, fwdPkg.Height) } } } // TestChannelLinkMultiHopDecodeError checks that we send HTLC cancel if // decoding of onion blob failed. func TestChannelLinkMultiHopDecodeError(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() // Replace decode function with another which throws an error. n.carolChannelLink.cfg.ExtractErrorEncrypter = func( *btcec.PublicKey) (hop.ErrorEncrypter, lnwire.FailCode) { return nil, lnwire.CodeInvalidOnionVersion } carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) receiver := n.carolServer firstHop := n.firstBobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err == nil { t.Fatal("error haven't been received") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T", err) } switch rtErr.WireMessage().(type) { case *lnwire.FailInvalidOnionVersion: default: t.Fatalf("wrong error have been received: %v", err) } // Wait for Bob to receive the revocation. time.Sleep(100 * time.Millisecond) // Check that alice invoice wasn't settled and bandwidth of htlc // links hasn't been changed. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State == channeldb.ContractSettled { t.Fatal("carol invoice have been settled") } if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore { t.Fatal("the bandwidth of alice channel link which handles " + "alice->bob channel should be the same") } if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "alice->bob channel should be the same") } if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore { t.Fatal("the bandwidth of bob channel link which handles " + "bob->carol channel should be the same") } if n.carolChannelLink.Bandwidth() != carolBandwidthBefore { t.Fatal("the bandwidth of carol channel link which handles " + "bob->carol channel should be the same") } } // TestChannelLinkExpiryTooSoonExitNode tests that if we send an HTLC to a node // with an expiry that is already expired, or too close to the current block // height, then it will cancel the HTLC. func TestChannelLinkExpiryTooSoonExitNode(t *testing.T) { t.Parallel() // The starting height for this test will be 200. So we'll base all // HTLC starting points off of that. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() const startingHeight = 200 n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, startingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) // We'll craft an HTLC packet, but set the final hop CLTV to 5 blocks // after the current true height. This is less than the test invoice // cltv delta of 6, so we expect the incoming htlc to be failed by the // exit hop. htlcAmt, totalTimelock, hops := generateHops(amount, startingHeight-1, n.firstBobChannelLink) // Now we'll send out the payment from Alice to Bob. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) // The payment should've failed as the time lock value was in the // _past_. if err == nil { t.Fatalf("payment should have failed due to a too early " + "time lock value") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T %v", rtErr, err) } switch rtErr.WireMessage().(type) { case *lnwire.FailIncorrectDetails: default: t.Fatalf("expected incorrect_or_unknown_payment_details, "+ "instead have: %v", err) } } // TestChannelLinkExpiryTooSoonExitNode tests that if we send a multi-hop HTLC, // and the time lock is too early for an intermediate node, then they cancel // the HTLC back to the sender. func TestChannelLinkExpiryTooSoonMidNode(t *testing.T) { t.Parallel() // The starting height for this test will be 200. So we'll base all // HTLC starting points off of that. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() const startingHeight = 200 n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, startingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) // We'll craft an HTLC packet, but set the starting height to 3 blocks // before the current true height. This means that the outgoing time // lock of the middle hop will be at starting height + 3 blocks (channel // policy time lock delta is 6 blocks). There is an expiry grace delta // of 3 blocks relative to the current height, meaning that htlc will // not be sent out by the middle hop. htlcAmt, totalTimelock, hops := generateHops(amount, startingHeight-3, n.firstBobChannelLink, n.carolChannelLink) // Now we'll send out the payment from Alice to Bob. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) // The payment should've failed as the time lock value was in the // _past_. if err == nil { t.Fatalf("payment should have failed due to a too early " + "time lock value") } rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected a ClearTextError, instead got: %T: %v", rtErr, err) } switch rtErr.WireMessage().(type) { case *lnwire.FailExpiryTooSoon: default: t.Fatalf("incorrect error, expected final time lock too "+ "early, instead have: %v", err) } } // TestChannelLinkSingleHopMessageOrdering test checks ordering of message which // flying around between Alice and Bob are correct when Bob sends payments to // Alice. func TestChannelLinkSingleHopMessageOrdering(t *testing.T) { t.Parallel() channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) chanID := n.aliceChannelLink.ChanID() messages := []expectedMessage{ {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, {"alice", "bob", &lnwire.UpdateAddHTLC{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, } debug := false if debug { // Log message that alice receives. n.aliceServer.intersect(createLogFunc("alice", n.aliceChannelLink.ChanID())) // Log message that bob receives. n.bobServer.intersect(createLogFunc("bob", n.firstBobChannelLink.ChanID())) } // Check that alice receives messages in right order. n.aliceServer.intersect(createInterceptorFunc("[alice] <-- [bob]", "alice", messages, chanID, false)) // Check that bob receives messages in right order. n.bobServer.intersect(createInterceptorFunc("[alice] --> [bob]", "bob", messages, chanID, false)) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink) // Wait for: // * HTLC add request to be sent to bob. // * alice<->bob commitment state to be updated. // * settle request to be sent back from bob to alice. // * alice<->bob commitment state to be updated. // * user notification to be sent. firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to make the payment: %v", err) } } type mockPeer struct { sync.Mutex disconnected bool sentMsgs chan lnwire.Message quit chan struct{} } func (m *mockPeer) QuitSignal() <-chan struct{} { return m.quit } var _ lnpeer.Peer = (*mockPeer)(nil) func (m *mockPeer) SendMessage(sync bool, msgs ...lnwire.Message) error { if m.disconnected { return fmt.Errorf("disconnected") } select { case m.sentMsgs <- msgs[0]: case <-m.quit: return fmt.Errorf("mockPeer shutting down") } return nil } func (m *mockPeer) SendMessageLazy(sync bool, msgs ...lnwire.Message) error { return m.SendMessage(sync, msgs...) } func (m *mockPeer) AddNewChannel(_ *channeldb.OpenChannel, _ <-chan struct{}) error { return nil } func (m *mockPeer) WipeChannel(*wire.OutPoint) {} func (m *mockPeer) PubKey() [33]byte { return [33]byte{} } func (m *mockPeer) IdentityKey() *btcec.PublicKey { return nil } func (m *mockPeer) Address() net.Addr { return nil } func (m *mockPeer) LocalFeatures() *lnwire.FeatureVector { return nil } func (m *mockPeer) RemoteFeatures() *lnwire.FeatureVector { return nil } func newSingleLinkTestHarness(chanAmt, chanReserve btcutil.Amount) ( ChannelLink, *lnwallet.LightningChannel, chan time.Time, func() error, func(), func() (*lnwallet.LightningChannel, error), error) { var chanIDBytes [8]byte if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil { return nil, nil, nil, nil, nil, nil, err } chanID := lnwire.NewShortChanIDFromInt( binary.BigEndian.Uint64(chanIDBytes[:])) aliceLc, bobLc, fCleanUp, err := createTestChannel( alicePrivKey, bobPrivKey, chanAmt, chanAmt, chanReserve, chanReserve, chanID, ) if err != nil { return nil, nil, nil, nil, nil, nil, err } var ( decoder = newMockIteratorDecoder() obfuscator = NewMockObfuscator() alicePeer = &mockPeer{ sentMsgs: make(chan lnwire.Message, 2000), quit: make(chan struct{}), } globalPolicy = ForwardingPolicy{ MinHTLCOut: lnwire.NewMSatFromSatoshis(5), MaxHTLC: lnwire.NewMSatFromSatoshis(chanAmt), BaseFee: lnwire.NewMSatFromSatoshis(1), TimeLockDelta: 6, } invoiceRegistry = newMockRegistry(globalPolicy.TimeLockDelta) ) pCache := newMockPreimageCache() aliceDb := aliceLc.channel.State().Db aliceSwitch, err := initSwitchWithDB(testStartingHeight, aliceDb) if err != nil { return nil, nil, nil, nil, nil, nil, err } // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ FwrdingPolicy: globalPolicy, Peer: alicePeer, Switch: aliceSwitch, Circuits: aliceSwitch.CircuitModifier(), ForwardPackets: aliceSwitch.ForwardPackets, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { return obfuscator, lnwire.CodeNone }, FetchLastChannelUpdate: mockGetChanUpdateMessage, PreimageCache: pCache, OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) { }, UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, Registry: invoiceRegistry, FeeEstimator: newMockFeeEstimator(), ChainEvents: &contractcourt.ChainEventSubscription{}, BatchTicker: bticker, FwdPkgGCTicker: ticker.NewForce(15 * time.Second), PendingCommitTicker: ticker.New(time.Minute), // Make the BatchSize and Min/MaxFeeUpdateTimeout large enough // to not trigger commit updates automatically during tests. BatchSize: 10000, MinFeeUpdateTimeout: 30 * time.Minute, MaxFeeUpdateTimeout: 40 * time.Minute, MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, MaxFeeAllocation: DefaultMaxLinkFeeAllocation, NotifyActiveLink: func(wire.OutPoint) {}, NotifyActiveChannel: func(wire.OutPoint) {}, NotifyInactiveChannel: func(wire.OutPoint) {}, HtlcNotifier: aliceSwitch.cfg.HtlcNotifier, } aliceLink := NewChannelLink(aliceCfg, aliceLc.channel) start := func() error { return aliceSwitch.AddLink(aliceLink) } go func() { for { select { case <-aliceLink.(*channelLink).htlcUpdates: case <-aliceLink.(*channelLink).quit: return } } }() cleanUp := func() { close(alicePeer.quit) defer fCleanUp() } return aliceLink, bobLc.channel, bticker.Force, start, cleanUp, aliceLc.restore, nil } func assertLinkBandwidth(t *testing.T, link ChannelLink, expected lnwire.MilliSatoshi) { currentBandwidth := link.Bandwidth() _, _, line, _ := runtime.Caller(1) if currentBandwidth != expected { t.Fatalf("line %v: alice's link bandwidth is incorrect: "+ "expected %v, got %v", line, expected, currentBandwidth) } } // handleStateUpdate handles the messages sent from the link after // the batch ticker has triggered a state update. func handleStateUpdate(link *channelLink, remoteChannel *lnwallet.LightningChannel) error { sentMsgs := link.cfg.Peer.(*mockPeer).sentMsgs var msg lnwire.Message select { case msg = <-sentMsgs: case <-time.After(60 * time.Second): return fmt.Errorf("did not receive CommitSig from Alice") } // The link should be sending a commit sig at this point. commitSig, ok := msg.(*lnwire.CommitSig) if !ok { return fmt.Errorf("expected CommitSig, got %T", msg) } // Let the remote channel receive the commit sig, and // respond with a revocation + commitsig. err := remoteChannel.ReceiveNewCommitment( commitSig.CommitSig, commitSig.HtlcSigs) if err != nil { return err } remoteRev, _, err := remoteChannel.RevokeCurrentCommitment() if err != nil { return err } link.HandleChannelUpdate(remoteRev) remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment() if err != nil { return err } commitSig = &lnwire.CommitSig{ CommitSig: remoteSig, HtlcSigs: remoteHtlcSigs, } link.HandleChannelUpdate(commitSig) // This should make the link respond with a revocation. select { case msg = <-sentMsgs: case <-time.After(60 * time.Second): return fmt.Errorf("did not receive RevokeAndAck from Alice") } revoke, ok := msg.(*lnwire.RevokeAndAck) if !ok { return fmt.Errorf("expected RevokeAndAck got %T", msg) } _, _, _, _, err = remoteChannel.ReceiveRevocation(revoke) if err != nil { return fmt.Errorf("unable to receive "+ "revocation: %v", err) } return nil } // updateState is used exchange the messages necessary to do a full state // transition. If initiateUpdate=true, then this call will make the link // trigger an update by sending on the batchTick channel, if not, it will // make the remoteChannel initiate the state update. func updateState(batchTick chan time.Time, link *channelLink, remoteChannel *lnwallet.LightningChannel, initiateUpdate bool) error { sentMsgs := link.cfg.Peer.(*mockPeer).sentMsgs if initiateUpdate { // Trigger update by ticking the batchTicker. select { case batchTick <- time.Now(): case <-link.quit: return fmt.Errorf("link shutting down") } return handleStateUpdate(link, remoteChannel) } // The remote is triggering the state update, emulate this by // signing and sending CommitSig to the link. remoteSig, remoteHtlcSigs, _, err := remoteChannel.SignNextCommitment() if err != nil { return err } commitSig := &lnwire.CommitSig{ CommitSig: remoteSig, HtlcSigs: remoteHtlcSigs, } link.HandleChannelUpdate(commitSig) // The link should respond with a revocation + commit sig. var msg lnwire.Message select { case msg = <-sentMsgs: case <-time.After(60 * time.Second): return fmt.Errorf("did not receive RevokeAndAck from Alice") } revoke, ok := msg.(*lnwire.RevokeAndAck) if !ok { return fmt.Errorf("expected RevokeAndAck got %T", msg) } _, _, _, _, err = remoteChannel.ReceiveRevocation(revoke) if err != nil { return fmt.Errorf("unable to receive "+ "revocation: %v", err) } select { case msg = <-sentMsgs: case <-time.After(60 * time.Second): return fmt.Errorf("did not receive CommitSig from Alice") } commitSig, ok = msg.(*lnwire.CommitSig) if !ok { return fmt.Errorf("expected CommitSig, got %T", msg) } err = remoteChannel.ReceiveNewCommitment( commitSig.CommitSig, commitSig.HtlcSigs) if err != nil { return err } // Lastly, send a revocation back to the link. remoteRev, _, err := remoteChannel.RevokeCurrentCommitment() if err != nil { return err } link.HandleChannelUpdate(remoteRev) // Sleep to make sure Alice has handled the remote revocation. time.Sleep(500 * time.Millisecond) return nil } // TestChannelLinkBandwidthConsistency ensures that the reported bandwidth of a // given ChannelLink is properly updated in response to downstream messages // from the switch, and upstream messages from its channel peer. // // TODO(roasbeef): add sync hook into packet processing so can eliminate all // sleep in this test and the one below func TestChannelLinkBandwidthConsistency(t *testing.T) { if !build.IsDevBuild() { t.Fatalf("htlcswitch tests must be run with '-tags debug") } t.Parallel() // TODO(roasbeef): replace manual bit twiddling with concept of // resource cost for packets? // * or also able to consult link // We'll start the test by creating a single instance of const chanAmt = btcutil.SatoshiPerBitcoin * 5 aliceLink, bobChannel, tmr, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( carolChanID = lnwire.NewShortChanIDFromInt(3) mockBlob [lnwire.OnionPacketSize]byte coreChan = aliceLink.(*channelLink).channel coreLink = aliceLink.(*channelLink) defaultCommitFee = coreChan.StateSnapshot().CommitFee aliceStartingBandwidth = aliceLink.Bandwidth() aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) // We put Alice into hodl.ExitSettle mode, such that she won't settle // incoming HTLCs automatically. coreLink.cfg.HodlMask = hodl.MaskFromFlags(hodl.ExitSettle) estimator := chainfee.NewStaticEstimator(6000, 0) feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob, minus the // commitment fee and fee for adding an additional HTLC. expectedBandwidth := lnwire.NewMSatFromSatoshis( chanAmt-defaultCommitFee, ) - htlcFee assertLinkBandwidth(t, aliceLink, expectedBandwidth) // Next, we'll create an HTLC worth 1 BTC, and send it into the link as // a switch initiated payment. The resulting bandwidth should // now be decremented to reflect the new HTLC. htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) invoice, htlc, _, err := generatePayment( htlcAmt, htlcAmt, 5, mockBlob, ) if err != nil { t.Fatalf("unable to create payment: %v", err) } addPkt := htlcPacket{ htlc: htlc, incomingChanID: hop.Source, incomingHTLCID: 0, obfuscator: NewMockObfuscator(), } circuit := makePaymentCircuit(&htlc.PaymentHash, &addPkt) _, err = coreLink.cfg.Switch.commitCircuits(&circuit) if err != nil { t.Fatalf("unable to commit circuit: %v", err) } addPkt.circuit = &circuit if err := aliceLink.HandleSwitchPacket(&addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } time.Sleep(time.Millisecond * 500) // The resulting bandwidth should reflect that Alice is paying the // htlc amount in addition to the htlc fee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // Alice should send the HTLC to Bob. var msg lnwire.Message select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } addHtlc, ok := msg.(*lnwire.UpdateAddHTLC) if !ok { t.Fatalf("expected UpdateAddHTLC, got %T", msg) } bobIndex, err := bobChannel.ReceiveHTLC(addHtlc) if err != nil { t.Fatalf("bob failed receiving htlc: %v", err) } // Lock in the HTLC. if err := updateState(tmr, coreLink, bobChannel, true); err != nil { t.Fatalf("unable to update state: %v", err) } // Locking in the HTLC should not change Alice's bandwidth. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // If we now send in a valid HTLC settle for the prior HTLC we added, // then the bandwidth should remain unchanged as the remote party will // gain additional channel balance. err = bobChannel.SettleHTLC(*invoice.Terms.PaymentPreimage, bobIndex, nil, nil, nil) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } htlcSettle := &lnwire.UpdateFulfillHTLC{ ID: 0, PaymentPreimage: *invoice.Terms.PaymentPreimage, } aliceLink.HandleChannelUpdate(htlcSettle) time.Sleep(time.Millisecond * 500) // Since the settle is not locked in yet, Alice's bandwidth should still // reflect that she has to pay the fee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // Lock in the settle. if err := updateState(tmr, coreLink, bobChannel, false); err != nil { t.Fatalf("unable to update state: %v", err) } // Now that it is settled, Alice should have gotten the htlc fee back. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt) // Next, we'll add another HTLC initiated by the switch (of the same // amount as the prior one). _, htlc, _, err = generatePayment(htlcAmt, htlcAmt, 5, mockBlob) if err != nil { t.Fatalf("unable to create payment: %v", err) } addPkt = htlcPacket{ htlc: htlc, incomingChanID: hop.Source, incomingHTLCID: 1, obfuscator: NewMockObfuscator(), } circuit = makePaymentCircuit(&htlc.PaymentHash, &addPkt) _, err = coreLink.cfg.Switch.commitCircuits(&circuit) if err != nil { t.Fatalf("unable to commit circuit: %v", err) } addPkt.circuit = &circuit if err := aliceLink.HandleSwitchPacket(&addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } time.Sleep(time.Millisecond * 500) // Again, Alice's bandwidth decreases by htlcAmt+htlcFee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-2*htlcAmt-htlcFee) // Alice will send the HTLC to Bob. select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } addHtlc, ok = msg.(*lnwire.UpdateAddHTLC) if !ok { t.Fatalf("expected UpdateAddHTLC, got %T", msg) } bobIndex, err = bobChannel.ReceiveHTLC(addHtlc) if err != nil { t.Fatalf("bob failed receiving htlc: %v", err) } // Lock in the HTLC, which should not affect the bandwidth. if err := updateState(tmr, coreLink, bobChannel, true); err != nil { t.Fatalf("unable to update state: %v", err) } assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt*2-htlcFee) // With that processed, we'll now generate an HTLC fail (sent by the // remote peer) to cancel the HTLC we just added. This should return us // back to the bandwidth of the link right before the HTLC was sent. err = bobChannel.FailHTLC(bobIndex, []byte("nop"), nil, nil, nil) if err != nil { t.Fatalf("unable to fail htlc: %v", err) } failMsg := &lnwire.UpdateFailHTLC{ ID: 1, Reason: lnwire.OpaqueReason([]byte("nop")), } aliceLink.HandleChannelUpdate(failMsg) time.Sleep(time.Millisecond * 500) // Before the Fail gets locked in, the bandwidth should remain unchanged. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt*2-htlcFee) // Lock in the Fail. if err := updateState(tmr, coreLink, bobChannel, false); err != nil { t.Fatalf("unable to update state: %v", err) } // Now the bandwidth should reflect the failed HTLC. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt) // Moving along, we'll now receive a new HTLC from the remote peer, // with an ID of 0 as this is their first HTLC. The bandwidth should // remain unchanged (but Alice will need to pay the fee for the extra // HTLC). htlcAmt, totalTimelock, hops := generateHops(htlcAmt, testStartingHeight, coreLink) blob, err := generateRoute(hops...) if err != nil { t.Fatalf("unable to gen route: %v", err) } invoice, htlc, _, err = generatePayment( htlcAmt, htlcAmt, totalTimelock, blob, ) if err != nil { t.Fatalf("unable to create payment: %v", err) } // We must add the invoice to the registry, such that Alice expects // this payment. err = coreLink.cfg.Registry.(*mockInvoiceRegistry).AddInvoice( *invoice, htlc.PaymentHash, ) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } htlc.ID = 0 _, err = bobChannel.AddHTLC(htlc, nil) if err != nil { t.Fatalf("unable to add htlc: %v", err) } aliceLink.HandleChannelUpdate(htlc) // Alice's balance remains unchanged until this HTLC is locked in. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt) // Lock in the HTLC. if err := updateState(tmr, coreLink, bobChannel, false); err != nil { t.Fatalf("unable to update state: %v", err) } // Since Bob is adding this HTLC, Alice only needs to pay the fee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) time.Sleep(time.Millisecond * 500) addPkt = htlcPacket{ htlc: htlc, incomingChanID: aliceLink.ShortChanID(), incomingHTLCID: 0, obfuscator: NewMockObfuscator(), } circuit = makePaymentCircuit(&htlc.PaymentHash, &addPkt) _, err = coreLink.cfg.Switch.commitCircuits(&circuit) if err != nil { t.Fatalf("unable to commit circuit: %v", err) } addPkt.outgoingChanID = carolChanID addPkt.outgoingHTLCID = 0 err = coreLink.cfg.Switch.openCircuits(addPkt.keystone()) if err != nil { t.Fatalf("unable to set keystone: %v", err) } // Next, we'll settle the HTLC with our knowledge of the pre-image that // we eventually learn (simulating a multi-hop payment). The bandwidth // of the channel should now be re-balanced to the starting point. settlePkt := htlcPacket{ incomingChanID: aliceLink.ShortChanID(), incomingHTLCID: 0, circuit: &circuit, outgoingChanID: addPkt.outgoingChanID, outgoingHTLCID: addPkt.outgoingHTLCID, htlc: &lnwire.UpdateFulfillHTLC{ ID: 0, PaymentPreimage: *invoice.Terms.PaymentPreimage, }, obfuscator: NewMockObfuscator(), } if err := aliceLink.HandleSwitchPacket(&settlePkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } time.Sleep(time.Millisecond * 500) // Settling this HTLC gives Alice all her original bandwidth back. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth) select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } settleMsg, ok := msg.(*lnwire.UpdateFulfillHTLC) if !ok { t.Fatalf("expected UpdateFulfillHTLC, got %T", msg) } err = bobChannel.ReceiveHTLCSettle(settleMsg.PaymentPreimage, settleMsg.ID) if err != nil { t.Fatalf("failed receiving fail htlc: %v", err) } // After failing an HTLC, the link will automatically trigger // a state update. if err := handleStateUpdate(coreLink, bobChannel); err != nil { t.Fatalf("unable to update state: %v", err) } // Finally, we'll test the scenario of failing an HTLC received by the // remote node. This should result in no perceived bandwidth changes. htlcAmt, totalTimelock, hops = generateHops(htlcAmt, testStartingHeight, coreLink) blob, err = generateRoute(hops...) if err != nil { t.Fatalf("unable to gen route: %v", err) } invoice, htlc, _, err = generatePayment( htlcAmt, htlcAmt, totalTimelock, blob, ) if err != nil { t.Fatalf("unable to create payment: %v", err) } err = coreLink.cfg.Registry.(*mockInvoiceRegistry).AddInvoice( *invoice, htlc.PaymentHash, ) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } // Since we are not using the link to handle HTLC IDs for the // remote channel, we must set this manually. This is the second // HTLC we add, hence it should have an ID of 1 (Alice's channel // link will set this automatically for her side). htlc.ID = 1 _, err = bobChannel.AddHTLC(htlc, nil) if err != nil { t.Fatalf("unable to add htlc: %v", err) } aliceLink.HandleChannelUpdate(htlc) time.Sleep(time.Millisecond * 500) // No changes before the HTLC is locked in. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth) if err := updateState(tmr, coreLink, bobChannel, false); err != nil { t.Fatalf("unable to update state: %v", err) } // After lock-in, Alice will have to pay the htlc fee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcFee) addPkt = htlcPacket{ htlc: htlc, incomingChanID: aliceLink.ShortChanID(), incomingHTLCID: 1, obfuscator: NewMockObfuscator(), } circuit = makePaymentCircuit(&htlc.PaymentHash, &addPkt) _, err = coreLink.cfg.Switch.commitCircuits(&circuit) if err != nil { t.Fatalf("unable to commit circuit: %v", err) } addPkt.outgoingChanID = carolChanID addPkt.outgoingHTLCID = 1 err = coreLink.cfg.Switch.openCircuits(addPkt.keystone()) if err != nil { t.Fatalf("unable to set keystone: %v", err) } failPkt := htlcPacket{ incomingChanID: aliceLink.ShortChanID(), incomingHTLCID: 1, circuit: &circuit, outgoingChanID: addPkt.outgoingChanID, outgoingHTLCID: addPkt.outgoingHTLCID, htlc: &lnwire.UpdateFailHTLC{ ID: 1, }, obfuscator: NewMockObfuscator(), } if err := aliceLink.HandleSwitchPacket(&failPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } time.Sleep(time.Millisecond * 500) // Alice should get all her bandwidth back. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth) // Message should be sent to Bob. select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } failMsg, ok = msg.(*lnwire.UpdateFailHTLC) if !ok { t.Fatalf("expected UpdateFailHTLC, got %T", msg) } err = bobChannel.ReceiveFailHTLC(failMsg.ID, []byte("fail")) if err != nil { t.Fatalf("failed receiving fail htlc: %v", err) } // After failing an HTLC, the link will automatically trigger // a state update. if err := handleStateUpdate(coreLink, bobChannel); err != nil { t.Fatalf("unable to update state: %v", err) } assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth) } // genAddsAndCircuits creates `numHtlcs` sequential ADD packets and there // corresponding circuits. The provided `htlc` is used in all test packets. func genAddsAndCircuits(numHtlcs int, htlc *lnwire.UpdateAddHTLC) ( []*htlcPacket, []*PaymentCircuit) { addPkts := make([]*htlcPacket, 0, numHtlcs) circuits := make([]*PaymentCircuit, 0, numHtlcs) for i := 0; i < numHtlcs; i++ { addPkt := htlcPacket{ htlc: htlc, incomingChanID: hop.Source, incomingHTLCID: uint64(i), obfuscator: NewMockObfuscator(), } circuit := makePaymentCircuit(&htlc.PaymentHash, &addPkt) addPkt.circuit = &circuit addPkts = append(addPkts, &addPkt) circuits = append(circuits, &circuit) } return addPkts, circuits } // TestChannelLinkTrimCircuitsPending checks that the switch and link properly // trim circuits if there are open circuits corresponding to ADDs on a pending // commmitment transaction. func TestChannelLinkTrimCircuitsPending(t *testing.T) { t.Parallel() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 numHtlcs = 4 halfHtlcs = numHtlcs / 2 ) // We'll start by creating a new link with our chanAmt (5 BTC). We will // only be testing Alice's behavior, so the reference to Bob's channel // state is unnecessary. aliceLink, _, batchTicker, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } alice := newPersistentLinkHarness( t, aliceLink, batchTicker, restore, ) // Compute the static fees that will be used to determine the // correctness of Alice's bandwidth when forwarding HTLCs. estimator := chainfee.NewStaticEstimator(6000, 0) feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob, minus the commitment // fee and fee of adding an HTLC. expectedBandwidth := lnwire.NewMSatFromSatoshis( chanAmt-defaultCommitFee, ) - htlcFee assertLinkBandwidth(t, alice.link, expectedBandwidth) // Capture Alice's starting bandwidth to perform later, relative // bandwidth assertions. aliceStartingBandwidth := alice.link.Bandwidth() // Next, we'll create an HTLC worth 1 BTC that will be used as a dummy // message for the test. var mockBlob [lnwire.OnionPacketSize]byte htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) _, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob) if err != nil { t.Fatalf("unable to create payment: %v", err) } // Create `numHtlc` htlcPackets and payment circuits that will be used // to drive the test. All of the packets will use the same dummy HTLC. addPkts, circuits := genAddsAndCircuits(numHtlcs, htlc) // To begin the test, start by committing the circuits belong to our // first two HTLCs. fwdActions := alice.commitCircuits(circuits[:halfHtlcs]) // Both of these circuits should have successfully added, as this is the // first attempt to send them. if len(fwdActions.Adds) != halfHtlcs { t.Fatalf("expected %d circuits to be added", halfHtlcs) } alice.assertNumPendingNumOpenCircuits(2, 0) // Since both were committed successfully, we will now deliver them to // Alice's link. for _, addPkt := range addPkts[:halfHtlcs] { if err := alice.link.HandleSwitchPacket(addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } } // Wait until Alice's link has sent both HTLCs via the peer. alice.checkSent(addPkts[:halfHtlcs]) // The resulting bandwidth should reflect that Alice is paying both // htlc amounts, in addition to both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) // Now, initiate a state transition by Alice so that the pending HTLCs // are locked in. This will *not* involve any participation by Bob, // which ensures the commitment will remain in a pending state. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(2, 2) // Restart Alice's link, which simulates a disconnection with the remote // peer. cleanUp = alice.restart(false, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 2) // Make a second attempt to commit the first two circuits. This can // happen if the incoming link flaps, but also allows us to verify that // the circuits were trimmed properly. fwdActions = alice.commitCircuits(circuits[:halfHtlcs]) // Since Alice has a pending commitment with the first two HTLCs, the // restart should not have trimmed them from the circuit map. // Therefore, we expect both of these circuits to be dropped by the // switch, as keystones should still be set. if len(fwdActions.Drops) != halfHtlcs { t.Fatalf("expected %d packets to be dropped", halfHtlcs) } // The resulting bandwidth should remain unchanged from before, // reflecting that Alice is paying both htlc amounts, in addition to // both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) // Now, restart Alice's link *and* the entire switch. This will ensure // that entire circuit map is reloaded from disk, and we can now test // against the behavioral differences of committing circuits that // conflict with duplicate circuits after a restart. cleanUp = alice.restart(true, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 2) // Alice should not send out any messages. Even though Alice has a // pending commitment transaction, channel reestablishment is not // enabled in this test. select { case <-alice.msgs: t.Fatalf("message should not have been sent by Alice") case <-time.After(time.Second): } // We will now try to commit the circuits for all of our HTLCs. The // first two are already on the pending commitment transaction, the // latter two are new HTLCs. fwdActions = alice.commitCircuits(circuits) // The first two circuits should have been dropped, as they are still on // the pending commitment transaction, and the restart should not have // trimmed the circuits for these valid HTLCs. if len(fwdActions.Drops) != halfHtlcs { t.Fatalf("expected %d packets to be dropped", halfHtlcs) } // The latter two circuits are unknown the circuit map, and should // report being added. if len(fwdActions.Adds) != halfHtlcs { t.Fatalf("expected %d packets to be added", halfHtlcs) } // Deliver the latter two HTLCs to Alice's links so that they can be // processed and added to the in-memory commitment state. for _, addPkt := range addPkts[halfHtlcs:] { if err := alice.link.HandleSwitchPacket(addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } } // Wait for Alice to send the two latter HTLCs via the peer. alice.checkSent(addPkts[halfHtlcs:]) // With two HTLCs on the pending commit, and two added to the in-memory // commitment state, the resulting bandwidth should reflect that Alice // is paying the all htlc amounts in addition to all htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-numHtlcs*(htlcAmt+htlcFee), ) // We will try to initiate a state transition for Alice, which will // ensure the circuits for the two in-memory HTLCs are opened. However, // since we have a pending commitment, these HTLCs will not actually be // included in a commitment. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(4, 4) // Restart Alice's link to simulate a disconnect. Since the switch // remains up throughout, the two latter HTLCs will remain in the link's // mailbox, and will reprocessed upon being reattached to the link. cleanUp = alice.restart(false, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(4, 2) // Again, try to recommit all of our circuits. fwdActions = alice.commitCircuits(circuits) // It is expected that all of these will get dropped by the switch. // The first two circuits are still open as a result of being on the // commitment transaction. The latter two should have had their open // circuits trimmed, *but* since the HTLCs are still in Alice's mailbox, // the switch knows not to fail them as a result of the latter two // circuits never having been loaded from disk. if len(fwdActions.Drops) != numHtlcs { t.Fatalf("expected %d packets to be dropped", numHtlcs) } // Wait for the latter two htlcs to be pulled from the mailbox, added to // the in-memory channel state, and sent out via the peer. alice.checkSent(addPkts[halfHtlcs:]) // This should result in reconstructing the same bandwidth as our last // assertion. There are two HTLCs on the pending commit, and two added // to the in-memory commitment state, the resulting bandwidth should // reflect that Alice is paying the all htlc amounts in addition to all // htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-numHtlcs*(htlcAmt+htlcFee), ) // Again, we will try to initiate a state transition for Alice, which // will ensure the circuits for the two in-memory HTLCs are opened. // As before, these HTLCs will not actually be included in a commitment // since we have a pending commitment. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(4, 4) // As a final persistence check, we will restart the link and switch, // wiping the latter two HTLCs from memory, and forcing their circuits // to be reloaded from disk. cleanUp = alice.restart(true, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(4, 2) // Alice's mailbox will be empty after the restart, and no channel // reestablishment is configured, so no messages will be sent upon // restart. select { case <-alice.msgs: t.Fatalf("message should not have been sent by Alice") case <-time.After(time.Second): } // Finally, make one last attempt to commit all circuits. fwdActions = alice.commitCircuits(circuits) // The first two HTLCs should still be dropped by the htlcswitch. Their // existence on the pending commitment transaction should prevent their // open circuits from being trimmed. if len(fwdActions.Drops) != halfHtlcs { t.Fatalf("expected %d packets to be dropped", halfHtlcs) } // The latter two HTLCs should now be failed by the switch. These will // have been trimmed by the link or switch restarting, and since the // HTLCs are known to be lost from memory (since their circuits were // loaded from disk), it is safe fail them back as they won't ever be // delivered to the outgoing link. if len(fwdActions.Fails) != halfHtlcs { t.Fatalf("expected %d packets to be dropped", halfHtlcs) } // Since the latter two HTLCs have been completely dropped from memory, // only the first two HTLCs we added should still be reflected in the // channel bandwidth. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) } // TestChannelLinkTrimCircuitsNoCommit checks that the switch and link properly trim // circuits if the ADDs corresponding to open circuits are never committed. func TestChannelLinkTrimCircuitsNoCommit(t *testing.T) { if !build.IsDevBuild() { t.Fatalf("htlcswitch tests must be run with '-tags debug") } t.Parallel() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 numHtlcs = 4 halfHtlcs = numHtlcs / 2 ) // We'll start by creating a new link with our chanAmt (5 BTC). We will // only be testing Alice's behavior, so the reference to Bob's channel // state is unnecessary. aliceLink, _, batchTicker, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } alice := newPersistentLinkHarness( t, aliceLink, batchTicker, restore, ) // We'll put Alice into hodl.Commit mode, such that the circuits for any // outgoing ADDs are opened, but the changes are not committed in the // channel state. alice.coreLink.cfg.HodlMask = hodl.Commit.Mask() // Compute the static fees that will be used to determine the // correctness of Alice's bandwidth when forwarding HTLCs. estimator := chainfee.NewStaticEstimator(6000, 0) feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob, minus the commitment // fee and fee for adding an additional HTLC. expectedBandwidth := lnwire.NewMSatFromSatoshis( chanAmt-defaultCommitFee, ) - htlcFee assertLinkBandwidth(t, alice.link, expectedBandwidth) // Capture Alice's starting bandwidth to perform later, relative // bandwidth assertions. aliceStartingBandwidth := alice.link.Bandwidth() // Next, we'll create an HTLC worth 1 BTC that will be used as a dummy // message for the test. var mockBlob [lnwire.OnionPacketSize]byte htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) _, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob) if err != nil { t.Fatalf("unable to create payment: %v", err) } // Create `numHtlc` htlcPackets and payment circuits that will be used // to drive the test. All of the packets will use the same dummy HTLC. addPkts, circuits := genAddsAndCircuits(numHtlcs, htlc) // To begin the test, start by committing the circuits belong to our // first two HTLCs. fwdActions := alice.commitCircuits(circuits[:halfHtlcs]) // Both of these circuits should have successfully added, as this is the // first attempt to send them. if len(fwdActions.Adds) != halfHtlcs { t.Fatalf("expected %d circuits to be added", halfHtlcs) } // Since both were committed successfully, we will now deliver them to // Alice's link. for _, addPkt := range addPkts[:halfHtlcs] { if err := alice.link.HandleSwitchPacket(addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } } // Wait until Alice's link has sent both HTLCs via the peer. alice.checkSent(addPkts[:halfHtlcs]) // The resulting bandwidth should reflect that Alice is paying both // htlc amounts, in addition to both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) alice.assertNumPendingNumOpenCircuits(2, 0) // Now, init a state transition by Alice to try and commit the HTLCs. // Since she is in hodl.Commit mode, this will fail, but the circuits // will be opened persistently. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(2, 2) // Restart Alice's link, which simulates a disconnection with the remote // peer. Alice's link and switch should trim the circuits that were // opened but not committed. cleanUp = alice.restart(false, false, hodl.Commit) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 0) // The first two HTLCs should have been reset in Alice's mailbox since // the switch was not shutdown. Knowing this the switch should drop the // two circuits, even if the circuits were trimmed. fwdActions = alice.commitCircuits(circuits[:halfHtlcs]) if len(fwdActions.Drops) != halfHtlcs { t.Fatalf("expected %d packets to be dropped since "+ "the switch has not been restarted", halfHtlcs) } // Wait for alice to process the first two HTLCs resend them via the // peer. alice.checkSent(addPkts[:halfHtlcs]) // The resulting bandwidth should reflect that Alice is paying both htlc // amounts, in addition to both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) // Again, initiate another state transition by Alice to try and commit // the HTLCs. Since she is in hodl.Commit mode, this will fail, but the // circuits will be opened persistently. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(2, 2) // Now, we we will do a full restart of the link and switch, configuring // Alice again in hodl.Commit mode. Since none of the HTLCs were // actually committed, the previously opened circuits should be trimmed // by both the link and switch. cleanUp = alice.restart(true, false, hodl.Commit) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 0) // Attempt another commit of our first two circuits. Both should fail, // as the opened circuits should have been trimmed, and circuit map // recognizes that these HTLCs were lost during the restart. fwdActions = alice.commitCircuits(circuits[:halfHtlcs]) if len(fwdActions.Fails) != halfHtlcs { t.Fatalf("expected %d packets to be failed", halfHtlcs) } // Bob should not receive any HTLCs from Alice, since Alice's mailbox is // empty and there is no pending commitment. select { case <-alice.msgs: t.Fatalf("received unexpected message from Alice") case <-time.After(time.Second): } // Alice's bandwidth should have reverted back to her starting value. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth) // Now, try to commit the last two payment circuits, which are unused // thus far. These should succeed without hesitation. fwdActions = alice.commitCircuits(circuits[halfHtlcs:]) if len(fwdActions.Adds) != halfHtlcs { t.Fatalf("expected %d packets to be added", halfHtlcs) } // Deliver the last two HTLCs to the link via Alice's mailbox. for _, addPkt := range addPkts[halfHtlcs:] { if err := alice.link.HandleSwitchPacket(addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } } // Verify that Alice processed and sent out the ADD packets via the // peer. alice.checkSent(addPkts[halfHtlcs:]) // The resulting bandwidth should reflect that Alice is paying both htlc // amounts, in addition to both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) // Now, initiate a state transition for Alice. Since we are hodl.Commit // mode, this will only open the circuits that were added to the // in-memory channel state. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(4, 2) // Restart Alice's link, and place her back in hodl.Commit mode. On // restart, all previously opened circuits should be trimmed by both the // link and the switch. cleanUp = alice.restart(false, false, hodl.Commit) defer cleanUp() alice.assertNumPendingNumOpenCircuits(4, 0) // Now, try to commit all of known circuits. fwdActions = alice.commitCircuits(circuits) // The first two HTLCs will fail to commit for the same reason as // before, the circuits have been trimmed. if len(fwdActions.Fails) != halfHtlcs { t.Fatalf("expected %d packet to be failed", halfHtlcs) } // The last two HTLCs will be dropped, as thought the circuits are // trimmed, the switch is aware that the HTLCs are still in Alice's // mailbox. if len(fwdActions.Drops) != halfHtlcs { t.Fatalf("expected %d packet to be dropped", halfHtlcs) } // Wait until Alice reprocesses the last two HTLCs and sends them via // the peer. alice.checkSent(addPkts[halfHtlcs:]) // Her bandwidth should now reflect having sent only those two HTLCs. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-halfHtlcs*(htlcAmt+htlcFee), ) // Now, initiate a state transition for Alice. Since we are hodl.Commit // mode, this will only open the circuits that were added to the // in-memory channel state. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(4, 2) // Finally, do one last restart of both the link and switch. This will // flush the HTLCs from the mailbox. The circuits should now be trimmed // for all of the HTLCs. cleanUp = alice.restart(true, false, hodl.Commit) defer cleanUp() alice.assertNumPendingNumOpenCircuits(4, 0) // Bob should not receive any HTLCs from Alice, as none of the HTLCs are // in Alice's mailbox, and channel reestablishment is disabled. select { case <-alice.msgs: t.Fatalf("received unexpected message from Alice") case <-time.After(time.Second): } // Attempt to commit the last two circuits, both should now fail since // though they were opened before shutting down, the circuits have been // properly trimmed. fwdActions = alice.commitCircuits(circuits[halfHtlcs:]) if len(fwdActions.Fails) != halfHtlcs { t.Fatalf("expected %d packet to be failed", halfHtlcs) } // Alice balance should not have changed since the start. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth) } // TestChannelLinkTrimCircuitsRemoteCommit checks that the switch and link // don't trim circuits if the ADD is locked in on the remote commitment but // not on our local commitment. func TestChannelLinkTrimCircuitsRemoteCommit(t *testing.T) { t.Parallel() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 numHtlcs = 2 ) // We'll start by creating a new link with our chanAmt (5 BTC). aliceLink, bobChan, batchTicker, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } defer cleanUp() alice := newPersistentLinkHarness( t, aliceLink, batchTicker, restore, ) // Compute the static fees that will be used to determine the // correctness of Alice's bandwidth when forwarding HTLCs. estimator := chainfee.NewStaticEstimator(6000, 0) feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } defaultCommitFee := alice.channel.StateSnapshot().CommitFee htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob, minus the commitment // fee and fee of adding an HTLC. expectedBandwidth := lnwire.NewMSatFromSatoshis( chanAmt-defaultCommitFee, ) - htlcFee assertLinkBandwidth(t, alice.link, expectedBandwidth) // Capture Alice's starting bandwidth to perform later, relative // bandwidth assertions. aliceStartingBandwidth := alice.link.Bandwidth() // Next, we'll create an HTLC worth 1 BTC that will be used as a dummy // message for the test. var mockBlob [lnwire.OnionPacketSize]byte htlcAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) _, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob) if err != nil { t.Fatalf("unable to create payment: %v", err) } // Create `numHtlc` htlcPackets and payment circuits that will be used // to drive the test. All of the packets will use the same dummy HTLC. addPkts, circuits := genAddsAndCircuits(numHtlcs, htlc) // To begin the test, start by committing the circuits for our first two // HTLCs. fwdActions := alice.commitCircuits(circuits) // Both of these circuits should have successfully added, as this is the // first attempt to send them. if len(fwdActions.Adds) != numHtlcs { t.Fatalf("expected %d circuits to be added", numHtlcs) } alice.assertNumPendingNumOpenCircuits(2, 0) // Since both were committed successfully, we will now deliver them to // Alice's link. for _, addPkt := range addPkts { if err := alice.link.HandleSwitchPacket(addPkt); err != nil { t.Fatalf("unable to handle switch packet: %v", err) } } // Wait until Alice's link has sent both HTLCs via the peer. alice.checkSent(addPkts) // Pass both of the htlcs to Bob. for i, addPkt := range addPkts { pkt, ok := addPkt.htlc.(*lnwire.UpdateAddHTLC) if !ok { t.Fatalf("unable to add packet") } pkt.ID = uint64(i) _, err := bobChan.ReceiveHTLC(pkt) if err != nil { t.Fatalf("unable to receive htlc: %v", err) } } // The resulting bandwidth should reflect that Alice is paying both // htlc amounts, in addition to both htlc fees. assertLinkBandwidth(t, alice.link, aliceStartingBandwidth-numHtlcs*(htlcAmt+htlcFee), ) // Now, initiate a state transition by Alice so that the pending HTLCs // are locked in. alice.trySignNextCommitment() alice.assertNumPendingNumOpenCircuits(2, 2) select { case aliceMsg := <-alice.msgs: // Pass the commitment signature to Bob. sig, ok := aliceMsg.(*lnwire.CommitSig) if !ok { t.Fatalf("alice did not send commitment signature") } err := bobChan.ReceiveNewCommitment(sig.CommitSig, sig.HtlcSigs) if err != nil { t.Fatalf("unable to receive new commitment: %v", err) } case <-time.After(time.Second): } // Next, revoke Bob's current commitment and send it to Alice so that we // can test that Alice's circuits aren't trimmed. rev, _, err := bobChan.RevokeCurrentCommitment() if err != nil { t.Fatalf("unable to revoke current commitment: %v", err) } _, _, _, _, err = alice.channel.ReceiveRevocation(rev) if err != nil { t.Fatalf("unable to receive revocation: %v", err) } // Restart Alice's link, which simulates a disconnection with the remote // peer. cleanUp = alice.restart(false, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 2) // Restart the link + switch and check that the number of open circuits // doesn't change. cleanUp = alice.restart(true, false) defer cleanUp() alice.assertNumPendingNumOpenCircuits(2, 2) } // TestChannelLinkBandwidthChanReserve checks that the bandwidth available // on the channel link reflects the channel reserve that must be kept // at all times. func TestChannelLinkBandwidthChanReserve(t *testing.T) { t.Parallel() // First start a link that has a balance greater than it's // channel reserve. const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTimer, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( mockBlob [lnwire.OnionPacketSize]byte coreLink = aliceLink.(*channelLink) coreChan = coreLink.channel defaultCommitFee = coreChan.StateSnapshot().CommitFee aliceStartingBandwidth = aliceLink.Bandwidth() aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) estimator := chainfee.NewStaticEstimator(6000, 0) feePerKw, err := estimator.EstimateFeePerKW(1) if err != nil { t.Fatalf("unable to query fee estimator: %v", err) } htlcFee := lnwire.NewMSatFromSatoshis( feePerKw.FeeForWeight(input.HTLCWeight), ) // The starting bandwidth of the channel should be exactly the amount // that we created the channel between her and Bob, minus the channel // reserve, commitment fee and fee for adding an additional HTLC. expectedBandwidth := lnwire.NewMSatFromSatoshis( chanAmt-defaultCommitFee-chanReserve) - htlcFee assertLinkBandwidth(t, aliceLink, expectedBandwidth) // Next, we'll create an HTLC worth 3 BTC, and send it into the link as // a switch initiated payment. The resulting bandwidth should // now be decremented to reflect the new HTLC. htlcAmt := lnwire.NewMSatFromSatoshis(3 * btcutil.SatoshiPerBitcoin) invoice, htlc, _, err := generatePayment(htlcAmt, htlcAmt, 5, mockBlob) if err != nil { t.Fatalf("unable to create payment: %v", err) } addPkt := &htlcPacket{ htlc: htlc, obfuscator: NewMockObfuscator(), } circuit := makePaymentCircuit(&htlc.PaymentHash, addPkt) _, err = coreLink.cfg.Switch.commitCircuits(&circuit) if err != nil { t.Fatalf("unable to commit circuit: %v", err) } aliceLink.HandleSwitchPacket(addPkt) time.Sleep(time.Millisecond * 100) assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // Alice should send the HTLC to Bob. var msg lnwire.Message select { case msg = <-aliceMsgs: case <-time.After(15 * time.Second): t.Fatalf("did not receive message") } addHtlc, ok := msg.(*lnwire.UpdateAddHTLC) if !ok { t.Fatalf("expected UpdateAddHTLC, got %T", msg) } bobIndex, err := bobChannel.ReceiveHTLC(addHtlc) if err != nil { t.Fatalf("bob failed receiving htlc: %v", err) } // Lock in the HTLC. if err := updateState(batchTimer, coreLink, bobChannel, true); err != nil { t.Fatalf("unable to update state: %v", err) } assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // If we now send in a valid HTLC settle for the prior HTLC we added, // then the bandwidth should remain unchanged as the remote party will // gain additional channel balance. err = bobChannel.SettleHTLC(*invoice.Terms.PaymentPreimage, bobIndex, nil, nil, nil) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } htlcSettle := &lnwire.UpdateFulfillHTLC{ ID: bobIndex, PaymentPreimage: *invoice.Terms.PaymentPreimage, } aliceLink.HandleChannelUpdate(htlcSettle) time.Sleep(time.Millisecond * 500) // Since the settle is not locked in yet, Alice's bandwidth should still // reflect that she has to pay the fee. assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt-htlcFee) // Lock in the settle. if err := updateState(batchTimer, coreLink, bobChannel, false); err != nil { t.Fatalf("unable to update state: %v", err) } time.Sleep(time.Millisecond * 100) assertLinkBandwidth(t, aliceLink, aliceStartingBandwidth-htlcAmt) // Now we create a channel that has a channel reserve that is // greater than it's balance. In these case only payments can // be received on this channel, not sent. The available bandwidth // should therefore be 0. const bobChanAmt = btcutil.SatoshiPerBitcoin * 1 const bobChanReserve = btcutil.SatoshiPerBitcoin * 1.5 bobLink, _, _, start, bobCleanUp, _, err := newSingleLinkTestHarness(bobChanAmt, bobChanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer bobCleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } // Make sure bandwidth is reported as 0. assertLinkBandwidth(t, bobLink, 0) } // TestChannelRetransmission tests the ability of the channel links to // synchronize theirs states after abrupt disconnect. func TestChannelRetransmission(t *testing.T) { t.Parallel() retransmissionTests := []struct { name string messages []expectedMessage }{ { // Tests the ability of the channel links states to be // synchronized after remote node haven't receive // revoke and ack message. name: "intercept last alice revoke_and_ack", messages: []expectedMessage{ // First initialization of the channel. {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, // Send payment from Alice to Bob and intercept // the last revocation message, in this case // Bob should not proceed the payment farther. {"alice", "bob", &lnwire.UpdateAddHTLC{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, true}, // Reestablish messages exchange on nodes restart. {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, // Alice should resend the revoke_and_ack // message to Bob because Bob claimed it in the // re-establish message. {"alice", "bob", &lnwire.RevokeAndAck{}, false}, // Proceed the payment farther by sending the // fulfilment message and trigger the state // update. {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, }, }, { // Tests the ability of the channel links states to be // synchronized after remote node haven't receive // revoke and ack message. name: "intercept bob revoke_and_ack commit_sig messages", messages: []expectedMessage{ {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, // Send payment from Alice to Bob and intercept // the last revocation message, in this case // Bob should not proceed the payment farther. {"alice", "bob", &lnwire.UpdateAddHTLC{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, // Intercept bob commit sig and revoke and ack // messages. {"bob", "alice", &lnwire.RevokeAndAck{}, true}, {"bob", "alice", &lnwire.CommitSig{}, true}, // Reestablish messages exchange on nodes restart. {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, // Bob should resend previously intercepted messages. {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, // Proceed the payment farther by sending the // fulfilment message and trigger the state // update. {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, }, }, { // Tests the ability of the channel links states to be // synchronized after remote node haven't receive // update and commit sig messages. name: "intercept update add htlc and commit sig messages", messages: []expectedMessage{ {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, // Attempt make a payment from Alice to Bob, // which is intercepted, emulating the Bob // server abrupt stop. {"alice", "bob", &lnwire.UpdateAddHTLC{}, true}, {"alice", "bob", &lnwire.CommitSig{}, true}, // Restart of the nodes, and after that nodes // should exchange the reestablish messages. {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, // After Bob has notified Alice that he didn't // receive updates Alice should re-send them. {"alice", "bob", &lnwire.UpdateAddHTLC{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.UpdateFulfillHTLC{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, }, }, } paymentWithRestart := func(t *testing.T, messages []expectedMessage) { channels, cleanUp, restoreChannelsFromDb, err := createClusterChannels( btcutil.SatoshiPerBitcoin*5, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() chanID := lnwire.NewChanIDFromOutPoint(channels.aliceToBob.ChannelPoint()) serverErr := make(chan error, 4) aliceInterceptor := createInterceptorFunc("[alice] <-- [bob]", "alice", messages, chanID, false) bobInterceptor := createInterceptorFunc("[alice] --> [bob]", "bob", messages, chanID, false) ct := newConcurrentTester(t) // Add interceptor to check the order of Bob and Alice // messages. n := newThreeHopNetwork(ct, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight, ) n.aliceServer.intersect(aliceInterceptor) n.bobServer.intersect(bobInterceptor) if err := n.start(); err != nil { ct.Fatalf("unable to start three hop network: %v", err) } defer n.stop() bobBandwidthBefore := n.firstBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink) // Send payment which should fail because we intercept the // update and commit messages. // // TODO(roasbeef); increase timeout? receiver := n.bobServer firstHop := n.firstBobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, receiver, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(time.Second * 5) if err == nil { ct.Fatalf("payment shouldn't haven been finished") } // Stop network cluster and create new one, with the old // channels states. Also do the *hack* - save the payment // receiver to pass it in new channel link, otherwise payment // will be failed because of the unknown payment hash. Hack // will be removed with sphinx payment. bobRegistry := n.bobServer.registry n.stop() channels, err = restoreChannelsFromDb() if err != nil { ct.Fatalf("unable to restore channels from database: %v", err) } n = newThreeHopNetwork(ct, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) n.firstBobChannelLink.cfg.Registry = bobRegistry n.aliceServer.intersect(aliceInterceptor) n.bobServer.intersect(bobInterceptor) if err := n.start(); err != nil { ct.Fatalf("unable to start three hop network: %v", err) } defer n.stop() // Wait for reestablishment to be proceeded and invoice to be settled. // TODO(andrew.shvv) Will be removed if we move the notification center // to the channel link itself. var invoice channeldb.Invoice for i := 0; i < 20; i++ { select { case <-time.After(time.Millisecond * 200): case serverErr := <-serverErr: ct.Fatalf("server error: %v", serverErr) } // Check that alice invoice wasn't settled and // bandwidth of htlc links hasn't been changed. invoice, err = receiver.registry.LookupInvoice(rhash) if err != nil { err = errors.Errorf("unable to get invoice: %v", err) continue } if invoice.State != channeldb.ContractSettled { err = errors.Errorf("alice invoice haven't been settled") continue } aliceExpectedBandwidth := aliceBandwidthBefore - htlcAmt if aliceExpectedBandwidth != n.aliceChannelLink.Bandwidth() { err = errors.Errorf("expected alice to have %v, instead has %v", aliceExpectedBandwidth, n.aliceChannelLink.Bandwidth()) continue } bobExpectedBandwidth := bobBandwidthBefore + htlcAmt if bobExpectedBandwidth != n.firstBobChannelLink.Bandwidth() { err = errors.Errorf("expected bob to have %v, instead has %v", bobExpectedBandwidth, n.firstBobChannelLink.Bandwidth()) continue } break } if err != nil { ct.Fatal(err) } } for _, test := range retransmissionTests { passed := t.Run(test.name, func(t *testing.T) { paymentWithRestart(t, test.messages) }) if !passed { break } } } // TestShouldAdjustCommitFee tests the shouldAdjustCommitFee pivot function to // ensure that ie behaves properly. We should only update the fee if it // deviates from our current fee by more 10% or more. func TestShouldAdjustCommitFee(t *testing.T) { tests := []struct { netFee chainfee.SatPerKWeight chanFee chainfee.SatPerKWeight shouldAdjust bool }{ // The network fee is 3x lower than the current commitment // transaction. As a result, we should adjust our fee to match // it. { netFee: 100, chanFee: 3000, shouldAdjust: true, }, // The network fee is lower than the current commitment fee, // but only slightly so, so we won't update the commitment fee. { netFee: 2999, chanFee: 3000, shouldAdjust: false, }, // The network fee is lower than the commitment fee, but only // right before it crosses our current threshold. { netFee: 1000, chanFee: 1099, shouldAdjust: false, }, // The network fee is lower than the commitment fee, and within // our range of adjustment, so we should adjust. { netFee: 1000, chanFee: 1100, shouldAdjust: true, }, // The network fee is 2x higher than our commitment fee, so we // should adjust upwards. { netFee: 2000, chanFee: 1000, shouldAdjust: true, }, // The network fee is higher than our commitment fee, but only // slightly so, so we won't update. { netFee: 1001, chanFee: 1000, shouldAdjust: false, }, // The network fee is higher than our commitment fee, but // hasn't yet crossed our activation threshold. { netFee: 1100, chanFee: 1099, shouldAdjust: false, }, // The network fee is higher than our commitment fee, and // within our activation threshold, so we should update our // fee. { netFee: 1100, chanFee: 1000, shouldAdjust: true, }, // Our fees match exactly, so we shouldn't update it at all. { netFee: 1000, chanFee: 1000, shouldAdjust: false, }, } for i, test := range tests { adjustedFee := shouldAdjustCommitFee( test.netFee, test.chanFee, ) if adjustedFee && !test.shouldAdjust { t.Fatalf("test #%v failed: net_fee=%v, "+ "chan_fee=%v, adjust_expect=%v, adjust_returned=%v", i, test.netFee, test.chanFee, test.shouldAdjust, adjustedFee) } } } // TestChannelLinkShutdownDuringForward asserts that a link can be fully // stopped when it is trying to send synchronously through the switch. The // specific case this can occur is when a link forwards incoming Adds. We test // this by forcing the switch into a state where it will not accept new packets, // and then killing the link, which can only succeed if forwarding can be // canceled by a call to Stop. func TestChannelLinkShutdownDuringForward(t *testing.T) { t.Parallel() // First, we'll create our traditional three hop network. We're // interested in testing the ability to stop the link when it is // synchronously forwarding to the switch, which happens when an // incoming link forwards Adds. Thus, the test will be performed // against Bob's first link. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() defer n.feeEstimator.Stop() // Define a helper method that strobes the switch's log ticker, and // unblocks after nothing has been pulled for two seconds. waitForBobsSwitchToBlock := func() { bobSwitch := n.firstBobChannelLink.cfg.Switch ticker := bobSwitch.cfg.LogEventTicker.(*ticker.Force) timeout := time.After(15 * time.Second) for { time.Sleep(50 * time.Millisecond) select { case ticker.Force <- time.Now(): case <-time.After(2 * time.Second): return case <-timeout: t.Fatalf("switch did not block") } } } // Define a helper method that strobes the link's batch ticker, and // unblocks after nothing has been pulled for two seconds. waitForBobsIncomingLinkToBlock := func() { ticker := n.firstBobChannelLink.cfg.BatchTicker.(*ticker.Force) timeout := time.After(15 * time.Second) for { time.Sleep(50 * time.Millisecond) select { case ticker.Force <- time.Now(): case <-time.After(2 * time.Second): // We'll give a little extra time here, to // ensure that the packet is being pressed // against the htlcPlex. time.Sleep(50 * time.Millisecond) return case <-timeout: t.Fatalf("link did not block") } } } // To test that the cancellation is happening properly, we will set the // switch's htlcPlex to nil, so that calls to routeAsync block, and can // only exit if the link (or switch) is exiting. We will only be testing // the link here. // // In order to avoid data races, we need to ensure the switch isn't // selecting on that channel in the meantime. We'll prevent this by // first acquiring the index mutex and forcing a log event so that the // htlcForwarder is blocked inside the logTicker case, which also needs // the indexMtx. n.firstBobChannelLink.cfg.Switch.indexMtx.Lock() // Strobe the log ticker, and wait for switch to stop accepting any more // log ticks. waitForBobsSwitchToBlock() // While the htlcForwarder is blocked, swap out the htlcPlex with a nil // channel, and unlock the indexMtx to allow return to the // htlcForwarder's main select. After this, any attempt to forward // through the switch will block. n.firstBobChannelLink.cfg.Switch.htlcPlex = nil n.firstBobChannelLink.cfg.Switch.indexMtx.Unlock() // Now, make a payment from Alice to Carol, which should cause Bob's // incoming link to block when it tries to submit the packet to the nil // htlcPlex. amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops( amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink, ) firstHop := n.firstBobChannelLink.ShortChanID() makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, totalTimelock, ) // Strobe the batch ticker of Bob's incoming link, waiting for it to // become fully blocked. waitForBobsIncomingLinkToBlock() // Finally, stop the link to test that it can exit while synchronously // forwarding Adds to the switch. done := make(chan struct{}) go func() { n.firstBobChannelLink.Stop() close(done) }() select { case <-time.After(3 * time.Second): t.Fatalf("unable to shutdown link while fwding incoming Adds") case <-done: } } // TestChannelLinkUpdateCommitFee tests that when a new block comes in, the // channel link properly checks to see if it should update the commitment fee. func TestChannelLinkUpdateCommitFee(t *testing.T) { t.Parallel() // First, we'll create our traditional three hop network. We'll only be // interacting with and asserting the state of two of the end points // for this test. const aliceInitialBalance = btcutil.SatoshiPerBitcoin * 3 channels, cleanUp, _, err := createClusterChannels( aliceInitialBalance, btcutil.SatoshiPerBitcoin*5, ) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) // First, we'll set up some message interceptors to ensure that the // proper messages are sent when updating fees. chanID := n.aliceChannelLink.ChanID() messages := []expectedMessage{ {"alice", "bob", &lnwire.ChannelReestablish{}, false}, {"bob", "alice", &lnwire.ChannelReestablish{}, false}, {"alice", "bob", &lnwire.FundingLocked{}, false}, {"bob", "alice", &lnwire.FundingLocked{}, false}, // First fee update. {"alice", "bob", &lnwire.UpdateFee{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, // Second fee update. {"alice", "bob", &lnwire.UpdateFee{}, false}, {"alice", "bob", &lnwire.CommitSig{}, false}, {"bob", "alice", &lnwire.RevokeAndAck{}, false}, {"bob", "alice", &lnwire.CommitSig{}, false}, {"alice", "bob", &lnwire.RevokeAndAck{}, false}, } n.aliceServer.intersect(createInterceptorFunc("[alice] <-- [bob]", "alice", messages, chanID, false)) n.bobServer.intersect(createInterceptorFunc("[alice] --> [bob]", "bob", messages, chanID, false)) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() defer n.feeEstimator.Stop() startingFeeRate := channels.aliceToBob.CommitFeeRate() // triggerFeeUpdate is a helper closure to determine whether a fee // update was triggered and completed properly. triggerFeeUpdate := func(feeEstimate, newFeeRate chainfee.SatPerKWeight, shouldUpdate bool) { t.Helper() // Record the fee rates before the links process the fee update // to test the case where a fee update isn't triggered. aliceBefore := channels.aliceToBob.CommitFeeRate() bobBefore := channels.bobToAlice.CommitFeeRate() // For the sake of this test, we'll reset the timer so that // Alice's link queries for a new network fee. n.aliceChannelLink.updateFeeTimer.Reset(time.Millisecond) // Next, we'll send the first fee rate response to Alice. select { case n.feeEstimator.byteFeeIn <- feeEstimate: case <-time.After(time.Second * 5): t.Fatalf("alice didn't query for the new network fee") } // Record the fee rates after the links have processed the fee // update and ensure they are correct based on whether a fee // update should have been triggered. require.Eventually(t, func() bool { aliceAfter := channels.aliceToBob.CommitFeeRate() bobAfter := channels.bobToAlice.CommitFeeRate() switch { case shouldUpdate && aliceAfter != newFeeRate: return false case shouldUpdate && bobAfter != newFeeRate: return false case !shouldUpdate && aliceAfter != aliceBefore: return false case !shouldUpdate && bobAfter != bobBefore: return false } return true }, 10*time.Second, time.Second) } // Triggering the link to update the fee of the channel with the same // fee rate should not send a fee update. triggerFeeUpdate(startingFeeRate, startingFeeRate, false) // Triggering the link to update the fee of the channel with a much // larger fee rate _should_ send a fee update. newFeeRate := startingFeeRate * 3 triggerFeeUpdate(newFeeRate, newFeeRate, true) // Triggering the link to update the fee of the channel with a fee rate // that exceeds its maximum fee allocation should result in a fee rate // corresponding to the maximum fee allocation. const maxFeeRate chainfee.SatPerKWeight = 207182320 triggerFeeUpdate(maxFeeRate+1, maxFeeRate, true) } // TestChannelLinkAcceptDuplicatePayment tests that if a link receives an // incoming HTLC for a payment we have already settled, then it accepts the // HTLC. We do this to simplify the processing of settles after restarts or // failures, reducing ambiguity when a batch is only partially processed. func TestChannelLinkAcceptDuplicatePayment(t *testing.T) { t.Parallel() // First, we'll create our traditional three hop network. We'll only be // interacting with and asserting the state of two of the end points // for this test. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) // We'll start off by making a payment from Alice to Carol. We'll // manually generate this request so we can control all the parameters. htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink) blob, err := generateRoute(hops...) if err != nil { t.Fatal(err) } invoice, htlc, pid, err := generatePayment( amount, htlcAmt, totalTimelock, blob, ) if err != nil { t.Fatal(err) } err = n.carolServer.registry.AddInvoice(*invoice, htlc.PaymentHash) if err != nil { t.Fatalf("unable to add invoice in carol registry: %v", err) } // With the invoice now added to Carol's registry, we'll send the // payment. err = n.aliceServer.htlcSwitch.SendHTLC( n.firstBobChannelLink.ShortChanID(), pid, htlc, ) if err != nil { t.Fatalf("unable to send payment to carol: %v", err) } resultChan, err := n.aliceServer.htlcSwitch.GetPaymentResult( pid, htlc.PaymentHash, newMockDeobfuscator(), ) if err != nil { t.Fatalf("unable to get payment result: %v", err) } // Now, if we attempt to send the payment *again* it should be rejected // as it's a duplicate request. err = n.aliceServer.htlcSwitch.SendHTLC( n.firstBobChannelLink.ShortChanID(), pid, htlc, ) if err != ErrDuplicateAdd { t.Fatalf("ErrDuplicateAdd should have been "+ "received got: %v", err) } select { case result, ok := <-resultChan: if !ok { t.Fatalf("unexpected shutdown") } if result.Error != nil { t.Fatalf("payment failed: %v", result.Error) } case <-time.After(5 * time.Second): t.Fatalf("payment result did not arrive") } } // TestChannelLinkAcceptOverpay tests that if we create an invoice for sender, // and the sender sends *more* than specified in the invoice, then we'll still // accept it and settle as normal. func TestChannelLinkAcceptOverpay(t *testing.T) { t.Parallel() // First, we'll create our traditional three hop network. We'll only be // interacting with and asserting the state of two of the end points // for this test. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() carolBandwidthBefore := n.carolChannelLink.Bandwidth() firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth() secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth() aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() // We'll request a route to send 10k satoshis via Alice -> Bob -> // Carol. amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops( amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink, ) // When we actually go to send the payment, we'll actually create an // invoice at Carol for only half of this amount. receiver := n.carolServer firstHop := n.firstBobChannelLink.ShortChanID() rhash, err := makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount/2, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to send payment: %v", err) } // Wait for Alice and Bob's second link to receive the revocation. time.Sleep(2 * time.Second) // Even though we sent 2x what was asked for, Carol should still have // accepted the payment and marked it as settled. invoice, err := receiver.registry.LookupInvoice(rhash) if err != nil { t.Fatalf("unable to get invoice: %v", err) } if invoice.State != channeldb.ContractSettled { t.Fatal("carol invoice haven't been settled") } expectedAliceBandwidth := aliceBandwidthBefore - htlcAmt if expectedAliceBandwidth != n.aliceChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedAliceBandwidth, n.aliceChannelLink.Bandwidth()) } expectedBobBandwidth1 := firstBobBandwidthBefore + htlcAmt if expectedBobBandwidth1 != n.firstBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth()) } expectedBobBandwidth2 := secondBobBandwidthBefore - amount if expectedBobBandwidth2 != n.secondBobChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth()) } expectedCarolBandwidth := carolBandwidthBefore + amount if expectedCarolBandwidth != n.carolChannelLink.Bandwidth() { t.Fatalf("channel bandwidth incorrect: expected %v, got %v", expectedCarolBandwidth, n.carolChannelLink.Bandwidth()) } // Finally, we'll ensure that the amount we paid is properly reflected // in the stored invoice. if invoice.AmtPaid != amount { t.Fatalf("expected amt paid to be %v, is instead %v", amount, invoice.AmtPaid) } } // persistentLinkHarness is used to control the lifecylce of a link and the // switch that operates it. It supports the ability to restart either the link // or both the link and the switch. type persistentLinkHarness struct { t *testing.T link ChannelLink coreLink *channelLink channel *lnwallet.LightningChannel batchTicker chan time.Time msgs chan lnwire.Message restoreChan func() (*lnwallet.LightningChannel, error) } // newPersistentLinkHarness initializes a new persistentLinkHarness and derives // the supporting references from the active link. func newPersistentLinkHarness(t *testing.T, link ChannelLink, batchTicker chan time.Time, restore func() (*lnwallet.LightningChannel, error)) *persistentLinkHarness { coreLink := link.(*channelLink) return &persistentLinkHarness{ t: t, link: link, coreLink: coreLink, channel: coreLink.channel, batchTicker: batchTicker, msgs: coreLink.cfg.Peer.(*mockPeer).sentMsgs, restoreChan: restore, } } // restart facilitates a shutdown and restart of the link maintained by the // harness. The primary purpose of this method is to ensure the consistency of // the supporting references is maintained across restarts. // // If `restartSwitch` is set, the entire switch will also be restarted, // and will be reinitialized with the contents of the channeldb backing Alice's // channel. // // Any number of hodl flags can be passed as additional arguments to this // method. If none are provided, the mask will be extracted as hodl.MaskNone. func (h *persistentLinkHarness) restart(restartSwitch, syncStates bool, hodlFlags ...hodl.Flag) func() { // First, remove the link from the switch. h.coreLink.cfg.Switch.RemoveLink(h.link.ChanID()) if restartSwitch { // If a switch restart is requested, we will stop it. It will be // reinstantiated in restartLink. h.coreLink.cfg.Switch.Stop() } // Since our in-memory state may have diverged from our persistent // state, we will restore the persisted state to ensure we always start // the link in a consistent state. var err error h.channel, err = h.restoreChan() if err != nil { h.t.Fatalf("unable to restore channels: %v", err) } // Now, restart the link using the channel state. This will take care of // adding the link to an existing switch, or creating a new one using // the database owned by the link. var cleanUp func() h.link, h.batchTicker, cleanUp, err = h.restartLink( h.channel, restartSwitch, syncStates, hodlFlags, ) if err != nil { h.t.Fatalf("unable to restart alicelink: %v", err) } // Repopulate the remaining fields in the harness. h.coreLink = h.link.(*channelLink) h.msgs = h.coreLink.cfg.Peer.(*mockPeer).sentMsgs return cleanUp } // checkSent reads the links message stream and verify that the messages are // dequeued in the same order as provided by `pkts`. func (h *persistentLinkHarness) checkSent(pkts []*htlcPacket) { for _, pkt := range pkts { var msg lnwire.Message select { case msg = <-h.msgs: case <-time.After(15 * time.Second): h.t.Fatalf("did not receive message") } if !reflect.DeepEqual(msg, pkt.htlc) { h.t.Fatalf("unexpected packet, want %v, got %v", pkt.htlc, msg) } } } // commitCircuits accepts a list of circuits and tries to commit them to the // switch's circuit map. The forwarding actions are returned if there was no // failure. func (h *persistentLinkHarness) commitCircuits(circuits []*PaymentCircuit) *CircuitFwdActions { fwdActions, err := h.coreLink.cfg.Switch.commitCircuits(circuits...) if err != nil { h.t.Fatalf("unable to commit circuit: %v", err) } return fwdActions } func (h *persistentLinkHarness) assertNumPendingNumOpenCircuits( wantPending, wantOpen int) { _, _, line, _ := runtime.Caller(1) numPending := h.coreLink.cfg.Switch.circuits.NumPending() if numPending != wantPending { h.t.Fatalf("line: %d: wrong number of pending circuits: "+ "want %d, got %d", line, wantPending, numPending) } numOpen := h.coreLink.cfg.Switch.circuits.NumOpen() if numOpen != wantOpen { h.t.Fatalf("line: %d: wrong number of open circuits: "+ "want %d, got %d", line, wantOpen, numOpen) } } // trySignNextCommitment signals the batch ticker so that the link will try to // update its commitment transaction. func (h *persistentLinkHarness) trySignNextCommitment() { select { case h.batchTicker <- time.Now(): // Give the link enough time to process the request. time.Sleep(time.Millisecond * 500) case <-time.After(15 * time.Second): h.t.Fatalf("did not initiate state transition") } } // restartLink creates a new channel link from the given channel state, and adds // to an htlcswitch. If none is provided by the caller, a new one will be // created using Alice's database. func (h *persistentLinkHarness) restartLink( aliceChannel *lnwallet.LightningChannel, restartSwitch, syncStates bool, hodlFlags []hodl.Flag) ( ChannelLink, chan time.Time, func(), error) { var ( decoder = newMockIteratorDecoder() obfuscator = NewMockObfuscator() alicePeer = &mockPeer{ sentMsgs: make(chan lnwire.Message, 2000), quit: make(chan struct{}), } globalPolicy = ForwardingPolicy{ MinHTLCOut: lnwire.NewMSatFromSatoshis(5), BaseFee: lnwire.NewMSatFromSatoshis(1), TimeLockDelta: 6, } pCache = newMockPreimageCache() ) aliceDb := aliceChannel.State().Db aliceSwitch := h.coreLink.cfg.Switch if restartSwitch { var err error aliceSwitch, err = initSwitchWithDB(testStartingHeight, aliceDb) if err != nil { return nil, nil, nil, err } } // Instantiate with a long interval, so that we can precisely control // the firing via force feeding. bticker := ticker.NewForce(time.Hour) aliceCfg := ChannelLinkConfig{ FwrdingPolicy: globalPolicy, Peer: alicePeer, Switch: aliceSwitch, Circuits: aliceSwitch.CircuitModifier(), ForwardPackets: aliceSwitch.ForwardPackets, DecodeHopIterators: decoder.DecodeHopIterators, ExtractErrorEncrypter: func(*btcec.PublicKey) ( hop.ErrorEncrypter, lnwire.FailCode) { return obfuscator, lnwire.CodeNone }, FetchLastChannelUpdate: mockGetChanUpdateMessage, PreimageCache: pCache, OnChannelFailure: func(lnwire.ChannelID, lnwire.ShortChannelID, LinkFailureError) { }, UpdateContractSignals: func(*contractcourt.ContractSignals) error { return nil }, Registry: h.coreLink.cfg.Registry, FeeEstimator: newMockFeeEstimator(), ChainEvents: &contractcourt.ChainEventSubscription{}, BatchTicker: bticker, FwdPkgGCTicker: ticker.New(5 * time.Second), PendingCommitTicker: ticker.New(time.Minute), // Make the BatchSize and Min/MaxFeeUpdateTimeout large enough // to not trigger commit updates automatically during tests. BatchSize: 10000, MinFeeUpdateTimeout: 30 * time.Minute, MaxFeeUpdateTimeout: 40 * time.Minute, // Set any hodl flags requested for the new link. HodlMask: hodl.MaskFromFlags(hodlFlags...), MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, MaxFeeAllocation: DefaultMaxLinkFeeAllocation, NotifyActiveLink: func(wire.OutPoint) {}, NotifyActiveChannel: func(wire.OutPoint) {}, NotifyInactiveChannel: func(wire.OutPoint) {}, HtlcNotifier: aliceSwitch.cfg.HtlcNotifier, SyncStates: syncStates, } aliceLink := NewChannelLink(aliceCfg, aliceChannel) if err := aliceSwitch.AddLink(aliceLink); err != nil { return nil, nil, nil, err } go func() { for { select { case <-aliceLink.(*channelLink).htlcUpdates: case <-aliceLink.(*channelLink).quit: return } } }() cleanUp := func() { close(alicePeer.quit) defer aliceLink.Stop() } return aliceLink, bticker.Force, cleanUp, nil } // gnerateHtlc generates a simple payment from Bob to Alice. func generateHtlc(t *testing.T, coreLink *channelLink, id uint64) *lnwire.UpdateAddHTLC { t.Helper() htlc, invoice := generateHtlcAndInvoice(t, id) // We must add the invoice to the registry, such that Alice // expects this payment. err := coreLink.cfg.Registry.(*mockInvoiceRegistry).AddInvoice( *invoice, htlc.PaymentHash, ) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } return htlc } // generateHtlcAndInvoice generates an invoice and a single hop htlc to send to // the receiver. func generateHtlcAndInvoice(t *testing.T, id uint64) (*lnwire.UpdateAddHTLC, *channeldb.Invoice) { t.Helper() htlcAmt := lnwire.NewMSatFromSatoshis(10000) htlcExpiry := testStartingHeight + testInvoiceCltvExpiry hops := []*hop.Payload{ hop.NewLegacyPayload(&sphinx.HopData{ Realm: [1]byte{}, // hop.BitcoinNetwork NextAddress: [8]byte{}, // hop.Exit, ForwardAmount: uint64(htlcAmt), OutgoingCltv: uint32(htlcExpiry), }), } blob, err := generateRoute(hops...) if err != nil { t.Fatalf("unable to generate route: %v", err) } invoice, htlc, _, err := generatePayment( htlcAmt, htlcAmt, uint32(htlcExpiry), blob, ) if err != nil { t.Fatalf("unable to create payment: %v", err) } htlc.ID = id return htlc, invoice } // TestChannelLinkNoMoreUpdates tests that we won't send a new commitment // when there are no new updates to sign. func TestChannelLinkNoMoreUpdates(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, _, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) // Add two HTLCs to Alice's registry, that Bob can pay. htlc1 := generateHtlc(t, coreLink, 0) htlc2 := generateHtlc(t, coreLink, 1) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } // We now play out the following scanario: // // (1) Alice receives htlc1 from Bob. // (2) Bob sends signature covering htlc1. // (3) Alice receives htlc2 from Bob. // (4) Since Bob has sent a new commitment signature, Alice should // first respond with a revocation. // (5) Alice should also send a commitment signature for the new state, // covering htlc1. // (6) Bob sends a new commitment signature, covering htlc2 that he sent // earlier. This signature should cover hltc1 + htlc2. // (7) Alice should revoke the old commitment. This ACKs htlc2. // (8) Bob can now revoke his old commitment in response to the // signature Alice sent covering htlc1. // (9) htlc1 is now locked in on Bob's commitment, and we expect Alice // to settle it. // (10) Alice should send a signature covering this settle to Bob. Only // htlc2 should now be covered by this signature. // (11) Bob can revoke his last state, which will also ACK the settle // of htlc1. // (12) Bob sends a new commitment signature. This signature should // cover htlc2. // (13) Alice will send a settle for htlc2. // (14) Alice will also send a signature covering the settle. // (15) Alice should send a revocation in response to the signature Bob // sent earlier. // (16) Bob will revoke his commitment in response to the commitment // Alice sent. // (17) Send a signature for the empty state. No HTLCs are left. // (18) Alice will revoke her previous state. // Alice Bob // | | // | ... | // | | <--- idle (no htlc on either side) // | | ctx.sendHtlcBobToAlice(htlc1) // |<----- add-1 ------| (1) ctx.sendCommitSigBobToAlice(1) // |<------ sig -------| (2) ctx.sendHtlcBobToAlice(htlc2) // |<----- add-2 ------| (3) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (4) <--- Alice acks add-1 ctx.receiveCommitSigAliceToBob(1) // |------- sig ------>| (5) <--- Alice signs add-1 ctx.sendCommitSigBobToAlice(2) // |<------ sig -------| (6) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (7) <--- Alice acks add-2 ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (8) ctx.receiveSettleAliceToBob() // |------ ful-1 ----->| (9) ctx.receiveCommitSigAliceToBob(1) // |------- sig ------>| (10) <--- Alice signs add-1 + add-2 + ful-1 = add-2 ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (11) ctx.sendCommitSigBobToAlice(1) // |<------ sig -------| (12) ctx.receiveSettleAliceToBob() // |------ ful-2 ----->| (13) ctx.receiveCommitSigAliceToBob(0) // |------- sig ------>| (14) <--- Alice signs add-2 + ful-2 = no htlcs ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (15) ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (16) <--- Bob acks that there are no more htlcs ctx.sendCommitSigBobToAlice(0) // |<------ sig -------| (17) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (18) <--- Alice acks that there are no htlcs on Alice's side // No there are no more changes to ACK or sign, make sure Alice doesn't // attempt to send any more messages. var msg lnwire.Message select { case msg = <-aliceMsgs: t.Fatalf("did not expect message %T", msg) case <-time.After(100 * time.Millisecond): } } // checkHasPreimages inspects Alice's preimage cache, and asserts whether the // preimages for the provided HTLCs are known and unknown, and that all of them // match the expected status of expOk. func checkHasPreimages(t *testing.T, coreLink *channelLink, htlcs []*lnwire.UpdateAddHTLC, expOk bool) { t.Helper() err := wait.NoError(func() error { for i := range htlcs { _, ok := coreLink.cfg.PreimageCache.LookupPreimage( htlcs[i].PaymentHash, ) if ok == expOk { continue } return fmt.Errorf("expected to find witness: %v, "+ "got %v for hash=%x", expOk, ok, htlcs[i].PaymentHash) } return nil }, 5*time.Second) if err != nil { t.Fatalf("unable to find preimages: %v", err) } } // TestChannelLinkWaitForRevocation tests that we will keep accepting updates // to our commitment transaction, even when we are waiting for a revocation // from the remote node. func TestChannelLinkWaitForRevocation(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, _, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) // We will send 10 HTLCs in total, from Bob to Alice. numHtlcs := 10 var htlcs []*lnwire.UpdateAddHTLC for i := 0; i < numHtlcs; i++ { htlc := generateHtlc(t, coreLink, uint64(i)) htlcs = append(htlcs, htlc) } ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } assertNoMsgFromAlice := func() { select { case <-aliceMsgs: t.Fatalf("did not expect message from Alice") case <-time.After(50 * time.Millisecond): } } // We play out the following scenario: // // (1) Add the first HTLC. // (2) Bob sends signature covering the htlc. // (3) Since Bob has sent a new commitment signature, Alice should first // respond with a revocation. This revocation will ACK the first htlc. // (4) Alice should also send a commitment signature for the new state, // locking in the HTLC on Bob's commitment. Note that we don't // immediately let Bob respond with a revocation in this case. // (5.i) Now we send the rest of the HTLCs from Bob to Alice. // (6.i) Bob sends a new commitment signature, covering all HTLCs up // to this point. // (7.i) Alice should respond to Bob's state updates with revocations, // but cannot send any new signatures for Bob's state because her // revocation window is exhausted. // (8) Now let Bob finally send his revocation. // (9) We expect Alice to settle her first HTLC, since it was already // locked in. // (10) Now Alice should send a signature covering this settle + lock // in the rest of the HTLCs on Bob's commitment. // (11) Bob receives the new signature for his commitment, and can // revoke his old state, ACKing the settle. // (12.i) Now Alice can settle all the HTLCs, since they are locked in // on both parties' commitments. // (13) Bob can send a signature covering the first settle Alice sent. // Bob's signature should cover all the remaining HTLCs as well, since // he hasn't ACKed the last settles yet. Alice receives the signature // from Bob. Alice's commitment now has the first HTLC settled, and all // the other HTLCs locked in. // (14) Alice will send a signature for all the settles she just sent. // (15) Bob can revoke his previous state, in response to Alice's // signature. // (16) In response to the signature Bob sent, Alice can // revoke her previous state. // (17) Bob still hasn't sent a commitment covering all settles, so do // that now. Since Bob ACKed all settles, no HTLCs should be left on // the commitment. // (18) Alice will revoke her previous state. // Alice Bob // | | // | ... | // | | <--- idle (no htlc on either side) // | | ctx.sendHtlcBobToAlice(htlcs[0]) // |<----- add-1 ------| (1) ctx.sendCommitSigBobToAlice(1) // |<------ sig -------| (2) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (3) <--- Alice acks add-1 ctx.receiveCommitSigAliceToBob(1) // |------- sig ------>| (4) <--- Alice signs add-1 for i := 1; i < numHtlcs; i++ { // | | ctx.sendHtlcBobToAlice(htlcs[i]) // |<----- add-i ------| (5.i) ctx.sendCommitSigBobToAlice(i + 1) // |<------ sig -------| (6.i) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (7.i) <--- Alice acks add-i assertNoMsgFromAlice() // | | // | | Alice should not send a sig for // | | Bob's last state, since she is // | | still waiting for a revocation // | | for the previous one. } // | | ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (8) Finally let Bob send rev ctx.receiveSettleAliceToBob() // |------ ful-1 ----->| (9) ctx.receiveCommitSigAliceToBob(numHtlcs - 1) // |------- sig ------>| (10) <--- Alice signs add-i ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (11) for i := 1; i < numHtlcs; i++ { // | | ctx.receiveSettleAliceToBob() // |------ ful-1 ----->| (12.i) } // | | ctx.sendCommitSigBobToAlice(numHtlcs - 1) // |<------ sig -------| (13) ctx.receiveCommitSigAliceToBob(0) // |------- sig ------>| (14) ctx.sendRevAndAckBobToAlice() // |<------ rev -------| (15) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (16) ctx.sendCommitSigBobToAlice(0) // |<------ sig -------| (17) ctx.receiveRevAndAckAliceToBob() // |------- rev ------>| (18) // Both side's state is now updated, no more messages should be sent. assertNoMsgFromAlice() } // TestChannelLinkNoEmptySig asserts that no empty commit sig message is sent // when the commitment txes are out of sync. func TestChannelLinkNoEmptySig(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } defer aliceLink.Stop() var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } // Send htlc 1 from Alice to Bob. htlc1, _ := generateHtlcAndInvoice(t, 0) ctx.sendHtlcAliceToBob(0, htlc1) ctx.receiveHtlcAliceToBob() // Tick the batch ticker to trigger a commitsig from Alice->Bob. select { case batchTicker <- time.Now(): case <-time.After(5 * time.Second): t.Fatalf("could not force commit sig") } // Receive a CommitSig from Alice covering the Add from above. ctx.receiveCommitSigAliceToBob(1) // Bob revokes previous commitment tx. ctx.sendRevAndAckBobToAlice() // Alice sends htlc 2 to Bob. htlc2, _ := generateHtlcAndInvoice(t, 0) ctx.sendHtlcAliceToBob(1, htlc2) ctx.receiveHtlcAliceToBob() // Tick the batch ticker to trigger a commitsig from Alice->Bob. select { case batchTicker <- time.Now(): case <-time.After(5 * time.Second): t.Fatalf("could not force commit sig") } // Get the commit sig from Alice, but don't send it to Bob yet. commitSigAlice := ctx.receiveCommitSigAlice(2) // Bob adds htlc 1 to its remote commit tx. ctx.sendCommitSigBobToAlice(1) // Now send Bob the signature from Alice covering both htlcs. err = bobChannel.ReceiveNewCommitment( commitSigAlice.CommitSig, commitSigAlice.HtlcSigs, ) if err != nil { t.Fatalf("bob failed receiving commitment: %v", err) } // Both Alice and Bob revoke their previous commitment txes. ctx.receiveRevAndAckAliceToBob() ctx.sendRevAndAckBobToAlice() // The commit txes are not in sync, but it is Bob's turn to send a new // signature. We don't expect Alice to send out any message. This check // allows some time for the log commit ticker to trigger for Alice. ctx.assertNoMsgFromAlice(time.Second) } // TestChannelLinkBatchPreimageWrite asserts that a link will batch preimage // writes when just as it receives a CommitSig to lock in any Settles, and also // if the link is aware of any uncommitted preimages if the link is stopped, // i.e. due to a disconnection or shutdown. func TestChannelLinkBatchPreimageWrite(t *testing.T) { t.Parallel() tests := []struct { name string disconnect bool }{ { name: "flush on commit sig", disconnect: false, }, { name: "flush on disconnect", disconnect: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { testChannelLinkBatchPreimageWrite(t, test.disconnect) }) } } func testChannelLinkBatchPreimageWrite(t *testing.T, disconnect bool) { const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, startUp, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := startUp(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) // We will send 10 HTLCs in total, from Bob to Alice. numHtlcs := 10 var htlcs []*lnwire.UpdateAddHTLC var invoices []*channeldb.Invoice for i := 0; i < numHtlcs; i++ { htlc, invoice := generateHtlcAndInvoice(t, uint64(i)) htlcs = append(htlcs, htlc) invoices = append(invoices, invoice) } ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } // First, send a batch of Adds from Alice to Bob. for i, htlc := range htlcs { ctx.sendHtlcAliceToBob(i, htlc) ctx.receiveHtlcAliceToBob() } // Assert that no preimages exist for these htlcs in Alice's cache. checkHasPreimages(t, coreLink, htlcs, false) // Force alice's link to sign a commitment covering the htlcs sent thus // far. select { case batchTicker <- time.Now(): case <-time.After(15 * time.Second): t.Fatalf("could not force commit sig") } // Do a commitment dance to lock in the Adds, we expect numHtlcs htlcs // to be on each party's commitment transactions. ctx.receiveCommitSigAliceToBob(numHtlcs) ctx.sendRevAndAckBobToAlice() ctx.sendCommitSigBobToAlice(numHtlcs) ctx.receiveRevAndAckAliceToBob() // Check again that no preimages exist for these htlcs in Alice's cache. checkHasPreimages(t, coreLink, htlcs, false) // Now, have Bob settle the HTLCs back to Alice using the preimages in // the invoice corresponding to each of the HTLCs. for i, invoice := range invoices { ctx.sendSettleBobToAlice( uint64(i), *invoice.Terms.PaymentPreimage, ) } // Assert that Alice has not yet written the preimages, even though she // has received them in the UpdateFulfillHTLC messages. checkHasPreimages(t, coreLink, htlcs, false) // If this is the disconnect run, we will having Bob send Alice his // CommitSig, and simply stop Alice's link. As she exits, we should // detect that she has uncommitted preimages and write them to disk. if disconnect { aliceLink.Stop() checkHasPreimages(t, coreLink, htlcs, true) return } // Otherwise, we are testing that Alice commits the preimages after // receiving a CommitSig from Bob. Bob's commitment should now have 0 // HTLCs. ctx.sendCommitSigBobToAlice(0) // Since Alice will process the CommitSig asynchronously, we wait until // she replies with her RevokeAndAck to ensure the tests reliably // inspect her cache after advancing her state. select { // Received Alice's RevokeAndAck, assert that she has written all of the // uncommitted preimages learned in this commitment. case <-aliceMsgs: checkHasPreimages(t, coreLink, htlcs, true) // Alice didn't send her RevokeAndAck, something is wrong. case <-time.After(15 * time.Second): t.Fatalf("alice did not send her revocation") } } // TestChannelLinkCleanupSpuriousResponses tests that we properly cleanup // references in the event that internal retransmission continues as a result of // not properly cleaning up Add/SettleFailRefs. func TestChannelLinkCleanupSpuriousResponses(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, _, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) // Settle Alice in hodl ExitSettle mode so that she won't respond // immediately to the htlc's meant for her. This allows us to control // the responses she gives back to Bob. coreLink.cfg.HodlMask = hodl.ExitSettle.Mask() // Add two HTLCs to Alice's registry, that Bob can pay. htlc1 := generateHtlc(t, coreLink, 0) htlc2 := generateHtlc(t, coreLink, 1) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } // We start with he following scenario: Bob sends Alice two HTLCs, and a // commitment dance ensures, leaving two HTLCs that Alice can respond // to. Since Alice is in ExitSettle mode, we will then take over and // provide targeted fail messages to test the link's ability to cleanup // spurious responses. // // Bob Alice // |------ add-1 ----->| // |------ add-2 ----->| // |------ sig ----->| commits add-1 + add-2 // |<----- rev ------| // |<----- sig ------| commits add-1 + add-2 // |------ rev ----->| ctx.sendHtlcBobToAlice(htlc1) ctx.sendHtlcBobToAlice(htlc2) ctx.sendCommitSigBobToAlice(2) ctx.receiveRevAndAckAliceToBob() ctx.receiveCommitSigAliceToBob(2) ctx.sendRevAndAckBobToAlice() // Give Alice to time to process the revocation. time.Sleep(time.Second) aliceFwdPkgs, err := coreLink.channel.LoadFwdPkgs() if err != nil { t.Fatalf("unable to load alice's fwdpkgs: %v", err) } // Alice should have exactly one forwarding package. if len(aliceFwdPkgs) != 1 { t.Fatalf("alice should have 1 fwd pkgs, has %d instead", len(aliceFwdPkgs)) } // We'll stash the height of these AddRefs, so that we can reconstruct // the proper references later. addHeight := aliceFwdPkgs[0].Height // The first fwdpkg should have exactly 2 entries, one for each Add that // was added during the last dance. if aliceFwdPkgs[0].AckFilter.Count() != 2 { t.Fatalf("alice fwdpkg should have 2 Adds, has %d instead", aliceFwdPkgs[0].AckFilter.Count()) } // Both of the entries in the FwdFilter should be unacked. for i := 0; i < 2; i++ { if aliceFwdPkgs[0].AckFilter.Contains(uint16(i)) { t.Fatalf("alice fwdpkg index %d should not "+ "have ack", i) } } // Now, construct a Fail packet for Bob settling the first HTLC. This // packet will NOT include a sourceRef, meaning the AddRef on disk will // not be acked after committing this response. fail0 := &htlcPacket{ incomingChanID: bobChannel.ShortChanID(), incomingHTLCID: 0, obfuscator: NewMockObfuscator(), htlc: &lnwire.UpdateFailHTLC{}, } aliceLink.HandleSwitchPacket(fail0) // Bob Alice // |<----- fal-1 ------| // |<----- sig ------| commits fal-1 ctx.receiveFailAliceToBob() ctx.receiveCommitSigAliceToBob(1) aliceFwdPkgs, err = coreLink.channel.LoadFwdPkgs() if err != nil { t.Fatalf("unable to load alice's fwdpkgs: %v", err) } // Alice should still only have one fwdpkg, as she hasn't yet received // another revocation from Bob. if len(aliceFwdPkgs) != 1 { t.Fatalf("alice should have 1 fwd pkgs, has %d instead", len(aliceFwdPkgs)) } // Assert the fwdpkg still has 2 entries for the original Adds. if aliceFwdPkgs[0].AckFilter.Count() != 2 { t.Fatalf("alice fwdpkg should have 2 Adds, has %d instead", aliceFwdPkgs[0].AckFilter.Count()) } // Since the fail packet was missing the AddRef, the forward filter for // either HTLC should not have been modified. for i := 0; i < 2; i++ { if aliceFwdPkgs[0].AckFilter.Contains(uint16(i)) { t.Fatalf("alice fwdpkg index %d should not "+ "have ack", i) } } // Complete the rest of the commitment dance, now that the forwarding // packages have been verified. // // Bob Alice // |------ rev ----->| // |------ sig ----->| // |<----- rev ------| ctx.sendRevAndAckBobToAlice() ctx.sendCommitSigBobToAlice(1) ctx.receiveRevAndAckAliceToBob() // Next, we'll construct a fail packet for add-2 (index 1), which we'll // send to Bob and lock in. Since the AddRef is set on this instance, we // should see the second HTLCs AddRef update the forward filter for the // first fwd pkg. fail1 := &htlcPacket{ sourceRef: &channeldb.AddRef{ Height: addHeight, Index: 1, }, incomingChanID: bobChannel.ShortChanID(), incomingHTLCID: 1, obfuscator: NewMockObfuscator(), htlc: &lnwire.UpdateFailHTLC{}, } aliceLink.HandleSwitchPacket(fail1) // Bob Alice // |<----- fal-1 ------| // |<----- sig ------| commits fal-1 ctx.receiveFailAliceToBob() ctx.receiveCommitSigAliceToBob(0) aliceFwdPkgs, err = coreLink.channel.LoadFwdPkgs() if err != nil { t.Fatalf("unable to load alice's fwdpkgs: %v", err) } // Now that another commitment dance has completed, Alice should have 2 // forwarding packages. if len(aliceFwdPkgs) != 2 { t.Fatalf("alice should have 2 fwd pkgs, has %d instead", len(aliceFwdPkgs)) } // The most recent package should have no new HTLCs, so it should be // empty. if aliceFwdPkgs[1].AckFilter.Count() != 0 { t.Fatalf("alice fwdpkg height=%d should have 0 Adds, "+ "has %d instead", aliceFwdPkgs[1].Height, aliceFwdPkgs[1].AckFilter.Count()) } // The index for the first AddRef should still be unacked, as the // sourceRef was missing on the htlcPacket. if aliceFwdPkgs[0].AckFilter.Contains(0) { t.Fatalf("alice fwdpkg height=%d index=0 should not "+ "have an ack", aliceFwdPkgs[0].Height) } // The index for the second AddRef should now be acked, as it was // properly constructed and committed in Alice's last commit sig. if !aliceFwdPkgs[0].AckFilter.Contains(1) { t.Fatalf("alice fwdpkg height=%d index=1 should have "+ "an ack", aliceFwdPkgs[0].Height) } // Complete the rest of the commitment dance. // // Bob Alice // |------ rev ----->| // |------ sig ----->| // |<----- rev ------| ctx.sendRevAndAckBobToAlice() ctx.sendCommitSigBobToAlice(0) ctx.receiveRevAndAckAliceToBob() // We'll do a quick sanity check, and blindly send the same fail packet // for the first HTLC. Since this HTLC index has already been settled, // this should trigger an attempt to cleanup the spurious response. // However, we expect it to result in a NOP since it is still missing // its sourceRef. aliceLink.HandleSwitchPacket(fail0) // Allow the link enough time to process and reject the duplicate // packet, we'll also check that this doesn't trigger Alice to send the // fail to Bob. select { case <-aliceMsgs: t.Fatalf("message sent for duplicate fail") case <-time.After(time.Second): } aliceFwdPkgs, err = coreLink.channel.LoadFwdPkgs() if err != nil { t.Fatalf("unable to load alice's fwdpkgs: %v", err) } // Alice should now have 3 forwarding packages, and the latest should be // empty. if len(aliceFwdPkgs) != 3 { t.Fatalf("alice should have 3 fwd pkgs, has %d instead", len(aliceFwdPkgs)) } if aliceFwdPkgs[2].AckFilter.Count() != 0 { t.Fatalf("alice fwdpkg height=%d should have 0 Adds, "+ "has %d instead", aliceFwdPkgs[2].Height, aliceFwdPkgs[2].AckFilter.Count()) } // The state of the forwarding packages should be unmodified from the // prior assertion, since the duplicate Fail for index 0 should have // been ignored. if aliceFwdPkgs[0].AckFilter.Contains(0) { t.Fatalf("alice fwdpkg height=%d index=0 should not "+ "have an ack", aliceFwdPkgs[0].Height) } if !aliceFwdPkgs[0].AckFilter.Contains(1) { t.Fatalf("alice fwdpkg height=%d index=1 should have "+ "an ack", aliceFwdPkgs[0].Height) } // Finally, construct a new Fail packet for the first HTLC, this time // with the sourceRef properly constructed. When the link handles this // duplicate, it should clean up the remaining AddRef state maintained // in Alice's link, but it should not result in anything being sent to // Bob. fail0 = &htlcPacket{ sourceRef: &channeldb.AddRef{ Height: addHeight, Index: 0, }, incomingChanID: bobChannel.ShortChanID(), incomingHTLCID: 0, obfuscator: NewMockObfuscator(), htlc: &lnwire.UpdateFailHTLC{}, } aliceLink.HandleSwitchPacket(fail0) // Allow the link enough time to process and reject the duplicate // packet, we'll also check that this doesn't trigger Alice to send the // fail to Bob. select { case <-aliceMsgs: t.Fatalf("message sent for duplicate fail") case <-time.After(time.Second): } aliceFwdPkgs, err = coreLink.channel.LoadFwdPkgs() if err != nil { t.Fatalf("unable to load alice's fwdpkgs: %v", err) } // Since no state transitions have been performed for the duplicate // packets, Alice should still have the same 3 forwarding packages. if len(aliceFwdPkgs) != 3 { t.Fatalf("alice should have 3 fwd pkgs, has %d instead", len(aliceFwdPkgs)) } // Assert that all indices in our original forwarded have now been acked // as a result of our spurious cleanup logic. for i := 0; i < 2; i++ { if !aliceFwdPkgs[0].AckFilter.Contains(uint16(i)) { t.Fatalf("alice fwdpkg height=%d index=%d "+ "should have ack", aliceFwdPkgs[0].Height, i) } } } type mockPackager struct { failLoadFwdPkgs bool } func (*mockPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *channeldb.FwdPkg) error { return nil } func (*mockPackager) SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *channeldb.PkgFilter) error { return nil } func (*mockPackager) AckAddHtlcs(tx kvdb.RwTx, addRefs ...channeldb.AddRef) error { return nil } func (m *mockPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*channeldb.FwdPkg, error) { if m.failLoadFwdPkgs { return nil, fmt.Errorf("failing LoadFwdPkgs") } return nil, nil } func (*mockPackager) RemovePkg(tx kvdb.RwTx, height uint64) error { return nil } func (*mockPackager) AckSettleFails(tx kvdb.RwTx, settleFailRefs ...channeldb.SettleFailRef) error { return nil } // TestChannelLinkFail tests that we will fail the channel, and force close the // channel in certain situations. func TestChannelLinkFail(t *testing.T) { t.Parallel() testCases := []struct { // options is used to set up mocks and configure the link // before it is started. options func(*channelLink) // link test is used to execute the given test on the channel // link after it is started. linkTest func(*testing.T, *channelLink, *lnwallet.LightningChannel) // shouldForceClose indicates whether we expect the link to // force close the channel in response to the actions performed // during the linkTest. shouldForceClose bool // permanentFailure indicates whether we expect the link to // consider the failure permanent in response to the actions // performed during the linkTest. permanentFailure bool }{ { // Test that we don't force close if syncing states // fails at startup. func(c *channelLink) { c.cfg.SyncStates = true // Make the syncChanStateCall fail by making // the SendMessage call fail. c.cfg.Peer.(*mockPeer).disconnected = true }, func(t *testing.T, c *channelLink, _ *lnwallet.LightningChannel) { // Should fail at startup. }, false, false, }, { // Test that we don't force closes the channel if // resolving forward packages fails at startup. func(c *channelLink) { // We make the call to resolveFwdPkgs fail by // making the underlying forwarder fail. pkg := &mockPackager{ failLoadFwdPkgs: true, } c.channel.State().Packager = pkg }, func(t *testing.T, c *channelLink, _ *lnwallet.LightningChannel) { // Should fail at startup. }, false, false, }, { // Test that we force close the channel if we receive // an invalid Settle message. func(c *channelLink) { }, func(t *testing.T, c *channelLink, _ *lnwallet.LightningChannel) { // Recevive an htlc settle for an htlc that was // never added. htlcSettle := &lnwire.UpdateFulfillHTLC{ ID: 0, PaymentPreimage: [32]byte{}, } c.HandleChannelUpdate(htlcSettle) }, true, false, }, { // Test that we force close the channel if we receive // an invalid CommitSig, not containing enough HTLC // sigs. func(c *channelLink) { }, func(t *testing.T, c *channelLink, remoteChannel *lnwallet.LightningChannel) { // Generate an HTLC and send to the link. htlc1 := generateHtlc(t, c, 0) ctx := linkTestContext{ t: t, aliceLink: c, bobChannel: remoteChannel, } ctx.sendHtlcBobToAlice(htlc1) // Sign a commitment that will include // signature for the HTLC just sent. sig, htlcSigs, _, err := remoteChannel.SignNextCommitment() if err != nil { t.Fatalf("error signing commitment: %v", err) } // Remove the HTLC sig, such that the commit // sig will be invalid. commitSig := &lnwire.CommitSig{ CommitSig: sig, HtlcSigs: htlcSigs[1:], } c.HandleChannelUpdate(commitSig) }, true, false, }, { // Test that we force close the channel if we receive // an invalid CommitSig, where the sig itself is // corrupted. func(c *channelLink) { }, func(t *testing.T, c *channelLink, remoteChannel *lnwallet.LightningChannel) { // Generate an HTLC and send to the link. htlc1 := generateHtlc(t, c, 0) ctx := linkTestContext{ t: t, aliceLink: c, bobChannel: remoteChannel, } ctx.sendHtlcBobToAlice(htlc1) // Sign a commitment that will include // signature for the HTLC just sent. sig, htlcSigs, _, err := remoteChannel.SignNextCommitment() if err != nil { t.Fatalf("error signing commitment: %v", err) } // Flip a bit on the signature, rendering it // invalid. sig[19] ^= 1 commitSig := &lnwire.CommitSig{ CommitSig: sig, HtlcSigs: htlcSigs, } c.HandleChannelUpdate(commitSig) }, true, false, }, { // Test that we consider the failure permanent if we // receive a link error from the remote. func(c *channelLink) { }, func(t *testing.T, c *channelLink, remoteChannel *lnwallet.LightningChannel) { err := &lnwire.Error{} c.HandleChannelUpdate(err) }, false, // TODO(halseth) For compatibility with CL we currently // don't treat Errors as permanent errors. false, }, } const chanAmt = btcutil.SatoshiPerBitcoin * 5 // Execute each test case. for i, test := range testCases { link, remoteChannel, _, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } coreLink := link.(*channelLink) // Set up a channel used to check whether the link error // force closed the channel. linkErrors := make(chan LinkFailureError, 1) coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID, _ lnwire.ShortChannelID, linkErr LinkFailureError) { linkErrors <- linkErr } // Set up the link before starting it. test.options(coreLink) if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } // Execute the test case. test.linkTest(t, coreLink, remoteChannel) // Currently we expect all test cases to lead to link error. var linkErr LinkFailureError select { case linkErr = <-linkErrors: case <-time.After(10 * time.Second): t.Fatalf("%d) Alice did not fail"+ "channel", i) } // If we expect the link to force close the channel in this // case, check that it happens. If not, make sure it does not // happen. if test.shouldForceClose != linkErr.ForceClose { t.Fatalf("%d) Expected Alice to force close(%v), "+ "instead got(%v)", i, test.shouldForceClose, linkErr.ForceClose) } if test.permanentFailure != linkErr.PermanentFailure { t.Fatalf("%d) Expected Alice set permanent failure(%v), "+ "instead got(%v)", i, test.permanentFailure, linkErr.PermanentFailure) } // Clean up before starting next test case. cleanUp() } } // TestExpectedFee tests calculation of ExpectedFee returns expected fee, given // a baseFee, a feeRate, and an htlc amount. func TestExpectedFee(t *testing.T) { testCases := []struct { baseFee lnwire.MilliSatoshi feeRate lnwire.MilliSatoshi htlcAmt lnwire.MilliSatoshi expected lnwire.MilliSatoshi }{ { lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(0), }, { lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(1), lnwire.MilliSatoshi(999999), lnwire.MilliSatoshi(0), }, { lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(1), lnwire.MilliSatoshi(1000000), lnwire.MilliSatoshi(1), }, { lnwire.MilliSatoshi(0), lnwire.MilliSatoshi(1), lnwire.MilliSatoshi(1000001), lnwire.MilliSatoshi(1), }, { lnwire.MilliSatoshi(1), lnwire.MilliSatoshi(1), lnwire.MilliSatoshi(1000000), lnwire.MilliSatoshi(2), }, } for _, test := range testCases { f := ForwardingPolicy{ BaseFee: test.baseFee, FeeRate: test.feeRate, } fee := ExpectedFee(f, test.htlcAmt) if fee != test.expected { t.Errorf("expected fee to be (%v), instead got (%v)", test.expected, fee) } } } // TestForwardingAsymmetricTimeLockPolicies tests that each link is able to // properly handle forwarding HTLCs when their outgoing channels have // asymmetric policies w.r.t what they require for time locks. func TestForwardingAsymmetricTimeLockPolicies(t *testing.T) { t.Parallel() // First, we'll create our traditional three hop network. Bob // interacting with and asserting the state of two of the end points // for this test. channels, cleanUp, _, err := createClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5, ) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newThreeHopNetwork( t, channels.aliceToBob, channels.bobToAlice, channels.bobToCarol, channels.carolToBob, testStartingHeight, ) if err := n.start(); err != nil { t.Fatalf("unable to start three hop network: %v", err) } defer n.stop() // Now that each of the links are up, we'll modify the link from Alice // -> Bob to have a greater time lock delta than that of the link of // Bob -> Carol. newPolicy := n.firstBobChannelLink.cfg.FwrdingPolicy newPolicy.TimeLockDelta = 7 n.firstBobChannelLink.UpdateForwardingPolicy(newPolicy) // Now that the Alice -> Bob link has been updated, we'll craft and // send a payment from Alice -> Carol. This should succeed as normal, // even though Bob has asymmetric time lock policies. amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops( amount, testStartingHeight, n.firstBobChannelLink, n.carolChannelLink, ) firstHop := n.firstBobChannelLink.ShortChanID() _, err = makePayment( n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt, totalTimelock, ).Wait(30 * time.Second) if err != nil { t.Fatalf("unable to send payment: %v", err) } } // TestCheckHtlcForward tests that a link is properly enforcing the HTLC // forwarding policy. func TestCheckHtlcForward(t *testing.T) { fetchLastChannelUpdate := func(lnwire.ShortChannelID) ( *lnwire.ChannelUpdate, error) { return &lnwire.ChannelUpdate{}, nil } testChannel, _, fCleanUp, err := createTestChannel( alicePrivKey, bobPrivKey, 100000, 100000, 1000, 1000, lnwire.ShortChannelID{}, ) if err != nil { t.Fatal(err) } defer fCleanUp() link := channelLink{ cfg: ChannelLinkConfig{ FwrdingPolicy: ForwardingPolicy{ TimeLockDelta: 20, MinHTLCOut: 500, MaxHTLC: 1000, BaseFee: 10, }, FetchLastChannelUpdate: fetchLastChannelUpdate, MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, HtlcNotifier: &mockHTLCNotifier{}, }, log: log, channel: testChannel.channel, } var hash [32]byte t.Run("satisfied", func(t *testing.T) { result := link.CheckHtlcForward(hash, 1500, 1000, 200, 150, 0) if result != nil { t.Fatalf("expected policy to be satisfied") } }) t.Run("below minhtlc", func(t *testing.T) { result := link.CheckHtlcForward(hash, 100, 50, 200, 150, 0) if _, ok := result.WireMessage().(*lnwire.FailAmountBelowMinimum); !ok { t.Fatalf("expected FailAmountBelowMinimum failure code") } }) t.Run("above maxhtlc", func(t *testing.T) { result := link.CheckHtlcForward(hash, 1500, 1200, 200, 150, 0) if _, ok := result.WireMessage().(*lnwire.FailTemporaryChannelFailure); !ok { t.Fatalf("expected FailTemporaryChannelFailure failure code") } }) t.Run("insufficient fee", func(t *testing.T) { result := link.CheckHtlcForward(hash, 1005, 1000, 200, 150, 0) if _, ok := result.WireMessage().(*lnwire.FailFeeInsufficient); !ok { t.Fatalf("expected FailFeeInsufficient failure code") } }) t.Run("expiry too soon", func(t *testing.T) { result := link.CheckHtlcForward(hash, 1500, 1000, 200, 150, 190) if _, ok := result.WireMessage().(*lnwire.FailExpiryTooSoon); !ok { t.Fatalf("expected FailExpiryTooSoon failure code") } }) t.Run("incorrect cltv expiry", func(t *testing.T) { result := link.CheckHtlcForward(hash, 1500, 1000, 200, 190, 0) if _, ok := result.WireMessage().(*lnwire.FailIncorrectCltvExpiry); !ok { t.Fatalf("expected FailIncorrectCltvExpiry failure code") } }) t.Run("cltv expiry too far in the future", func(t *testing.T) { // Check that expiry isn't too far in the future. result := link.CheckHtlcForward(hash, 1500, 1000, 10200, 10100, 0) if _, ok := result.WireMessage().(*lnwire.FailExpiryTooFar); !ok { t.Fatalf("expected FailExpiryTooFar failure code") } }) } // TestChannelLinkCanceledInvoice in this test checks the interaction // between Alice and Bob for a canceled invoice. func TestChannelLinkCanceledInvoice(t *testing.T) { t.Parallel() // Setup a alice-bob network. alice, bob, cleanUp, err := createTwoClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5) if err != nil { t.Fatalf("unable to create channel: %v", err) } defer cleanUp() n := newTwoHopNetwork(t, alice.channel, bob.channel, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } defer n.stop() // Prepare an alice -> bob payment. amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight, n.bobChannelLink) firstHop := n.bobChannelLink.ShortChanID() invoice, payFunc, err := preparePayment( n.aliceServer, n.bobServer, firstHop, hops, amount, htlcAmt, totalTimelock, ) if err != nil { t.Fatalf("unable to prepare the payment: %v", err) } // Cancel the invoice at bob's end. hash := invoice.Terms.PaymentPreimage.Hash() err = n.bobServer.registry.CancelInvoice(hash) if err != nil { t.Fatal(err) } // Have Alice fire the payment. err = waitForPayFuncResult(payFunc, 30*time.Second) // Because the invoice is canceled, we expect an unknown payment hash // result. rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected ClearTextError, but got %v", err) } _, ok = rtErr.WireMessage().(*lnwire.FailIncorrectDetails) if !ok { t.Fatalf("expected unknown payment hash, but got %v", err) } } type hodlInvoiceTestCtx struct { n *twoHopNetwork startBandwidthAlice lnwire.MilliSatoshi startBandwidthBob lnwire.MilliSatoshi hash lntypes.Hash preimage lntypes.Preimage amount lnwire.MilliSatoshi errChan chan error restoreBob func() (*lnwallet.LightningChannel, error) cleanUp func() } func newHodlInvoiceTestCtx(t *testing.T) (*hodlInvoiceTestCtx, error) { // Setup a alice-bob network. alice, bob, cleanUp, err := createTwoClusterChannels( btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5, ) if err != nil { t.Fatalf("unable to create channel: %v", err) } n := newTwoHopNetwork(t, alice.channel, bob.channel, testStartingHeight) if err := n.start(); err != nil { t.Fatal(err) } aliceBandwidthBefore := n.aliceChannelLink.Bandwidth() bobBandwidthBefore := n.bobChannelLink.Bandwidth() debug := false if debug { // Log message that alice receives. n.aliceServer.intersect( createLogFunc("alice", n.aliceChannelLink.ChanID()), ) // Log message that bob receives. n.bobServer.intersect( createLogFunc("bob", n.bobChannelLink.ChanID()), ) } amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin) htlcAmt, totalTimelock, hops := generateHops( amount, testStartingHeight, n.bobChannelLink, ) // Generate hold invoice preimage. r, err := generateRandomBytes(sha256.Size) if err != nil { t.Fatal(err) } preimage, err := lntypes.MakePreimage(r) if err != nil { t.Fatal(err) } hash := preimage.Hash() // Have alice pay the hodl invoice, wait for bob's commitment state to // be updated and the invoice state to be updated. receiver := n.bobServer receiver.registry.settleChan = make(chan lntypes.Hash) firstHop := n.bobChannelLink.ShortChanID() errChan := n.makeHoldPayment( n.aliceServer, receiver, firstHop, hops, amount, htlcAmt, totalTimelock, preimage, ) select { case err := <-errChan: t.Fatalf("no payment result expected: %v", err) case <-time.After(5 * time.Second): t.Fatal("timeout") case h := <-receiver.registry.settleChan: if hash != h { t.Fatal("unexpect invoice settled") } } return &hodlInvoiceTestCtx{ n: n, startBandwidthAlice: aliceBandwidthBefore, startBandwidthBob: bobBandwidthBefore, preimage: preimage, hash: hash, amount: amount, errChan: errChan, restoreBob: bob.restore, cleanUp: func() { cleanUp() n.stop() }, }, nil } // TestChannelLinkHoldInvoiceSettle asserts that a hodl invoice can be settled. func TestChannelLinkHoldInvoiceSettle(t *testing.T) { t.Parallel() defer timeout(t)() ctx, err := newHodlInvoiceTestCtx(t) if err != nil { t.Fatal(err) } defer ctx.cleanUp() err = ctx.n.bobServer.registry.SettleHodlInvoice(ctx.preimage) if err != nil { t.Fatal(err) } // Wait for payment to succeed. err = <-ctx.errChan if err != nil { t.Fatal(err) } // Wait for Alice to receive the revocation. This is needed // because the settles are pipelined to the switch and otherwise // the bandwidth won't be updated by the time Alice receives a // response here. time.Sleep(2 * time.Second) if ctx.startBandwidthAlice-ctx.amount != ctx.n.aliceChannelLink.Bandwidth() { t.Fatal("alice bandwidth should have decrease on payment " + "amount") } if ctx.startBandwidthBob+ctx.amount != ctx.n.bobChannelLink.Bandwidth() { t.Fatalf("bob bandwidth isn't match: expected %v, got %v", ctx.startBandwidthBob+ctx.amount, ctx.n.bobChannelLink.Bandwidth()) } } // TestChannelLinkHoldInvoiceSettle asserts that a hodl invoice can be canceled. func TestChannelLinkHoldInvoiceCancel(t *testing.T) { t.Parallel() defer timeout(t)() ctx, err := newHodlInvoiceTestCtx(t) if err != nil { t.Fatal(err) } defer ctx.cleanUp() err = ctx.n.bobServer.registry.CancelInvoice(ctx.hash) if err != nil { t.Fatal(err) } // Wait for payment to succeed. err = <-ctx.errChan assertFailureCode(t, err, lnwire.CodeIncorrectOrUnknownPaymentDetails) } // TestChannelLinkHoldInvoiceRestart asserts hodl htlcs are held after blocks // are mined and the link is restarted. The initial expiry checks should not // apply to hodl htlcs after restart. func TestChannelLinkHoldInvoiceRestart(t *testing.T) { t.Parallel() defer timeout(t)() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 ) // We'll start by creating a new link with our chanAmt (5 BTC). We will // only be testing Alice's behavior, so the reference to Bob's channel // state is unnecessary. aliceLink, bobChannel, _, start, cleanUp, restore, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() alice := newPersistentLinkHarness( t, aliceLink, nil, restore, ) if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = alice.coreLink registry = coreLink.cfg.Registry.(*mockInvoiceRegistry) ) registry.settleChan = make(chan lntypes.Hash) htlc, invoice := generateHtlcAndInvoice(t, 0) // Convert into a hodl invoice and save the preimage for later. preimage := invoice.Terms.PaymentPreimage invoice.Terms.PaymentPreimage = nil invoice.HodlInvoice = true // We must add the invoice to the registry, such that Alice // expects this payment. err = registry.AddInvoice( *invoice, htlc.PaymentHash, ) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } ctx := linkTestContext{ t: t, aliceLink: alice.link, aliceMsgs: alice.msgs, bobChannel: bobChannel, } // Lock in htlc paying the hodl invoice. ctx.sendHtlcBobToAlice(htlc) ctx.sendCommitSigBobToAlice(1) ctx.receiveRevAndAckAliceToBob() ctx.receiveCommitSigAliceToBob(1) ctx.sendRevAndAckBobToAlice() // We expect a call to the invoice registry to notify the arrival of the // htlc. <-registry.settleChan // Increase block height. This height will be retrieved by the link // after restart. coreLink.cfg.Switch.bestHeight++ // Restart link. alice.restart(false, false) ctx.aliceLink = alice.link ctx.aliceMsgs = alice.msgs // Expect htlc to be reprocessed. <-registry.settleChan // Settle the invoice with the preimage. err = registry.SettleHodlInvoice(*preimage) if err != nil { t.Fatalf("settle hodl invoice: %v", err) } // Expect alice to send a settle and commitsig message to bob. ctx.receiveSettleAliceToBob() ctx.receiveCommitSigAliceToBob(0) // Stop the link alice.link.Stop() // Check that no unexpected messages were sent. select { case msg := <-alice.msgs: t.Fatalf("did not expect message %T", msg) default: } } // TestChannelLinkRevocationWindowRegular asserts that htlcs paying to a regular // invoice are settled even if the revocation window gets exhausted. func TestChannelLinkRevocationWindowRegular(t *testing.T) { t.Parallel() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 ) // We'll start by creating a new link with our chanAmt (5 BTC). We will // only be testing Alice's behavior, so the reference to Bob's channel // state is unnecessary. aliceLink, bobChannel, _, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } defer aliceLink.Stop() var ( coreLink = aliceLink.(*channelLink) registry = coreLink.cfg.Registry.(*mockInvoiceRegistry) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } registry.settleChan = make(chan lntypes.Hash) htlc1, invoice1 := generateHtlcAndInvoice(t, 0) htlc2, invoice2 := generateHtlcAndInvoice(t, 1) // We must add the invoice to the registry, such that Alice // expects this payment. err = registry.AddInvoice(*invoice1, htlc1.PaymentHash) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } err = registry.AddInvoice(*invoice2, htlc2.PaymentHash) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } // Lock in htlc 1 on both sides. ctx.sendHtlcBobToAlice(htlc1) ctx.sendCommitSigBobToAlice(1) ctx.receiveRevAndAckAliceToBob() ctx.receiveCommitSigAliceToBob(1) ctx.sendRevAndAckBobToAlice() // We expect a call to the invoice registry to notify the arrival of the // htlc. select { case <-registry.settleChan: case <-time.After(5 * time.Second): t.Fatal("expected invoice to be settled") } // Expect alice to send a settle and commitsig message to bob. Bob does // not yet send the revocation. ctx.receiveSettleAliceToBob() ctx.receiveCommitSigAliceToBob(0) // Pay invoice 2. ctx.sendHtlcBobToAlice(htlc2) ctx.sendCommitSigBobToAlice(2) ctx.receiveRevAndAckAliceToBob() // At this point, Alice cannot send a new commit sig to bob because the // revocation window is exhausted. // Bob sends revocation and signs commit with htlc1 settled. ctx.sendRevAndAckBobToAlice() // After the revocation, it is again possible for Alice to send a commit // sig with htlc2. ctx.receiveCommitSigAliceToBob(1) } // TestChannelLinkRevocationWindowHodl asserts that htlcs paying to a hodl // invoice are settled even if the revocation window gets exhausted. func TestChannelLinkRevocationWindowHodl(t *testing.T) { t.Parallel() const ( chanAmt = btcutil.SatoshiPerBitcoin * 5 ) // We'll start by creating a new link with our chanAmt (5 BTC). We will // only be testing Alice's behavior, so the reference to Bob's channel // state is unnecessary. aliceLink, bobChannel, batchTicker, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, 0) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) registry = coreLink.cfg.Registry.(*mockInvoiceRegistry) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) registry.settleChan = make(chan lntypes.Hash) // Generate two invoice-htlc pairs. htlc1, invoice1 := generateHtlcAndInvoice(t, 0) htlc2, invoice2 := generateHtlcAndInvoice(t, 1) // Convert into hodl invoices and save the preimages for later. preimage1 := invoice1.Terms.PaymentPreimage invoice1.Terms.PaymentPreimage = nil invoice1.HodlInvoice = true preimage2 := invoice2.Terms.PaymentPreimage invoice2.Terms.PaymentPreimage = nil invoice2.HodlInvoice = true // We must add the invoices to the registry, such that Alice // expects the payments. err = registry.AddInvoice(*invoice1, htlc1.PaymentHash) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } err = registry.AddInvoice(*invoice2, htlc2.PaymentHash) if err != nil { t.Fatalf("unable to add invoice to registry: %v", err) } ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } // Lock in htlc 1 on both sides. ctx.sendHtlcBobToAlice(htlc1) ctx.sendCommitSigBobToAlice(1) ctx.receiveRevAndAckAliceToBob() ctx.receiveCommitSigAliceToBob(1) ctx.sendRevAndAckBobToAlice() // We expect a call to the invoice registry to notify the arrival of // htlc 1. select { case <-registry.settleChan: case <-time.After(15 * time.Second): t.Fatal("exit hop notification not received") } // Lock in htlc 2 on both sides. ctx.sendHtlcBobToAlice(htlc2) ctx.sendCommitSigBobToAlice(2) ctx.receiveRevAndAckAliceToBob() ctx.receiveCommitSigAliceToBob(2) ctx.sendRevAndAckBobToAlice() select { case <-registry.settleChan: case <-time.After(15 * time.Second): t.Fatal("exit hop notification not received") } // Settle invoice 1 with the preimage. err = registry.SettleHodlInvoice(*preimage1) if err != nil { t.Fatalf("settle hodl invoice: %v", err) } // Expect alice to send a settle and commitsig message to bob. Bob does // not yet send the revocation. ctx.receiveSettleAliceToBob() ctx.receiveCommitSigAliceToBob(1) // Settle invoice 2 with the preimage. err = registry.SettleHodlInvoice(*preimage2) if err != nil { t.Fatalf("settle hodl invoice: %v", err) } // Expect alice to send a settle for htlc 2. ctx.receiveSettleAliceToBob() // At this point, Alice cannot send a new commit sig to bob because the // revocation window is exhausted. // Sleep to let timer(s) expire. time.Sleep(time.Second) // We don't expect a commitSig from Alice. select { case msg := <-aliceMsgs: t.Fatalf("did not expect message %T", msg) default: } // Bob sends revocation and signs commit with htlc 1 settled. ctx.sendRevAndAckBobToAlice() // Allow some time for it to be processed by the link. time.Sleep(time.Second) // Trigger the batch timer as this may trigger Alice to send a commit // sig. batchTicker <- time.Time{} // After the revocation, it is again possible for Alice to send a commit // sig no more htlcs. Bob acks the update. ctx.receiveCommitSigAliceToBob(0) ctx.sendRevAndAckBobToAlice() // Bob updates his remote commit tx. ctx.sendCommitSigBobToAlice(0) ctx.receiveRevAndAckAliceToBob() // Stop the link aliceLink.Stop() // Check that no unexpected messages were sent. select { case msg := <-aliceMsgs: t.Fatalf("did not expect message %T", msg) default: } } // TestChannelLinkReceiveEmptySig tests the response of the link to receiving an // empty commit sig. This should be tolerated, but we shouldn't send out an // empty sig ourselves. func TestChannelLinkReceiveEmptySig(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } defer cleanUp() if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) ctx := linkTestContext{ t: t, aliceLink: aliceLink, aliceMsgs: aliceMsgs, bobChannel: bobChannel, } htlc, _ := generateHtlcAndInvoice(t, 0) // First, send an Add from Alice to Bob. ctx.sendHtlcAliceToBob(0, htlc) ctx.receiveHtlcAliceToBob() // Tick the batch ticker to trigger a commitsig from Alice->Bob. select { case batchTicker <- time.Now(): case <-time.After(5 * time.Second): t.Fatalf("could not force commit sig") } // Make Bob send a CommitSig. Since Bob hasn't received Alice's sig, he // cannot add the htlc to his remote tx yet. The commit sig that we // force Bob to send will be empty. Note that this normally does not // happen, because the link (which is not present for Bob in this test) // check whether Bob actually owes a sig first. ctx.sendCommitSigBobToAlice(0) // Receive a CommitSig from Alice covering the htlc from above. ctx.receiveCommitSigAliceToBob(1) // Wait for RevokeAndAck Alice->Bob. Even though Bob sent an empty // commit sig, Alice still needs to revoke the previous commitment tx. ctx.receiveRevAndAckAliceToBob() // Send RevokeAndAck Bob->Alice to ack the added htlc. ctx.sendRevAndAckBobToAlice() // We received an empty commit sig, we accepted it, but there is nothing // new to sign for us. // No other messages are expected. ctx.assertNoMsgFromAlice(time.Second) // Stop the link aliceLink.Stop() } // TestPendingCommitTicker tests that a link will fail itself after a timeout if // the commitment dance stalls out. func TestPendingCommitTicker(t *testing.T) { t.Parallel() const chanAmt = btcutil.SatoshiPerBitcoin * 5 const chanReserve = btcutil.SatoshiPerBitcoin * 1 aliceLink, bobChannel, batchTicker, start, cleanUp, _, err := newSingleLinkTestHarness(chanAmt, chanReserve) if err != nil { t.Fatalf("unable to create link: %v", err) } var ( coreLink = aliceLink.(*channelLink) aliceMsgs = coreLink.cfg.Peer.(*mockPeer).sentMsgs ) coreLink.cfg.PendingCommitTicker = ticker.NewForce(time.Millisecond) linkErrs := make(chan LinkFailureError) coreLink.cfg.OnChannelFailure = func(_ lnwire.ChannelID, _ lnwire.ShortChannelID, linkErr LinkFailureError) { linkErrs <- linkErr } if err := start(); err != nil { t.Fatalf("unable to start test harness: %v", err) } defer cleanUp() ctx := linkTestContext{ t: t, aliceLink: aliceLink, bobChannel: bobChannel, aliceMsgs: aliceMsgs, } // Send an HTLC from Alice to Bob, and signal the batch ticker to signa // a commitment. htlc, _ := generateHtlcAndInvoice(t, 0) ctx.sendHtlcAliceToBob(0, htlc) ctx.receiveHtlcAliceToBob() batchTicker <- time.Now() select { case msg := <-aliceMsgs: if _, ok := msg.(*lnwire.CommitSig); !ok { t.Fatalf("expected CommitSig, got: %T", msg) } case <-time.After(time.Second): t.Fatalf("alice did not send commit sig") } // Check that Alice hasn't failed. select { case linkErr := <-linkErrs: t.Fatalf("link failed unexpectedly: %v", linkErr) case <-time.After(50 * time.Millisecond): } // Without completing the dance, send another HTLC from Alice to Bob. // Since the revocation window has been exhausted, we should see the // link fail itself immediately due to the low pending commit timeout. // In production this would be much longer, e.g. a minute. htlc, _ = generateHtlcAndInvoice(t, 1) ctx.sendHtlcAliceToBob(1, htlc) ctx.receiveHtlcAliceToBob() batchTicker <- time.Now() // Assert that we get the expected link failure from Alice. select { case linkErr := <-linkErrs: if linkErr.code != ErrRemoteUnresponsive { t.Fatalf("error code mismatch, "+ "want: ErrRemoteUnresponsive, got: %v", linkErr.code) } case <-time.After(time.Second): t.Fatalf("did not receive failure") } } // assertFailureCode asserts that an error is of type ClearTextError and that // the failure code is as expected. func assertFailureCode(t *testing.T, err error, code lnwire.FailCode) { rtErr, ok := err.(ClearTextError) if !ok { t.Fatalf("expected ClearTextError but got %T", err) } if rtErr.WireMessage().Code() != code { t.Fatalf("expected %v but got %v", code, rtErr.WireMessage().Code()) } }