2381 lines
65 KiB
Go
2381 lines
65 KiB
Go
package htlcswitch
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/fastsha256"
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/ticker"
|
|
)
|
|
|
|
func genPreimage() ([32]byte, error) {
|
|
var preimage [32]byte
|
|
if _, err := io.ReadFull(rand.Reader, preimage[:]); err != nil {
|
|
return preimage, err
|
|
}
|
|
return preimage, nil
|
|
}
|
|
|
|
// TestSwitchAddDuplicateLink tests that the switch will reject duplicate links
|
|
// for both pending and live links. It also tests that we can successfully
|
|
// add a link after having removed it.
|
|
func TestSwitchAddDuplicateLink(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, _ := genIDs()
|
|
|
|
pendingChanID := lnwire.ShortChannelID{}
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, pendingChanID, alicePeer, false,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
|
|
// Alice should have a pending link, adding again should fail.
|
|
if err := s.AddLink(aliceChannelLink); err == nil {
|
|
t.Fatalf("adding duplicate link should have failed")
|
|
}
|
|
|
|
// Update the short chan id of the channel, so that the link goes live.
|
|
aliceChannelLink.setLiveShortChanID(aliceChanID)
|
|
err = s.UpdateShortChanID(chanID1)
|
|
if err != nil {
|
|
t.Fatalf("unable to update alice short_chan_id: %v", err)
|
|
}
|
|
|
|
// Alice should have a live link, adding again should fail.
|
|
if err := s.AddLink(aliceChannelLink); err == nil {
|
|
t.Fatalf("adding duplicate link should have failed")
|
|
}
|
|
|
|
// Remove the live link to ensure the indexes are cleared.
|
|
s.RemoveLink(chanID1)
|
|
|
|
// Alice has no links, adding should succeed.
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestSwitchHasActiveLink tests the behavior of HasActiveLink, and asserts that
|
|
// it only returns true if a link's short channel id has confirmed (meaning the
|
|
// channel is no longer pending) and it's EligibleToForward method returns true,
|
|
// i.e. it has received FundingLocked from the remote peer.
|
|
func TestSwitchHasActiveLink(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, _ := genIDs()
|
|
|
|
pendingChanID := lnwire.ShortChannelID{}
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, pendingChanID, alicePeer, false,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
|
|
// The link has been added, but it's still pending. HasActiveLink should
|
|
// return false since the link has not been added to the linkIndex
|
|
// containing live links.
|
|
if s.HasActiveLink(chanID1) {
|
|
t.Fatalf("link should not be active yet, still pending")
|
|
}
|
|
|
|
// Update the short chan id of the channel, so that the link goes live.
|
|
aliceChannelLink.setLiveShortChanID(aliceChanID)
|
|
err = s.UpdateShortChanID(chanID1)
|
|
if err != nil {
|
|
t.Fatalf("unable to update alice short_chan_id: %v", err)
|
|
}
|
|
|
|
// UpdateShortChanID will cause the mock link to become eligible to
|
|
// forward. However, we can simulate the event where the short chan id
|
|
// is confirmed, but funding locked has yet to be received by resetting
|
|
// the mock link's eligibility to false.
|
|
aliceChannelLink.eligible = false
|
|
|
|
// Now, even though the link has been added to the linkIndex because the
|
|
// short channel id has confirmed, we should still see HasActiveLink
|
|
// fail because EligibleToForward should return false.
|
|
if s.HasActiveLink(chanID1) {
|
|
t.Fatalf("link should not be active yet, still ineligible")
|
|
}
|
|
|
|
// Finally, simulate the link receiving funding locked by setting its
|
|
// eligibility to true.
|
|
aliceChannelLink.eligible = true
|
|
|
|
// The link should now be reported as active, since EligibleToForward
|
|
// returns true and the link is in the linkIndex.
|
|
if !s.HasActiveLink(chanID1) {
|
|
t.Fatalf("link should not be active now")
|
|
}
|
|
}
|
|
|
|
// TestSwitchSendPending checks the inability of htlc switch to forward adds
|
|
// over pending links, and the UpdateShortChanID makes a pending link live.
|
|
func TestSwitchSendPending(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, bobChanID := genIDs()
|
|
|
|
pendingChanID := lnwire.ShortChannelID{}
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, pendingChanID, alicePeer, false,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
|
|
// Create request which should is being forwarded from Bob channel
|
|
// link to Alice channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
packet := &htlcPacket{
|
|
incomingChanID: bobChanID,
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: aliceChanID,
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
// Send the ADD packet, this should not be forwarded out to the link
|
|
// since there are no eligible links.
|
|
err = s.forward(packet)
|
|
expErr := fmt.Sprintf("unable to find link with destination %v",
|
|
aliceChanID)
|
|
if err != nil && err.Error() != expErr {
|
|
t.Fatalf("expected forward failure: %v", err)
|
|
}
|
|
|
|
// No message should be sent, since the packet was failed.
|
|
select {
|
|
case <-aliceChannelLink.packets:
|
|
t.Fatal("expected not to receive message")
|
|
case <-time.After(time.Second):
|
|
}
|
|
|
|
// Since the packet should have been failed, there should be no active
|
|
// circuits.
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Now, update Alice's link with her final short channel id. This should
|
|
// move the link to the live state.
|
|
aliceChannelLink.setLiveShortChanID(aliceChanID)
|
|
err = s.UpdateShortChanID(chanID1)
|
|
if err != nil {
|
|
t.Fatalf("unable to update alice short_chan_id: %v", err)
|
|
}
|
|
|
|
// Increment the packet's HTLC index, so that it does not collide with
|
|
// the prior attempt.
|
|
packet.incomingHTLCID++
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(packet); err != nil {
|
|
t.Fatalf("unexpected forward failure: %v", err)
|
|
}
|
|
|
|
// Since Alice's link is now active, this packet should succeed.
|
|
select {
|
|
case <-aliceChannelLink.packets:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to alice")
|
|
}
|
|
}
|
|
|
|
// TestSwitchForward checks the ability of htlc switch to forward add/settle
|
|
// requests.
|
|
func TestSwitchForward(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
packet := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(packet); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case <-bobChannelLink.packets:
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Create settle request pretending that bob link handled the add htlc
|
|
// request and sent the htlc settle request back. This request should
|
|
// be forwarder back to Alice link.
|
|
packet = &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFulfillHTLC{
|
|
PaymentPreimage: preimage,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that payment circuit works properly.
|
|
if err := s.forward(packet); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case pkt := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.deleteCircuit(pkt); err != nil {
|
|
t.Fatalf("unable to remove circuit: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to channelPoint")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
func TestSwitchForwardFailAfterFullAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
tempPath, err := ioutil.TempDir("", "circuitdb")
|
|
if err != nil {
|
|
t.Fatalf("unable to temporary path: %v", err)
|
|
}
|
|
|
|
cdb, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, cdb)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
ogPacket := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Pull packet from bob's link, but do not perform a full add.
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
// Complete the payment circuit and assign the outgoing htlc id
|
|
// before restarting.
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Now we will restart bob, leaving the forwarding decision for this
|
|
// htlc is in the half-added state.
|
|
if err := s.Stop(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
if err := cdb.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb2, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s2, err := initSwitchWithDB(testStartingHeight, cdb2)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s2.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s2 later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s2.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s2, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s2, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s2.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s2.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 1 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Craft a failure message from the remote peer.
|
|
fail := &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{},
|
|
}
|
|
|
|
// Send the fail packet from the remote peer through the switch.
|
|
if err := s2.forward(fail); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
// Pull packet from alice's link, as it should have gone through
|
|
// successfully.
|
|
select {
|
|
case pkt := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(pkt); err != nil {
|
|
t.Fatalf("unable to remove circuit: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
// Circuit map should be empty now.
|
|
if s2.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Send the fail packet from the remote peer through the switch.
|
|
if err := s2.forward(fail); err == nil {
|
|
t.Fatalf("expected failure when sending duplicate fail " +
|
|
"with no pending circuit")
|
|
}
|
|
}
|
|
|
|
func TestSwitchForwardSettleAfterFullAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
tempPath, err := ioutil.TempDir("", "circuitdb")
|
|
if err != nil {
|
|
t.Fatalf("unable to temporary path: %v", err)
|
|
}
|
|
|
|
cdb, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, cdb)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
ogPacket := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Pull packet from bob's link, but do not perform a full add.
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
// Complete the payment circuit and assign the outgoing htlc id
|
|
// before restarting.
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Now we will restart bob, leaving the forwarding decision for this
|
|
// htlc is in the half-added state.
|
|
if err := s.Stop(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
if err := cdb.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb2, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s2, err := initSwitchWithDB(testStartingHeight, cdb2)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s2.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s2 later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s2.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s2, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s2, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s2.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s2.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 1 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Craft a settle message from the remote peer.
|
|
settle := &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFulfillHTLC{
|
|
PaymentPreimage: preimage,
|
|
},
|
|
}
|
|
|
|
// Send the settle packet from the remote peer through the switch.
|
|
if err := s2.forward(settle); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
// Pull packet from alice's link, as it should have gone through
|
|
// successfully.
|
|
select {
|
|
case packet := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete circuit with in key=%s: %v",
|
|
packet.inKey(), err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
// Circuit map should be empty now.
|
|
if s2.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Send the settle packet again, which should fail.
|
|
if err := s2.forward(settle); err == nil {
|
|
t.Fatalf("expected failure when sending duplicate settle " +
|
|
"with no pending circuit")
|
|
}
|
|
}
|
|
|
|
func TestSwitchForwardDropAfterFullAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
tempPath, err := ioutil.TempDir("", "circuitdb")
|
|
if err != nil {
|
|
t.Fatalf("unable to temporary path: %v", err)
|
|
}
|
|
|
|
cdb, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, cdb)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
ogPacket := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
|
|
// Pull packet from bob's link, but do not perform a full add.
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
// Complete the payment circuit and assign the outgoing htlc id
|
|
// before restarting.
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
// Now we will restart bob, leaving the forwarding decision for this
|
|
// htlc is in the half-added state.
|
|
if err := s.Stop(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
if err := cdb.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb2, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s2, err := initSwitchWithDB(testStartingHeight, cdb2)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s2.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s2 later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s2.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s2, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s2, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s2.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s2.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
|
|
// Resend the failed htlc, it should be returned to alice since the
|
|
// switch will detect that it has been half added previously.
|
|
err = s2.forward(ogPacket)
|
|
if err != ErrDuplicateAdd {
|
|
t.Fatal("unexpected error when reforwarding a "+
|
|
"failed packet", err)
|
|
}
|
|
|
|
// After detecting an incomplete forward, the fail packet should have
|
|
// been returned to the sender.
|
|
select {
|
|
case <-aliceChannelLink.packets:
|
|
t.Fatal("request should not have returned to source")
|
|
case <-bobChannelLink.packets:
|
|
t.Fatal("request should not have forwarded to destination")
|
|
case <-time.After(time.Second):
|
|
}
|
|
}
|
|
|
|
func TestSwitchForwardFailAfterHalfAdd(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
tempPath, err := ioutil.TempDir("", "circuitdb")
|
|
if err != nil {
|
|
t.Fatalf("unable to temporary path: %v", err)
|
|
}
|
|
|
|
cdb, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, cdb)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
ogPacket := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
|
|
// Pull packet from bob's link, but do not perform a full add.
|
|
select {
|
|
case <-bobChannelLink.packets:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
// Now we will restart bob, leaving the forwarding decision for this
|
|
// htlc is in the half-added state.
|
|
if err := s.Stop(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
if err := cdb.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb2, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s2, err := initSwitchWithDB(testStartingHeight, cdb2)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s2.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s2 later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s2.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s2, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s2, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s2.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s2.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
|
|
// Resend the failed htlc, it should be returned to alice since the
|
|
// switch will detect that it has been half added previously.
|
|
err = s2.forward(ogPacket)
|
|
if err != ErrIncompleteForward {
|
|
t.Fatal("unexpected error when reforwarding a "+
|
|
"failed packet", err)
|
|
}
|
|
|
|
// After detecting an incomplete forward, the fail packet should have
|
|
// been returned to the sender.
|
|
select {
|
|
case <-aliceChannelLink.packets:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
}
|
|
|
|
// TestSwitchForwardCircuitPersistence checks the ability of htlc switch to
|
|
// maintain the proper entries in the circuit map in the face of restarts.
|
|
func TestSwitchForwardCircuitPersistence(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
tempPath, err := ioutil.TempDir("", "circuitdb")
|
|
if err != nil {
|
|
t.Fatalf("unable to temporary path: %v", err)
|
|
}
|
|
|
|
cdb, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to open channeldb: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, cdb)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarded from Alice channel link to
|
|
// bob channel link.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
ogPacket := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
|
|
// Retrieve packet from outgoing link and cache until after restart.
|
|
var packet *htlcPacket
|
|
select {
|
|
case packet = <-bobChannelLink.packets:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if err := s.Stop(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
if err := cdb.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb2, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s2, err := initSwitchWithDB(testStartingHeight, cdb2)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s2.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
|
|
// Even though we intend to Stop s2 later in the test, it is safe to
|
|
// defer this Stop since its execution it is protected by an atomic
|
|
// guard, guaranteeing it executes at most once.
|
|
defer s2.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s2, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s2, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s2.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s2.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
|
|
// Now that the switch has restarted, complete the payment circuit.
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s2.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Create settle request pretending that bob link handled the add htlc
|
|
// request and sent the htlc settle request back. This request should
|
|
// be forwarder back to Alice link.
|
|
ogPacket = &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFulfillHTLC{
|
|
PaymentPreimage: preimage,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that payment circuit works properly.
|
|
if err := s2.forward(ogPacket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case packet = <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete circuit with in key=%s: %v",
|
|
packet.inKey(), err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to channelPoint")
|
|
}
|
|
|
|
if s2.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits, want 1, got %d",
|
|
s2.circuits.NumPending())
|
|
}
|
|
if s2.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
if err := s2.Stop(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := cdb2.Close(); err != nil {
|
|
t.Fatalf(err.Error())
|
|
}
|
|
|
|
cdb3, err := channeldb.Open(tempPath)
|
|
if err != nil {
|
|
t.Fatalf("unable to reopen channeldb: %v", err)
|
|
}
|
|
|
|
s3, err := initSwitchWithDB(testStartingHeight, cdb3)
|
|
if err != nil {
|
|
t.Fatalf("unable reinit switch: %v", err)
|
|
}
|
|
if err := s3.Start(); err != nil {
|
|
t.Fatalf("unable to restart switch: %v", err)
|
|
}
|
|
defer s3.Stop()
|
|
|
|
aliceChannelLink = newMockChannelLink(
|
|
s3, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink = newMockChannelLink(
|
|
s3, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s3.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s3.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
if s3.circuits.NumPending() != 0 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s3.circuits.NumOpen() != 0 {
|
|
t.Fatalf("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
// TestSkipIneligibleLinksMultiHopForward tests that if a multi-hop HTLC comes
|
|
// along, then we won't attempt to froward it down al ink that isn't yet able
|
|
// to forward any HTLC's.
|
|
func TestSkipIneligibleLinksMultiHopForward(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var packet *htlcPacket
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
|
|
// We'll create a link for Bob, but mark the link as unable to forward
|
|
// any new outgoing HTLC's.
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, false,
|
|
)
|
|
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create a new packet that's destined for Bob as an incoming HTLC from
|
|
// Alice.
|
|
preimage := [sha256.Size]byte{1}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
packet = &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
obfuscator: NewMockObfuscator(),
|
|
}
|
|
|
|
// The request to forward should fail as
|
|
err = s.forward(packet)
|
|
if err == nil {
|
|
t.Fatalf("forwarding should have failed due to inactive link")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
// TestSkipIneligibleLinksLocalForward ensures that the switch will not attempt
|
|
// to forward any HTLC's down a link that isn't yet eligible for forwarding.
|
|
func TestSkipIneligibleLinksLocalForward(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testSkipLinkLocalForward(t, false, nil)
|
|
}
|
|
|
|
// TestSkipPolicyUnsatisfiedLinkLocalForward ensures that the switch will not
|
|
// attempt to send locally initiated HTLCs that would violate the channel policy
|
|
// down a link.
|
|
func TestSkipPolicyUnsatisfiedLinkLocalForward(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testSkipLinkLocalForward(t, true, lnwire.NewTemporaryChannelFailure(nil))
|
|
}
|
|
|
|
func testSkipLinkLocalForward(t *testing.T, eligible bool,
|
|
policyResult lnwire.FailureMessage) {
|
|
|
|
// We'll create a single link for this test, marking it as being unable
|
|
// to forward form the get go.
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, _ := genIDs()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, eligible,
|
|
)
|
|
aliceChannelLink.htlcSatifiesPolicyLocalResult = policyResult
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
addMsg := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
}
|
|
|
|
// We'll attempt to send out a new HTLC that has Alice as the first
|
|
// outgoing link. This should fail as Alice isn't yet able to forward
|
|
// any active HTLC's.
|
|
err = s.SendHTLC(aliceChannelLink.ShortChanID(), 0, addMsg)
|
|
if err == nil {
|
|
t.Fatalf("local forward should fail due to inactive link")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
// TestSwitchCancel checks that if htlc was rejected we remove unused
|
|
// circuits.
|
|
func TestSwitchCancel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarder from alice channel link
|
|
// to bob channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
request := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumPending() != 1 {
|
|
t.Fatalf("wrong amount of half circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Create settle request pretending that bob channel link handled
|
|
// the add htlc request and sent the htlc settle request back. This
|
|
// request should be forwarder back to alice channel link.
|
|
request = &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{},
|
|
}
|
|
|
|
// Handle the request and checks that payment circuit works properly.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case pkt := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(pkt); err != nil {
|
|
t.Fatalf("unable to remove circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to channelPoint")
|
|
}
|
|
|
|
if s.circuits.NumPending() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
// TestSwitchAddSamePayment tests that we send the payment with the same
|
|
// payment hash.
|
|
func TestSwitchAddSamePayment(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chanID1, chanID2, aliceChanID, bobChanID := genIDs()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
bobPeer, err := newMockServer(t, "bob", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create bob server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
bobChannelLink := newMockChannelLink(
|
|
s, chanID2, bobChanID, bobPeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add alice link: %v", err)
|
|
}
|
|
if err := s.AddLink(bobChannelLink); err != nil {
|
|
t.Fatalf("unable to add bob link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarder from alice channel link
|
|
// to bob channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
request := &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 0,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
request = &htlcPacket{
|
|
incomingChanID: aliceChannelLink.ShortChanID(),
|
|
incomingHTLCID: 1,
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
obfuscator: NewMockObfuscator(),
|
|
htlc: &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
},
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case packet := <-bobChannelLink.packets:
|
|
if err := bobChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 2 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Create settle request pretending that bob channel link handled
|
|
// the add htlc request and sent the htlc settle request back. This
|
|
// request should be forwarder back to alice channel link.
|
|
request = &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{},
|
|
}
|
|
|
|
// Handle the request and checks that payment circuit works properly.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case pkt := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(pkt); err != nil {
|
|
t.Fatalf("unable to remove circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to channelPoint")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
request = &htlcPacket{
|
|
outgoingChanID: bobChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 1,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{},
|
|
}
|
|
|
|
// Handle the request and checks that payment circuit works properly.
|
|
if err := s.forward(request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case pkt := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(pkt); err != nil {
|
|
t.Fatalf("unable to remove circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to channelPoint")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 0 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
}
|
|
|
|
// TestSwitchSendPayment tests ability of htlc switch to respond to the
|
|
// users when response is came back from channel link.
|
|
func TestSwitchSendPayment(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, _ := genIDs()
|
|
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add link: %v", err)
|
|
}
|
|
|
|
// Create request which should be forwarder from alice channel link
|
|
// to bob channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
update := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
}
|
|
paymentID := uint64(123)
|
|
|
|
// First check that the switch will correctly respond that this payment
|
|
// ID is unknown.
|
|
_, err = s.GetPaymentResult(
|
|
paymentID, rhash, newMockDeobfuscator(),
|
|
)
|
|
if err != ErrPaymentIDNotFound {
|
|
t.Fatalf("expected ErrPaymentIDNotFound, got %v", err)
|
|
}
|
|
|
|
// Handle the request and checks that bob channel link received it.
|
|
errChan := make(chan error)
|
|
go func() {
|
|
err := s.SendHTLC(
|
|
aliceChannelLink.ShortChanID(), paymentID, update,
|
|
)
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
|
|
resultChan, err := s.GetPaymentResult(
|
|
paymentID, rhash, newMockDeobfuscator(),
|
|
)
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
|
|
result, ok := <-resultChan
|
|
if !ok {
|
|
errChan <- fmt.Errorf("shutting down")
|
|
}
|
|
|
|
if result.Error != nil {
|
|
errChan <- result.Error
|
|
return
|
|
}
|
|
|
|
errChan <- nil
|
|
}()
|
|
|
|
select {
|
|
case packet := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case err := <-errChan:
|
|
if err != nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
if s.circuits.NumOpen() != 1 {
|
|
t.Fatal("wrong amount of circuits")
|
|
}
|
|
|
|
// Create fail request pretending that bob channel link handled
|
|
// the add htlc request with error and sent the htlc fail request
|
|
// back. This request should be forwarded back to alice channel link.
|
|
obfuscator := NewMockObfuscator()
|
|
failure := lnwire.NewFailUnknownPaymentHash(update.Amount)
|
|
reason, err := obfuscator.EncryptFirstHop(failure)
|
|
if err != nil {
|
|
t.Fatalf("unable obfuscate failure: %v", err)
|
|
}
|
|
|
|
packet := &htlcPacket{
|
|
outgoingChanID: aliceChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{
|
|
Reason: reason,
|
|
},
|
|
}
|
|
|
|
if err := s.forward(packet); err != nil {
|
|
t.Fatalf("can't forward htlc packet: %v", err)
|
|
}
|
|
|
|
select {
|
|
case err := <-errChan:
|
|
fErr, ok := err.(*ForwardingError)
|
|
if !ok {
|
|
t.Fatal("expected ForwardingError")
|
|
}
|
|
|
|
if _, ok := fErr.FailureMessage.(*lnwire.FailUnknownPaymentHash); !ok {
|
|
t.Fatalf("expected UnknownPaymentHash got %v", fErr)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("err wasn't received")
|
|
}
|
|
}
|
|
|
|
// TestLocalPaymentNoForwardingEvents tests that if we send a series of locally
|
|
// initiated payments, then they aren't reflected in the forwarding log.
|
|
func TestLocalPaymentNoForwardingEvents(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 the first end point 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)
|
|
}
|
|
|
|
// We'll now craft and send a payment from Alice to Bob.
|
|
amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
|
htlcAmt, totalTimelock, hops := generateHops(
|
|
amount, testStartingHeight, n.firstBobChannelLink,
|
|
)
|
|
|
|
// With the payment crafted, we'll send it from Alice to Bob. We'll
|
|
// wait for Alice to receive the preimage for the payment before
|
|
// proceeding.
|
|
receiver := n.bobServer
|
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
|
_, 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)
|
|
}
|
|
|
|
// At this point, we'll forcibly stop the three hop network. Doing
|
|
// this will cause any pending forwarding events to be flushed by the
|
|
// various switches in the network.
|
|
n.stop()
|
|
|
|
// With all the switches stopped, we'll fetch Alice's mock forwarding
|
|
// event log.
|
|
log, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
|
|
if !ok {
|
|
t.Fatalf("mockForwardingLog assertion failed")
|
|
}
|
|
log.Lock()
|
|
defer log.Unlock()
|
|
|
|
// If we examine the memory of the forwarding log, then it should be
|
|
// blank.
|
|
if len(log.events) != 0 {
|
|
t.Fatalf("log should have no events, instead has: %v",
|
|
spew.Sdump(log.events))
|
|
}
|
|
}
|
|
|
|
// TestMultiHopPaymentForwardingEvents tests that if we send a series of
|
|
// multi-hop payments via Alice->Bob->Carol. Then Bob properly logs forwarding
|
|
// events, while Alice and Carol don't.
|
|
func TestMultiHopPaymentForwardingEvents(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// First, we'll create our traditional three hop network.
|
|
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)
|
|
}
|
|
|
|
// We'll make now 10 payments, of 100k satoshis each from Alice to
|
|
// Carol via Bob.
|
|
const numPayments = 10
|
|
finalAmt := lnwire.NewMSatFromSatoshis(100000)
|
|
htlcAmt, totalTimelock, hops := generateHops(
|
|
finalAmt, testStartingHeight, n.firstBobChannelLink,
|
|
n.carolChannelLink,
|
|
)
|
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
|
for i := 0; i < numPayments/2; i++ {
|
|
_, err := makePayment(
|
|
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
|
htlcAmt, totalTimelock,
|
|
).Wait(30 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
}
|
|
|
|
bobLog, ok := n.bobServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
|
|
if !ok {
|
|
t.Fatalf("mockForwardingLog assertion failed")
|
|
}
|
|
|
|
// After sending 5 of the payments, trigger the forwarding ticker, to
|
|
// make sure the events are properly flushed.
|
|
bobTicker, ok := n.bobServer.htlcSwitch.cfg.FwdEventTicker.(*ticker.Force)
|
|
if !ok {
|
|
t.Fatalf("mockTicker assertion failed")
|
|
}
|
|
|
|
// We'll trigger the ticker, and wait for the events to appear in Bob's
|
|
// forwarding log.
|
|
timeout := time.After(15 * time.Second)
|
|
for {
|
|
select {
|
|
case bobTicker.Force <- time.Now():
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("unable to force tick")
|
|
}
|
|
|
|
// If all 5 events is found in Bob's log, we can break out and
|
|
// continue the test.
|
|
bobLog.Lock()
|
|
if len(bobLog.events) == 5 {
|
|
bobLog.Unlock()
|
|
break
|
|
}
|
|
bobLog.Unlock()
|
|
|
|
// Otherwise wait a little bit before checking again.
|
|
select {
|
|
case <-time.After(50 * time.Millisecond):
|
|
case <-timeout:
|
|
bobLog.Lock()
|
|
defer bobLog.Unlock()
|
|
t.Fatalf("expected 5 events in event log, instead "+
|
|
"found: %v", spew.Sdump(bobLog.events))
|
|
}
|
|
}
|
|
|
|
// Send the remaining payments.
|
|
for i := numPayments / 2; i < numPayments; i++ {
|
|
_, err := makePayment(
|
|
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
|
htlcAmt, totalTimelock,
|
|
).Wait(30 * time.Second)
|
|
if err != nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
}
|
|
|
|
// With all 10 payments sent. We'll now manually stop each of the
|
|
// switches so we can examine their end state.
|
|
n.stop()
|
|
|
|
// Alice and Carol shouldn't have any recorded forwarding events, as
|
|
// they were the source and the sink for these payment flows.
|
|
aliceLog, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
|
|
if !ok {
|
|
t.Fatalf("mockForwardingLog assertion failed")
|
|
}
|
|
aliceLog.Lock()
|
|
defer aliceLog.Unlock()
|
|
if len(aliceLog.events) != 0 {
|
|
t.Fatalf("log should have no events, instead has: %v",
|
|
spew.Sdump(aliceLog.events))
|
|
}
|
|
|
|
carolLog, ok := n.carolServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
|
|
if !ok {
|
|
t.Fatalf("mockForwardingLog assertion failed")
|
|
}
|
|
carolLog.Lock()
|
|
defer carolLog.Unlock()
|
|
if len(carolLog.events) != 0 {
|
|
t.Fatalf("log should have no events, instead has: %v",
|
|
spew.Sdump(carolLog.events))
|
|
}
|
|
|
|
// Bob on the other hand, should have 10 events.
|
|
bobLog.Lock()
|
|
defer bobLog.Unlock()
|
|
if len(bobLog.events) != 10 {
|
|
t.Fatalf("log should have 10 events, instead has: %v",
|
|
spew.Sdump(bobLog.events))
|
|
}
|
|
|
|
// Each of the 10 events should have had all fields set properly.
|
|
for _, event := range bobLog.events {
|
|
// The incoming and outgoing channels should properly be set for
|
|
// the event.
|
|
if event.IncomingChanID != n.aliceChannelLink.ShortChanID() {
|
|
t.Fatalf("chan id mismatch: expected %v, got %v",
|
|
event.IncomingChanID,
|
|
n.aliceChannelLink.ShortChanID())
|
|
}
|
|
if event.OutgoingChanID != n.carolChannelLink.ShortChanID() {
|
|
t.Fatalf("chan id mismatch: expected %v, got %v",
|
|
event.OutgoingChanID,
|
|
n.carolChannelLink.ShortChanID())
|
|
}
|
|
|
|
// Additionally, the incoming and outgoing amounts should also
|
|
// be properly set.
|
|
if event.AmtIn != htlcAmt {
|
|
t.Fatalf("incoming amt mismatch: expected %v, got %v",
|
|
event.AmtIn, htlcAmt)
|
|
}
|
|
if event.AmtOut != finalAmt {
|
|
t.Fatalf("outgoing amt mismatch: expected %v, got %v",
|
|
event.AmtOut, finalAmt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestUpdateFailMalformedHTLCErrorConversion tests that we're able to properly
|
|
// convert malformed HTLC errors that originate at the direct link, as well as
|
|
// during multi-hop HTLC forwarding.
|
|
func TestUpdateFailMalformedHTLCErrorConversion(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// First, we'll create our traditional three hop network.
|
|
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)
|
|
}
|
|
|
|
assertPaymentFailure := func(t *testing.T) {
|
|
// With the decoder modified, we'll now attempt to send a
|
|
// payment from Alice to carol.
|
|
finalAmt := lnwire.NewMSatFromSatoshis(100000)
|
|
htlcAmt, totalTimelock, hops := generateHops(
|
|
finalAmt, testStartingHeight, n.firstBobChannelLink,
|
|
n.carolChannelLink,
|
|
)
|
|
firstHop := n.firstBobChannelLink.ShortChanID()
|
|
_, err = makePayment(
|
|
n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
|
|
htlcAmt, totalTimelock,
|
|
).Wait(30 * time.Second)
|
|
|
|
// The payment should fail as Carol is unable to decode the
|
|
// onion blob sent to her.
|
|
if err == nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
|
|
fwdingErr := err.(*ForwardingError)
|
|
failureMsg := fwdingErr.FailureMessage
|
|
if _, ok := failureMsg.(*lnwire.FailTemporaryChannelFailure); !ok {
|
|
t.Fatalf("expected temp chan failure instead got: %v",
|
|
fwdingErr.FailureMessage)
|
|
}
|
|
}
|
|
|
|
t.Run("multi-hop error conversion", func(t *testing.T) {
|
|
// Now that we have our network up, we'll modify the hop
|
|
// iterator for the Bob <-> Carol channel to fail to decode in
|
|
// order to simulate either a replay attack or an issue
|
|
// decoding the onion.
|
|
n.carolOnionDecoder.decodeFail = true
|
|
|
|
assertPaymentFailure(t)
|
|
})
|
|
|
|
t.Run("direct channel error conversion", func(t *testing.T) {
|
|
// Similar to the above test case, we'll now make the Alice <->
|
|
// Bob link always fail to decode an onion. This differs from
|
|
// the above test case in that there's no encryption on the
|
|
// error at all since Alice will directly receive a
|
|
// UpdateFailMalformedHTLC message.
|
|
n.bobOnionDecoder.decodeFail = true
|
|
|
|
assertPaymentFailure(t)
|
|
})
|
|
}
|
|
|
|
// TestSwitchGetPaymentResult tests that the switch interacts as expected with
|
|
// the circuit map and network result store when looking up the result of a
|
|
// payment ID. This is important for not to lose results under concurrent
|
|
// lookup and receiving results.
|
|
func TestSwitchGetPaymentResult(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const paymentID = 123
|
|
var preimg lntypes.Preimage
|
|
preimg[0] = 3
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
lookup := make(chan *PaymentCircuit, 1)
|
|
s.circuits = &mockCircuitMap{
|
|
lookup: lookup,
|
|
}
|
|
|
|
// If the payment circuit is not found in the circuit map, the payment
|
|
// result must be found in the store if available. Since we haven't
|
|
// added anything to the store yet, ErrPaymentIDNotFound should be
|
|
// returned.
|
|
lookup <- nil
|
|
_, err = s.GetPaymentResult(
|
|
paymentID, lntypes.Hash{}, newMockDeobfuscator(),
|
|
)
|
|
if err != ErrPaymentIDNotFound {
|
|
t.Fatalf("expected ErrPaymentIDNotFound, got %v", err)
|
|
}
|
|
|
|
// Next let the lookup find the circuit in the circuit map. It should
|
|
// subscribe to payment results, and return the result when available.
|
|
lookup <- &PaymentCircuit{}
|
|
resultChan, err := s.GetPaymentResult(
|
|
paymentID, lntypes.Hash{}, newMockDeobfuscator(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to get payment result: %v", err)
|
|
}
|
|
|
|
// Add the result to the store.
|
|
n := &networkResult{
|
|
msg: &lnwire.UpdateFulfillHTLC{
|
|
PaymentPreimage: preimg,
|
|
},
|
|
unencrypted: true,
|
|
isResolution: true,
|
|
}
|
|
|
|
err = s.networkResults.storeResult(paymentID, n)
|
|
if err != nil {
|
|
t.Fatalf("unable to store result: %v", err)
|
|
}
|
|
|
|
// The result should be availble.
|
|
select {
|
|
case res, ok := <-resultChan:
|
|
if !ok {
|
|
t.Fatalf("channel was closed")
|
|
}
|
|
|
|
if res.Error != nil {
|
|
t.Fatalf("got unexpected error result")
|
|
}
|
|
|
|
if res.Preimage != preimg {
|
|
t.Fatalf("expected preimg %v, got %v",
|
|
preimg, res.Preimage)
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("result not received")
|
|
}
|
|
|
|
// As a final test, try to get the result again. Now that is no longer
|
|
// in the circuit map, it should be immediately available from the
|
|
// store.
|
|
lookup <- nil
|
|
resultChan, err = s.GetPaymentResult(
|
|
paymentID, lntypes.Hash{}, newMockDeobfuscator(),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to get payment result: %v", err)
|
|
}
|
|
|
|
select {
|
|
case res, ok := <-resultChan:
|
|
if !ok {
|
|
t.Fatalf("channel was closed")
|
|
}
|
|
|
|
if res.Error != nil {
|
|
t.Fatalf("got unexpected error result")
|
|
}
|
|
|
|
if res.Preimage != preimg {
|
|
t.Fatalf("expected preimg %v, got %v",
|
|
preimg, res.Preimage)
|
|
}
|
|
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatalf("result not received")
|
|
}
|
|
}
|
|
|
|
// TestInvalidFailure tests that the switch returns an unreadable failure error
|
|
// if the failure cannot be decrypted.
|
|
func TestInvalidFailure(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alicePeer, err := newMockServer(t, "alice", testStartingHeight, nil, 6)
|
|
if err != nil {
|
|
t.Fatalf("unable to create alice server: %v", err)
|
|
}
|
|
|
|
s, err := initSwitchWithDB(testStartingHeight, nil)
|
|
if err != nil {
|
|
t.Fatalf("unable to init switch: %v", err)
|
|
}
|
|
if err := s.Start(); err != nil {
|
|
t.Fatalf("unable to start switch: %v", err)
|
|
}
|
|
defer s.Stop()
|
|
|
|
chanID1, _, aliceChanID, _ := genIDs()
|
|
|
|
// Set up a mock channel link.
|
|
aliceChannelLink := newMockChannelLink(
|
|
s, chanID1, aliceChanID, alicePeer, true,
|
|
)
|
|
if err := s.AddLink(aliceChannelLink); err != nil {
|
|
t.Fatalf("unable to add link: %v", err)
|
|
}
|
|
|
|
// Create a request which should be forwarded to the mock channel link.
|
|
preimage, err := genPreimage()
|
|
if err != nil {
|
|
t.Fatalf("unable to generate preimage: %v", err)
|
|
}
|
|
rhash := fastsha256.Sum256(preimage[:])
|
|
update := &lnwire.UpdateAddHTLC{
|
|
PaymentHash: rhash,
|
|
Amount: 1,
|
|
}
|
|
|
|
paymentID := uint64(123)
|
|
|
|
// Send the request.
|
|
err = s.SendHTLC(
|
|
aliceChannelLink.ShortChanID(), paymentID, update,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to send payment: %v", err)
|
|
}
|
|
|
|
// Catch the packet and complete the circuit so that the switch is ready
|
|
// for a response.
|
|
select {
|
|
case packet := <-aliceChannelLink.packets:
|
|
if err := aliceChannelLink.completeCircuit(packet); err != nil {
|
|
t.Fatalf("unable to complete payment circuit: %v", err)
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("request was not propagated to destination")
|
|
}
|
|
|
|
// Send response packet with an unreadable failure message to the
|
|
// switch. The reason failed is not relevant, because we mock the
|
|
// decryption.
|
|
packet := &htlcPacket{
|
|
outgoingChanID: aliceChannelLink.ShortChanID(),
|
|
outgoingHTLCID: 0,
|
|
amount: 1,
|
|
htlc: &lnwire.UpdateFailHTLC{
|
|
Reason: []byte{1, 2, 3},
|
|
},
|
|
}
|
|
|
|
if err := s.forward(packet); err != nil {
|
|
t.Fatalf("can't forward htlc packet: %v", err)
|
|
}
|
|
|
|
// Get payment result from switch. We expect an unreadable failure
|
|
// message error.
|
|
deobfuscator := SphinxErrorDecrypter{
|
|
OnionErrorDecrypter: &mockOnionErrorDecryptor{
|
|
err: ErrUnreadableFailureMessage,
|
|
},
|
|
}
|
|
|
|
resultChan, err := s.GetPaymentResult(
|
|
paymentID, rhash, &deobfuscator,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case result := <-resultChan:
|
|
if result.Error != ErrUnreadableFailureMessage {
|
|
t.Fatal("expected unreadable failure message")
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("err wasn't received")
|
|
}
|
|
|
|
// Modify the decryption to simulate that decryption went alright, but
|
|
// the failure cannot be decoded.
|
|
deobfuscator = SphinxErrorDecrypter{
|
|
OnionErrorDecrypter: &mockOnionErrorDecryptor{
|
|
sourceIdx: 2,
|
|
message: []byte{200},
|
|
},
|
|
}
|
|
|
|
resultChan, err = s.GetPaymentResult(
|
|
paymentID, rhash, &deobfuscator,
|
|
)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
select {
|
|
case result := <-resultChan:
|
|
fErr, ok := result.Error.(*ForwardingError)
|
|
if !ok {
|
|
t.Fatal("expected ForwardingError")
|
|
}
|
|
if fErr.FailureSourceIdx != 2 {
|
|
t.Fatal("unexpected error source index")
|
|
}
|
|
if fErr.FailureMessage != nil {
|
|
t.Fatal("expected empty failure message")
|
|
}
|
|
|
|
case <-time.After(time.Second):
|
|
t.Fatal("err wasn't received")
|
|
}
|
|
}
|