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") } } type multiHopFwdTest struct { name string eligible1, eligible2 bool failure1, failure2 lnwire.FailureMessage expectedReply lnwire.FailCode } // 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) { tests := []multiHopFwdTest{ // None of the channels is eligible. { name: "not eligible", expectedReply: lnwire.CodeUnknownNextPeer, }, // Channel one has a policy failure and the other channel isn't // available. { name: "policy fail", eligible1: true, failure1: lnwire.NewFinalIncorrectCltvExpiry(0), expectedReply: lnwire.CodeFinalIncorrectCltvExpiry, }, // The requested channel is not eligible, but the packet is // forwarded through the other channel. { name: "non-strict success", eligible2: true, expectedReply: lnwire.CodeNone, }, // The requested channel has insufficient bandwidth and the // other channel's policy isn't satisfied. { name: "non-strict policy fail", eligible1: true, failure1: lnwire.NewTemporaryChannelFailure(nil), eligible2: true, failure2: lnwire.NewFinalIncorrectCltvExpiry(0), expectedReply: lnwire.CodeTemporaryChannelFailure, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { testSkipIneligibleLinksMultiHopForward(t, &test) }) } } // 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, testCase *multiHopFwdTest) { 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, aliceChanID := genID() 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. chanID2, bobChanID2 := genID() bobChannelLink1 := newMockChannelLink( s, chanID2, bobChanID2, bobPeer, testCase.eligible1, ) bobChannelLink1.checkHtlcForwardResult = testCase.failure1 chanID3, bobChanID3 := genID() bobChannelLink2 := newMockChannelLink( s, chanID3, bobChanID3, bobPeer, testCase.eligible2, ) bobChannelLink2.checkHtlcForwardResult = testCase.failure2 if err := s.AddLink(aliceChannelLink); err != nil { t.Fatalf("unable to add alice link: %v", err) } if err := s.AddLink(bobChannelLink1); err != nil { t.Fatalf("unable to add bob link: %v", err) } if err := s.AddLink(bobChannelLink2); 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[:]) obfuscator := NewMockObfuscator() packet = &htlcPacket{ incomingChanID: aliceChannelLink.ShortChanID(), incomingHTLCID: 0, outgoingChanID: bobChannelLink1.ShortChanID(), htlc: &lnwire.UpdateAddHTLC{ PaymentHash: rhash, Amount: 1, }, obfuscator: obfuscator, } // The request to forward should fail as err = s.forward(packet) failure := obfuscator.(*mockObfuscator).failure if testCase.expectedReply == lnwire.CodeNone { if err != nil { t.Fatalf("forwarding should have succeeded") } if failure != nil { t.Fatalf("unexpected failure %T", failure) } } else { if err == nil { t.Fatalf("forwarding should have failed due to " + "inactive link") } if failure.Code() != testCase.expectedReply { t.Fatalf("unexpected failure %T", failure) } } 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.checkHtlcTransitResult = 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.NewFailIncorrectDetails(update.Amount, 100) 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: assertFailureCode( t, err, lnwire.CodeIncorrectOrUnknownPaymentDetails, ) 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.FailInvalidOnionKey); !ok { t.Fatalf("expected onion 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") } }