htlcswitch: add channel link tests
Step #5 in making htlcManager (aka channelLink) testable: Combine all that have been done so far and add test framework for channel links which allow unit test: * message ordering * detect redundant messages * single hop payment * multihop payment * several cancel payment scenarios
This commit is contained in:
parent
3897343db1
commit
882aec704d
596
htlcswitch/link_test.go
Normal file
596
htlcswitch/link_test.go
Normal file
@ -0,0 +1,596 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// messageToString is used to produce less spammy log messages in trace
|
||||||
|
// mode by setting the 'Curve" parameter to nil. Doing this avoids printing out
|
||||||
|
// each of the field elements in the curve parameters for secp256k1.
|
||||||
|
func messageToString(msg lnwire.Message) string {
|
||||||
|
switch m := msg.(type) {
|
||||||
|
case *lnwire.RevokeAndAck:
|
||||||
|
m.NextRevocationKey.Curve = nil
|
||||||
|
case *lnwire.NodeAnnouncement:
|
||||||
|
m.NodeID.Curve = nil
|
||||||
|
case *lnwire.ChannelAnnouncement:
|
||||||
|
m.NodeID1.Curve = nil
|
||||||
|
m.NodeID2.Curve = nil
|
||||||
|
m.BitcoinKey1.Curve = nil
|
||||||
|
m.BitcoinKey2.Curve = nil
|
||||||
|
case *lnwire.SingleFundingComplete:
|
||||||
|
m.RevocationKey.Curve = nil
|
||||||
|
case *lnwire.SingleFundingRequest:
|
||||||
|
m.CommitmentKey.Curve = nil
|
||||||
|
m.ChannelDerivationPoint.Curve = nil
|
||||||
|
case *lnwire.SingleFundingResponse:
|
||||||
|
m.ChannelDerivationPoint.Curve = nil
|
||||||
|
m.CommitmentKey.Curve = nil
|
||||||
|
m.RevocationKey.Curve = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return spew.Sdump(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createLogFunc is a helper function which returns the function which will be
|
||||||
|
// used for logging message are received from another peer.
|
||||||
|
func createLogFunc(name string, channelID lnwire.ChannelID) messageInterceptor {
|
||||||
|
return func(m lnwire.Message) {
|
||||||
|
if getChanID(m) == channelID {
|
||||||
|
// Skip logging of extend revocation window messages.
|
||||||
|
switch m := m.(type) {
|
||||||
|
case *lnwire.RevokeAndAck:
|
||||||
|
var zeroHash chainhash.Hash
|
||||||
|
if bytes.Equal(zeroHash[:], m.Revocation[:]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("---------------------- \n %v received: "+
|
||||||
|
"%v", name, messageToString(m))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkSingleHopPayment in this test we checks the interaction
|
||||||
|
// between Alice and Bob within scope of one channel.
|
||||||
|
func TestChannelLinkSingleHopPayment(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
bobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
debug := false
|
||||||
|
if debug {
|
||||||
|
// Log message that alice receives.
|
||||||
|
n.aliceServer.record(createLogFunc("alice",
|
||||||
|
n.aliceChannelLink.ChanID()))
|
||||||
|
|
||||||
|
// Log message that bob receives.
|
||||||
|
n.bobServer.record(createLogFunc("bob",
|
||||||
|
n.firstBobChannelLink.ChanID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for:
|
||||||
|
// * HTLC add request to be sent to bob.
|
||||||
|
// * alice<->bob commitment state to be updated.
|
||||||
|
// * settle request to be sent back from bob to alice.
|
||||||
|
// * alice<->bob commitment state to be updated.
|
||||||
|
// * user notification to be sent.
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
invoice, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
}, amount)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to make the payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Bob to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alice invoice was settled and bandwidth of HTLC
|
||||||
|
// links was changed.
|
||||||
|
if !invoice.Terms.Settled {
|
||||||
|
t.Fatal("invoice wasn't settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if aliceBandwidthBefore-amount != n.aliceChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("alice bandwidth should have descreased on payment " +
|
||||||
|
"amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bobBandwidthBefore+amount != n.firstBobChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("bob bandwidth isn't match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkMultiHopPayment checks the ability to send payment over two
|
||||||
|
// hopes. In this test we send the payment from Carol to Alice over Bob peer.
|
||||||
|
// (Carol -> Bob -> Alice) and checking that HTLC was settled properly and
|
||||||
|
// balances were changed in two channels.
|
||||||
|
func TestChannelLinkMultiHopPayment(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||||
|
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
debug := false
|
||||||
|
if debug {
|
||||||
|
// Log messages that alice receives from bob.
|
||||||
|
n.aliceServer.record(createLogFunc("[alice]<-bob<-carol: ",
|
||||||
|
n.aliceChannelLink.ChanID()))
|
||||||
|
|
||||||
|
// Log messages that bob receives from alice.
|
||||||
|
n.bobServer.record(createLogFunc("alice->[bob]->carol: ",
|
||||||
|
n.firstBobChannelLink.ChanID()))
|
||||||
|
|
||||||
|
// Log messages that bob receives from carol.
|
||||||
|
n.bobServer.record(createLogFunc("alice<-[bob]<-carol: ",
|
||||||
|
n.secondBobChannelLink.ChanID()))
|
||||||
|
|
||||||
|
// Log messages that carol receives from bob.
|
||||||
|
n.carolServer.record(createLogFunc("alice->bob->[carol]",
|
||||||
|
n.carolChannelLink.ChanID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for:
|
||||||
|
// * HTLC add request to be sent from Alice to Bob.
|
||||||
|
// * Alice<->Bob commitment states to be updated.
|
||||||
|
// * HTLC add request to be propagated to Carol.
|
||||||
|
// * Bob<->Carol commitment state to be updated.
|
||||||
|
// * settle request to be sent back from Carol to Bob.
|
||||||
|
// * Alice<->Bob commitment state to be updated.
|
||||||
|
// * settle request to be sent back from Bob to Alice.
|
||||||
|
// * Alice<->Bob commitment states to be updated.
|
||||||
|
// * user notification to be sent.
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
invoice, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
n.carolServer,
|
||||||
|
}, amount)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to send payment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Bob to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that Carol invoice was settled and bandwidth of HTLC
|
||||||
|
// links were changed.
|
||||||
|
if !invoice.Terms.Settled {
|
||||||
|
t.Fatal("alice invoice wasn't settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if aliceBandwidthBefore-amount != n.aliceChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("the bandwidth of alice channel link which handles " +
|
||||||
|
"alice->bob channel wasn't decreased on htlc amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstBobBandwidthBefore+amount != n.firstBobChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"alice->bob channel wasn't increased on htlc amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secondBobBandwidthBefore-amount != n.secondBobChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"bob->carol channel wasn't decreased on htlc amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if carolBandwidthBefore+amount != n.carolChannelLink.Bandwidth() {
|
||||||
|
t.Fatal("the bandwidth of carol channel link which handles " +
|
||||||
|
"carol->bob channel wasn't decreased on htlc amount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkMultiHopInsufficientPayment checks that we receive error if
|
||||||
|
// bob<->alice channel has insufficient BTC capacity/bandwidth. In this test we
|
||||||
|
// send the payment from Carol to Alice over Bob peer. (Carol -> Bob -> Alice)
|
||||||
|
func TestChannelLinkMultiHopInsufficientPayment(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatalf("can't start three hop network: %v", err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||||
|
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
// Wait for:
|
||||||
|
// * HTLC add request to be sent to from Alice to Bob.
|
||||||
|
// * Alice<->Bob commitment states to be updated.
|
||||||
|
// * Bob trying to add HTLC add request in Bob<->Carol channel.
|
||||||
|
// * Cancel HTLC request to be sent back from Bob to Alice.
|
||||||
|
// * user notification to be sent.
|
||||||
|
var amount btcutil.Amount = 4 * btcutil.SatoshiPerBitcoin
|
||||||
|
invoice, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
n.carolServer,
|
||||||
|
}, amount)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error haven't been received")
|
||||||
|
} else if err.Error() != errors.New(lnwire.InsufficientCapacity).Error() {
|
||||||
|
t.Fatalf("wrong error have been received: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Alice to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
|
// links hasn't been changed.
|
||||||
|
if invoice.Terms.Settled {
|
||||||
|
t.Fatal("alice invoice was settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of alice channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.carolChannelLink.Bandwidth() != carolBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of carol channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkMultiHopUnknownPaymentHash checks that we receive remote error
|
||||||
|
// from Alice if she received not suitable payment hash for htlc.
|
||||||
|
func TestChannelLinkMultiHopUnknownPaymentHash(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatalf("can't start three hop network: %v", err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||||
|
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
|
||||||
|
// Generate route convert it to blob, and return next destination for
|
||||||
|
// htlc add request.
|
||||||
|
peers := []Peer{
|
||||||
|
n.bobServer,
|
||||||
|
n.carolServer,
|
||||||
|
}
|
||||||
|
firstNode, blob, err := generateRoute(peers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate payment: invoice and htlc.
|
||||||
|
invoice, htlc, err := generatePayment(amount, blob)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to have wrong rhash for that reason we should change the
|
||||||
|
// preimage. Inverse first byte by xoring with 0xff.
|
||||||
|
invoice.Terms.PaymentPreimage[0] ^= byte(255)
|
||||||
|
|
||||||
|
// Check who is last in the route and add invoice to server registry.
|
||||||
|
if err := n.carolServer.registry.AddInvoice(invoice); err != nil {
|
||||||
|
t.Fatalf("can't add invoice in carol registry: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send payment and expose err channel.
|
||||||
|
if _, err := n.aliceServer.htlcSwitch.SendHTLC(firstNode,
|
||||||
|
htlc); err == nil {
|
||||||
|
t.Fatal("error wasn't received")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Alice to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
|
// links hasn't been changed.
|
||||||
|
if invoice.Terms.Settled {
|
||||||
|
t.Fatal("alice invoice was settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of alice channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.carolChannelLink.Bandwidth() != carolBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of carol channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkMultiHopUnknownNextHop construct the chain of hops
|
||||||
|
// Carol<->Bob<->Alice and checks that we receive remote error from Bob if he
|
||||||
|
// has no idea about next hop (hop might goes down and routing info not updated
|
||||||
|
// yet)
|
||||||
|
func TestChannelLinkMultiHopUnknownNextHop(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||||
|
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
|
||||||
|
dave := newMockServer(t, "save")
|
||||||
|
invoice, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
dave,
|
||||||
|
}, amount)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error haven't been received")
|
||||||
|
} else if err.Error() != errors.New(lnwire.UnknownDestination).Error() {
|
||||||
|
t.Fatalf("wrong error have been received: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Alice to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
|
// links hasn't been changed.
|
||||||
|
if invoice.Terms.Settled {
|
||||||
|
t.Fatal("alice invoice was settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of alice channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.carolChannelLink.Bandwidth() != carolBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of carol channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkMultiHopDecodeError checks that we send HTLC cancel if
|
||||||
|
// decoding of onion blob failed.
|
||||||
|
func TestChannelLinkMultiHopDecodeError(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatalf("can't start three hop network: %v", err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
// Replace decode function with another which throws an error.
|
||||||
|
n.carolChannelLink.cfg.DecodeOnion = func(r io.Reader, meta []byte) (
|
||||||
|
HopIterator, error) {
|
||||||
|
return nil, errors.New("some sphinx decode error")
|
||||||
|
}
|
||||||
|
|
||||||
|
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||||
|
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||||
|
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||||
|
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||||
|
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
invoice, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
n.carolServer,
|
||||||
|
}, amount)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error haven't been received")
|
||||||
|
} else if err.Error() != errors.New(lnwire.SphinxParseError).Error() {
|
||||||
|
t.Fatalf("wrong error have been received: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for Bob to receive the revocation.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Check that alice invoice wasn't settled and bandwidth of htlc
|
||||||
|
// links hasn't been changed.
|
||||||
|
if invoice.Terms.Settled {
|
||||||
|
t.Fatal("alice invoice was settled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.aliceChannelLink.Bandwidth() != aliceBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of alice channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.firstBobChannelLink.Bandwidth() != firstBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"alice->bob channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.secondBobChannelLink.Bandwidth() != secondBobBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of bob channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.carolChannelLink.Bandwidth() != carolBandwidthBefore {
|
||||||
|
t.Fatal("the bandwidth of carol channel link which handles " +
|
||||||
|
"bob->carol channel should be the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChannelLinkSingleHopMessageOrdering test checks ordering of message which
|
||||||
|
// flying around between Alice and Bob are correct when Bob sends payments to
|
||||||
|
// Alice.
|
||||||
|
func TestChannelLinkSingleHopMessageOrdering(t *testing.T) {
|
||||||
|
n := newThreeHopNetwork(t,
|
||||||
|
btcutil.SatoshiPerBitcoin*3,
|
||||||
|
btcutil.SatoshiPerBitcoin*5,
|
||||||
|
)
|
||||||
|
|
||||||
|
chanPoint := n.aliceChannelLink.ChanID()
|
||||||
|
|
||||||
|
// Append initial channel window revocation messages which occurs after
|
||||||
|
// channel opening.
|
||||||
|
var aliceOrder []lnwire.Message
|
||||||
|
for i := 0; i < lnwallet.InitialRevocationWindow; i++ {
|
||||||
|
aliceOrder = append(aliceOrder, &lnwire.RevokeAndAck{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The order in which Alice receives wire messages.
|
||||||
|
aliceOrder = append(aliceOrder, []lnwire.Message{
|
||||||
|
&lnwire.RevokeAndAck{},
|
||||||
|
&lnwire.CommitSig{},
|
||||||
|
&lnwire.UpdateFufillHTLC{},
|
||||||
|
&lnwire.CommitSig{},
|
||||||
|
&lnwire.RevokeAndAck{},
|
||||||
|
}...)
|
||||||
|
|
||||||
|
// Append initial channel window revocation messages which occurs after
|
||||||
|
// channel channel opening.
|
||||||
|
var bobOrder []lnwire.Message
|
||||||
|
for i := 0; i < lnwallet.InitialRevocationWindow; i++ {
|
||||||
|
bobOrder = append(bobOrder, &lnwire.RevokeAndAck{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The order in which Bob receives wire messages.
|
||||||
|
bobOrder = append(bobOrder, []lnwire.Message{
|
||||||
|
&lnwire.UpdateAddHTLC{},
|
||||||
|
&lnwire.CommitSig{},
|
||||||
|
&lnwire.RevokeAndAck{},
|
||||||
|
&lnwire.RevokeAndAck{},
|
||||||
|
&lnwire.CommitSig{},
|
||||||
|
}...)
|
||||||
|
|
||||||
|
debug := false
|
||||||
|
if debug {
|
||||||
|
// Log message that alice receives.
|
||||||
|
n.aliceServer.record(createLogFunc("alice",
|
||||||
|
n.aliceChannelLink.ChanID()))
|
||||||
|
|
||||||
|
// Log message that bob receives.
|
||||||
|
n.bobServer.record(createLogFunc("bob",
|
||||||
|
n.firstBobChannelLink.ChanID()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that alice receives messages in right order.
|
||||||
|
n.aliceServer.record(func(m lnwire.Message) {
|
||||||
|
if getChanID(m) == chanPoint {
|
||||||
|
if len(aliceOrder) == 0 {
|
||||||
|
t.Fatal("redudant messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.TypeOf(aliceOrder[0]) != reflect.TypeOf(m) {
|
||||||
|
t.Fatalf("alice received wrong message: \n"+
|
||||||
|
"real: %v\n expected: %v", m.MsgType(),
|
||||||
|
aliceOrder[0].MsgType())
|
||||||
|
}
|
||||||
|
aliceOrder = aliceOrder[1:]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check that bob receives messages in right order.
|
||||||
|
n.bobServer.record(func(m lnwire.Message) {
|
||||||
|
if getChanID(m) == chanPoint {
|
||||||
|
if len(bobOrder) == 0 {
|
||||||
|
t.Fatal("redudant messages")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reflect.TypeOf(bobOrder[0]) != reflect.TypeOf(m) {
|
||||||
|
t.Fatalf("bob received wrong message: \n"+
|
||||||
|
"real: %v\n expected: %v", m.MsgType(),
|
||||||
|
bobOrder[0].MsgType())
|
||||||
|
}
|
||||||
|
bobOrder = bobOrder[1:]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := n.start(); err != nil {
|
||||||
|
t.Fatalf("can't start three hop network: %v", err)
|
||||||
|
}
|
||||||
|
defer n.stop()
|
||||||
|
|
||||||
|
// Wait for:
|
||||||
|
// * htlc add htlc request to be sent to alice
|
||||||
|
// * alice<->bob commitment state to be updated
|
||||||
|
// * settle request to be sent back from alice to bob
|
||||||
|
// * alice<->bob commitment state to be updated
|
||||||
|
var amount btcutil.Amount = btcutil.SatoshiPerBitcoin
|
||||||
|
if _, err := n.makePayment([]Peer{
|
||||||
|
n.aliceServer,
|
||||||
|
n.bobServer,
|
||||||
|
}, amount); err != nil {
|
||||||
|
t.Fatalf("unable to make the payment: %v", err)
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,24 @@ package htlcswitch
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"io"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/btcsuite/fastsha256"
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/roasbeef/btcutil"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet"
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/roasbeef/btcd/btcec"
|
||||||
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/roasbeef/btcd/txscript"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockServer struct {
|
type mockServer struct {
|
||||||
@ -28,6 +37,7 @@ type mockServer struct {
|
|||||||
id []byte
|
id []byte
|
||||||
htlcSwitch *Switch
|
htlcSwitch *Switch
|
||||||
|
|
||||||
|
registry *mockInvoiceRegistry
|
||||||
recordFuncs []func(lnwire.Message)
|
recordFuncs []func(lnwire.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,12 +45,12 @@ var _ Peer = (*mockServer)(nil)
|
|||||||
|
|
||||||
func newMockServer(t *testing.T, name string) *mockServer {
|
func newMockServer(t *testing.T, name string) *mockServer {
|
||||||
return &mockServer{
|
return &mockServer{
|
||||||
t: t,
|
t: t,
|
||||||
id: []byte(name),
|
id: []byte(name),
|
||||||
name: name,
|
name: name,
|
||||||
messages: make(chan lnwire.Message, 3000),
|
messages: make(chan lnwire.Message, 3000),
|
||||||
|
|
||||||
quit: make(chan bool),
|
quit: make(chan bool),
|
||||||
|
registry: newMockRegistry(),
|
||||||
htlcSwitch: New(Config{}),
|
htlcSwitch: New(Config{}),
|
||||||
recordFuncs: make([]func(lnwire.Message), 0),
|
recordFuncs: make([]func(lnwire.Message), 0),
|
||||||
}
|
}
|
||||||
@ -78,6 +88,74 @@ func (s *mockServer) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mockHopIterator represents the test version of hop iterator which instead
|
||||||
|
// of encrypting the path in onion blob just stores the path as a list of hops.
|
||||||
|
type mockHopIterator struct {
|
||||||
|
hops []HopID
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockHopIterator(hops ...HopID) HopIterator {
|
||||||
|
return &mockHopIterator{hops: hops}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockHopIterator) Next() *HopID {
|
||||||
|
if len(r.hops) != 0 {
|
||||||
|
next := r.hops[0]
|
||||||
|
r.hops = r.hops[1:]
|
||||||
|
return &next
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mockHopIterator) Encode(w io.Writer) error {
|
||||||
|
var hopLength [4]byte
|
||||||
|
binary.BigEndian.PutUint32(hopLength[:], uint32(len(r.hops)))
|
||||||
|
|
||||||
|
if _, err := w.Write(hopLength[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hop := range r.hops {
|
||||||
|
if _, err := w.Write(hop[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ HopIterator = (*mockHopIterator)(nil)
|
||||||
|
|
||||||
|
// mockIteratorDecoder test version of hop iterator decoder which decodes the
|
||||||
|
// encoded array of hops.
|
||||||
|
type mockIteratorDecoder struct{}
|
||||||
|
|
||||||
|
func (p *mockIteratorDecoder) Decode(r io.Reader, meta []byte) (
|
||||||
|
HopIterator, error) {
|
||||||
|
|
||||||
|
var b [4]byte
|
||||||
|
_, err := r.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hopLength := binary.BigEndian.Uint32(b[:])
|
||||||
|
|
||||||
|
hops := make([]HopID, hopLength)
|
||||||
|
for i := uint32(0); i < hopLength; i++ {
|
||||||
|
var hop HopID
|
||||||
|
|
||||||
|
_, err := r.Read(hop[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hops[i] = hop
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMockHopIterator(hops...), nil
|
||||||
|
}
|
||||||
|
|
||||||
// messageInterceptor is function that handles the incoming peer messages and
|
// messageInterceptor is function that handles the incoming peer messages and
|
||||||
// may decide should we handle it or not.
|
// may decide should we handle it or not.
|
||||||
type messageInterceptor func(m lnwire.Message)
|
type messageInterceptor func(m lnwire.Message)
|
||||||
@ -205,3 +283,105 @@ func (f *mockChannelLink) Start() error { return nil }
|
|||||||
func (f *mockChannelLink) Stop() {}
|
func (f *mockChannelLink) Stop() {}
|
||||||
|
|
||||||
var _ ChannelLink = (*mockChannelLink)(nil)
|
var _ ChannelLink = (*mockChannelLink)(nil)
|
||||||
|
|
||||||
|
type mockInvoiceRegistry struct {
|
||||||
|
sync.Mutex
|
||||||
|
invoices map[chainhash.Hash]*channeldb.Invoice
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockRegistry() *mockInvoiceRegistry {
|
||||||
|
return &mockInvoiceRegistry{
|
||||||
|
invoices: make(map[chainhash.Hash]*channeldb.Invoice),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *mockInvoiceRegistry) LookupInvoice(rHash chainhash.Hash) (*channeldb.Invoice, error) {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
invoice, ok := i.invoices[rHash]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("can't find mock invoice")
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *mockInvoiceRegistry) SettleInvoice(rhash chainhash.Hash) error {
|
||||||
|
|
||||||
|
invoice, err := i.LookupInvoice(rhash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Lock()
|
||||||
|
invoice.Terms.Settled = true
|
||||||
|
i.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *mockInvoiceRegistry) AddInvoice(invoice *channeldb.Invoice) error {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
|
||||||
|
rhash := fastsha256.Sum256(invoice.Terms.PaymentPreimage[:])
|
||||||
|
i.invoices[chainhash.Hash(rhash)] = invoice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ InvoiceDatabase = (*mockInvoiceRegistry)(nil)
|
||||||
|
|
||||||
|
type mockSigner struct {
|
||||||
|
key *btcec.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *lnwallet.SignDescriptor) ([]byte, error) {
|
||||||
|
amt := signDesc.Output.Value
|
||||||
|
witnessScript := signDesc.WitnessScript
|
||||||
|
privKey := m.key
|
||||||
|
|
||||||
|
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
|
||||||
|
signDesc.InputIndex, amt, witnessScript, txscript.SigHashAll, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sig[:len(sig)-1], nil
|
||||||
|
}
|
||||||
|
func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *lnwallet.SignDescriptor) (*lnwallet.InputScript, error) {
|
||||||
|
|
||||||
|
witnessScript, err := txscript.WitnessScript(tx, signDesc.SigHashes,
|
||||||
|
signDesc.InputIndex, signDesc.Output.Value, signDesc.Output.PkScript,
|
||||||
|
txscript.SigHashAll, m.key, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lnwallet.InputScript{
|
||||||
|
Witness: witnessScript,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockNotifier struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockNotifier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint) (*chainntnfs.SpendEvent, error) {
|
||||||
|
return &chainntnfs.SpendEvent{
|
||||||
|
Spend: make(chan *chainntnfs.SpendDetail),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
507
htlcswitch/test_utils.go
Normal file
507
htlcswitch/test_utils.go
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
package htlcswitch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/btcsuite/fastsha256"
|
||||||
|
"github.com/go-errors/errors"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/shachain"
|
||||||
|
"github.com/roasbeef/btcd/btcec"
|
||||||
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
"github.com/roasbeef/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alicePrivKey = []byte("alice priv key")
|
||||||
|
bobPrivKey = []byte("bob priv key")
|
||||||
|
carolPrivKey = []byte("carol priv key")
|
||||||
|
)
|
||||||
|
|
||||||
|
// generateRandomBytes returns securely generated random bytes.
|
||||||
|
// It will return an error if the system's secure random
|
||||||
|
// number generator fails to function correctly, in which
|
||||||
|
// case the caller should not continue.
|
||||||
|
func generateRandomBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
|
||||||
|
_, err := rand.Read(b[:])
|
||||||
|
// Note that Err == nil only if we read len(b) bytes.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createTestChannel creates the channel and returns our and remote channels
|
||||||
|
// representations.
|
||||||
|
func createTestChannel(alicePrivKey, bobPrivKey []byte,
|
||||||
|
aliceAmount, bobAmount btcutil.Amount) (
|
||||||
|
*lnwallet.LightningChannel, *lnwallet.LightningChannel, func(), error) {
|
||||||
|
|
||||||
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), alicePrivKey)
|
||||||
|
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobPrivKey)
|
||||||
|
|
||||||
|
channelCapacity := aliceAmount + bobAmount
|
||||||
|
aliceDustLimit := btcutil.Amount(200)
|
||||||
|
bobDustLimit := btcutil.Amount(800)
|
||||||
|
csvTimeoutAlice := uint32(5)
|
||||||
|
csvTimeoutBob := uint32(4)
|
||||||
|
|
||||||
|
witnessScript, _, err := lnwallet.GenFundingPkScript(
|
||||||
|
aliceKeyPub.SerializeCompressed(),
|
||||||
|
bobKeyPub.SerializeCompressed(),
|
||||||
|
int64(channelCapacity),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash [sha256.Size]byte
|
||||||
|
randomSeed, err := generateRandomBytes(sha256.Size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
copy(hash[:], randomSeed)
|
||||||
|
|
||||||
|
prevOut := &wire.OutPoint{
|
||||||
|
Hash: chainhash.Hash(hash),
|
||||||
|
Index: 0,
|
||||||
|
}
|
||||||
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
||||||
|
|
||||||
|
bobRoot := lnwallet.DeriveRevocationRoot(bobKeyPriv, bobKeyPub,
|
||||||
|
aliceKeyPub)
|
||||||
|
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
|
||||||
|
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
bobRevokeKey := lnwallet.DeriveRevocationPubkey(aliceKeyPub,
|
||||||
|
bobFirstRevoke[:])
|
||||||
|
|
||||||
|
aliceRoot := lnwallet.DeriveRevocationRoot(aliceKeyPriv, aliceKeyPub,
|
||||||
|
bobKeyPub)
|
||||||
|
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
|
||||||
|
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
aliceRevokeKey := lnwallet.DeriveRevocationPubkey(bobKeyPub,
|
||||||
|
aliceFirstRevoke[:])
|
||||||
|
|
||||||
|
aliceCommitTx, err := lnwallet.CreateCommitTx(
|
||||||
|
fundingTxIn,
|
||||||
|
aliceKeyPub,
|
||||||
|
bobKeyPub,
|
||||||
|
aliceRevokeKey,
|
||||||
|
csvTimeoutAlice,
|
||||||
|
aliceAmount,
|
||||||
|
bobAmount,
|
||||||
|
lnwallet.DefaultDustLimit(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
bobCommitTx, err := lnwallet.CreateCommitTx(
|
||||||
|
fundingTxIn,
|
||||||
|
bobKeyPub,
|
||||||
|
aliceKeyPub,
|
||||||
|
bobRevokeKey,
|
||||||
|
csvTimeoutBob,
|
||||||
|
bobAmount,
|
||||||
|
aliceAmount,
|
||||||
|
lnwallet.DefaultDustLimit(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
alicePath, err := ioutil.TempDir("", "alicedb")
|
||||||
|
dbAlice, err := channeldb.Open(alicePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bobPath, err := ioutil.TempDir("", "bobdb")
|
||||||
|
dbBob, err := channeldb.Open(bobPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var obsfucator [lnwallet.StateHintSize]byte
|
||||||
|
copy(obsfucator[:], aliceFirstRevoke[:])
|
||||||
|
|
||||||
|
aliceChannelState := &channeldb.OpenChannel{
|
||||||
|
IdentityPub: aliceKeyPub,
|
||||||
|
ChanID: prevOut,
|
||||||
|
ChanType: channeldb.SingleFunder,
|
||||||
|
IsInitiator: true,
|
||||||
|
StateHintObsfucator: obsfucator,
|
||||||
|
OurCommitKey: aliceKeyPub,
|
||||||
|
TheirCommitKey: bobKeyPub,
|
||||||
|
Capacity: channelCapacity,
|
||||||
|
OurBalance: aliceAmount,
|
||||||
|
TheirBalance: bobAmount,
|
||||||
|
OurCommitTx: aliceCommitTx,
|
||||||
|
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
|
FundingOutpoint: prevOut,
|
||||||
|
OurMultiSigKey: aliceKeyPub,
|
||||||
|
TheirMultiSigKey: bobKeyPub,
|
||||||
|
FundingWitnessScript: witnessScript,
|
||||||
|
LocalCsvDelay: csvTimeoutAlice,
|
||||||
|
RemoteCsvDelay: csvTimeoutBob,
|
||||||
|
TheirCurrentRevocation: bobRevokeKey,
|
||||||
|
RevocationProducer: alicePreimageProducer,
|
||||||
|
RevocationStore: shachain.NewRevocationStore(),
|
||||||
|
TheirDustLimit: bobDustLimit,
|
||||||
|
OurDustLimit: aliceDustLimit,
|
||||||
|
Db: dbAlice,
|
||||||
|
}
|
||||||
|
bobChannelState := &channeldb.OpenChannel{
|
||||||
|
IdentityPub: bobKeyPub,
|
||||||
|
ChanID: prevOut,
|
||||||
|
ChanType: channeldb.SingleFunder,
|
||||||
|
IsInitiator: false,
|
||||||
|
StateHintObsfucator: obsfucator,
|
||||||
|
OurCommitKey: bobKeyPub,
|
||||||
|
TheirCommitKey: aliceKeyPub,
|
||||||
|
Capacity: channelCapacity,
|
||||||
|
OurBalance: bobAmount,
|
||||||
|
TheirBalance: aliceAmount,
|
||||||
|
OurCommitTx: bobCommitTx,
|
||||||
|
OurCommitSig: bytes.Repeat([]byte{1}, 71),
|
||||||
|
FundingOutpoint: prevOut,
|
||||||
|
OurMultiSigKey: bobKeyPub,
|
||||||
|
TheirMultiSigKey: aliceKeyPub,
|
||||||
|
FundingWitnessScript: witnessScript,
|
||||||
|
LocalCsvDelay: csvTimeoutBob,
|
||||||
|
RemoteCsvDelay: csvTimeoutAlice,
|
||||||
|
TheirCurrentRevocation: aliceRevokeKey,
|
||||||
|
RevocationProducer: bobPreimageProducer,
|
||||||
|
RevocationStore: shachain.NewRevocationStore(),
|
||||||
|
TheirDustLimit: aliceDustLimit,
|
||||||
|
OurDustLimit: bobDustLimit,
|
||||||
|
Db: dbBob,
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanUpFunc := func() {
|
||||||
|
os.RemoveAll(bobPath)
|
||||||
|
os.RemoveAll(alicePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceSigner := &mockSigner{aliceKeyPriv}
|
||||||
|
bobSigner := &mockSigner{bobKeyPriv}
|
||||||
|
estimator := &lnwallet.StaticFeeEstimator{
|
||||||
|
FeeRate: 24,
|
||||||
|
Confirmation: 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
channelAlice, err := lnwallet.NewLightningChannel(aliceSigner,
|
||||||
|
nil, estimator, aliceChannelState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
channelBob, err := lnwallet.NewLightningChannel(bobSigner, nil,
|
||||||
|
estimator, bobChannelState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelAlice, channelBob, cleanUpFunc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getChanID retrieves the channel point from nwire message.
|
||||||
|
func getChanID(msg lnwire.Message) lnwire.ChannelID {
|
||||||
|
var point lnwire.ChannelID
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *lnwire.UpdateAddHTLC:
|
||||||
|
point = msg.ChanID
|
||||||
|
case *lnwire.UpdateFufillHTLC:
|
||||||
|
point = msg.ChanID
|
||||||
|
case *lnwire.UpdateFailHTLC:
|
||||||
|
point = msg.ChanID
|
||||||
|
case *lnwire.RevokeAndAck:
|
||||||
|
point = msg.ChanID
|
||||||
|
case *lnwire.CommitSig:
|
||||||
|
point = msg.ChanID
|
||||||
|
}
|
||||||
|
|
||||||
|
return point
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePayment generates the htlc add request by given path blob and
|
||||||
|
// invoice which should be added by destination peer.
|
||||||
|
func generatePayment(amount btcutil.Amount, blob [lnwire.OnionPacketSize]byte) (
|
||||||
|
*channeldb.Invoice, *lnwire.UpdateAddHTLC, error) {
|
||||||
|
|
||||||
|
// Initialize random seed with unix time in order to generate random
|
||||||
|
// preimage every time.
|
||||||
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
|
|
||||||
|
var preimage [sha256.Size]byte
|
||||||
|
r, err := generateRandomBytes(sha256.Size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
copy(preimage[:], r)
|
||||||
|
rhash := fastsha256.Sum256(preimage[:])
|
||||||
|
|
||||||
|
// Generate and add the invoice in carol invoice registry as far as
|
||||||
|
// htlc request should go to the
|
||||||
|
return &channeldb.Invoice{
|
||||||
|
CreationDate: time.Now(),
|
||||||
|
Terms: channeldb.ContractTerm{
|
||||||
|
Value: amount,
|
||||||
|
PaymentPreimage: preimage,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&lnwire.UpdateAddHTLC{
|
||||||
|
PaymentHash: rhash,
|
||||||
|
Amount: amount,
|
||||||
|
OnionBlob: blob,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRoute generates the path blob by given array of peers.
|
||||||
|
func generateRoute(peers []Peer) ([]byte, [lnwire.OnionPacketSize]byte, error) {
|
||||||
|
var blob [lnwire.OnionPacketSize]byte
|
||||||
|
if len(peers) == 0 {
|
||||||
|
return nil, blob, errors.New("empty path")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create array of hops in order to create onion blob.
|
||||||
|
hops := make([]HopID, len(peers)-1)
|
||||||
|
for i, peer := range peers[1:] {
|
||||||
|
hops[i] = NewHopID(peer.PubKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize iterator and encode it.
|
||||||
|
var b bytes.Buffer
|
||||||
|
iterator := newMockHopIterator(hops...)
|
||||||
|
if err := iterator.Encode(&b); err != nil {
|
||||||
|
return nil, blob, err
|
||||||
|
}
|
||||||
|
copy(blob[:], b.Bytes())
|
||||||
|
|
||||||
|
return peers[0].PubKey(), blob, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// threeHopNetwork is used for managing the created cluster of 3 hops.
|
||||||
|
type threeHopNetwork struct {
|
||||||
|
aliceServer *mockServer
|
||||||
|
aliceChannelLink *channelLink
|
||||||
|
|
||||||
|
firstBobChannelLink *channelLink
|
||||||
|
bobServer *mockServer
|
||||||
|
secondBobChannelLink *channelLink
|
||||||
|
|
||||||
|
carolChannelLink *channelLink
|
||||||
|
carolServer *mockServer
|
||||||
|
|
||||||
|
firstChannelCleanup func()
|
||||||
|
secondChannelCleanup func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// makePayment takes the destination node and amount as input, sends the
|
||||||
|
// payment and returns the error channel to wait for error to be received and
|
||||||
|
// invoice in order to check its status after the payment finished.
|
||||||
|
//
|
||||||
|
// With this function you can send payments:
|
||||||
|
// * from Alice to Bob
|
||||||
|
// * from Alice to Carol through the Bob
|
||||||
|
// * from Alice to some another peer through the Bob
|
||||||
|
func (n *threeHopNetwork) makePayment(peers []Peer,
|
||||||
|
amount btcutil.Amount) (*channeldb.Invoice, error) {
|
||||||
|
|
||||||
|
// Extract sender peer.
|
||||||
|
senderPeer := peers[0].(*mockServer)
|
||||||
|
peers = peers[1:]
|
||||||
|
|
||||||
|
// Generate route convert it to blob, and return next destination for
|
||||||
|
// htlc add request.
|
||||||
|
firstNode, blob, err := generateRoute(peers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate payment: invoice and htlc.
|
||||||
|
invoice, htlc, err := generatePayment(amount, blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check who is last in the route and add invoice to server registry.
|
||||||
|
receiverPeer := peers[len(peers)-1].(*mockServer)
|
||||||
|
if err := receiverPeer.registry.AddInvoice(invoice); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send payment and expose err channel.
|
||||||
|
errChan := make(chan error)
|
||||||
|
go func() {
|
||||||
|
_, err := senderPeer.htlcSwitch.SendHTLC(firstNode, htlc)
|
||||||
|
errChan <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return invoice, err
|
||||||
|
case <-time.After(6 * time.Second):
|
||||||
|
return invoice, errors.New("htlc was no settled in time")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start starts the three hop network alice,bob,carol servers.
|
||||||
|
func (n *threeHopNetwork) start() error {
|
||||||
|
if err := n.aliceServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := n.bobServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := n.carolServer.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop stops nodes and cleanup its databases.
|
||||||
|
func (n *threeHopNetwork) stop() {
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
n.aliceServer.Stop()
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n.bobServer.Stop()
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
n.carolServer.Stop()
|
||||||
|
done <- struct{}{}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
n.firstChannelCleanup()
|
||||||
|
n.secondChannelCleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// newThreeHopNetwork function creates the following topology and returns the
|
||||||
|
// control object to manage this cluster:
|
||||||
|
//
|
||||||
|
// alice bob carol
|
||||||
|
// server - <-connection-> - server - - <-connection-> - - - server
|
||||||
|
// | | |
|
||||||
|
// alice htlc bob htlc carol htlc
|
||||||
|
// switch switch \ switch
|
||||||
|
// | | \ |
|
||||||
|
// | | \ |
|
||||||
|
// alice first bob second bob carol
|
||||||
|
// channel link channel link channel link channel link
|
||||||
|
//
|
||||||
|
func newThreeHopNetwork(t *testing.T, aliceToBob,
|
||||||
|
bobToCarol btcutil.Amount) *threeHopNetwork {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Create three peers/servers.
|
||||||
|
aliceServer := newMockServer(t, "alice")
|
||||||
|
bobServer := newMockServer(t, "bob")
|
||||||
|
carolServer := newMockServer(t, "carol")
|
||||||
|
|
||||||
|
// Create mock decoder instead of sphinx one in order to mock the
|
||||||
|
// route which htlc should follow.
|
||||||
|
decoder := &mockIteratorDecoder{}
|
||||||
|
|
||||||
|
// Create lightning channels between Alice<->Bob and Bob<->Carol
|
||||||
|
aliceChannel, firstBobChannel, fCleanUp, err := createTestChannel(
|
||||||
|
alicePrivKey, bobPrivKey, aliceToBob, aliceToBob)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create alice<->bob channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondBobChannel, carolChannel, sCleanUp, err := createTestChannel(
|
||||||
|
bobPrivKey, carolPrivKey, bobToCarol, bobToCarol)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create bob<->carol channel: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceChannelLink := NewChannelLink(
|
||||||
|
&ChannelLinkConfig{
|
||||||
|
// htlc responses will be sent to this node
|
||||||
|
Peer: bobServer,
|
||||||
|
// htlc will be propagated to this switch
|
||||||
|
Switch: aliceServer.htlcSwitch,
|
||||||
|
// route will be generated by this decoder
|
||||||
|
DecodeOnion: decoder.Decode,
|
||||||
|
Registry: aliceServer.registry,
|
||||||
|
}, aliceChannel)
|
||||||
|
if err := aliceServer.htlcSwitch.addLink(aliceChannelLink); err != nil {
|
||||||
|
t.Fatalf("unable to add alice channel link: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstBobChannelLink := NewChannelLink(
|
||||||
|
&ChannelLinkConfig{
|
||||||
|
Peer: aliceServer,
|
||||||
|
Switch: bobServer.htlcSwitch,
|
||||||
|
DecodeOnion: decoder.Decode,
|
||||||
|
Registry: bobServer.registry,
|
||||||
|
}, firstBobChannel)
|
||||||
|
if err := bobServer.htlcSwitch.addLink(firstBobChannelLink); err != nil {
|
||||||
|
t.Fatalf("unable to add first bob channel link: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondBobChannelLink := NewChannelLink(
|
||||||
|
&ChannelLinkConfig{
|
||||||
|
Peer: carolServer,
|
||||||
|
Switch: bobServer.htlcSwitch,
|
||||||
|
DecodeOnion: decoder.Decode,
|
||||||
|
Registry: bobServer.registry,
|
||||||
|
}, secondBobChannel)
|
||||||
|
|
||||||
|
if err := bobServer.htlcSwitch.addLink(secondBobChannelLink); err != nil {
|
||||||
|
t.Fatalf("unable to add second bob channel link: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
carolChannelLink := NewChannelLink(
|
||||||
|
&ChannelLinkConfig{
|
||||||
|
Peer: bobServer,
|
||||||
|
Switch: carolServer.htlcSwitch,
|
||||||
|
DecodeOnion: decoder.Decode,
|
||||||
|
Registry: carolServer.registry,
|
||||||
|
}, carolChannel)
|
||||||
|
if err := carolServer.htlcSwitch.addLink(carolChannelLink); err != nil {
|
||||||
|
t.Fatalf("unable to add carol channel link: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &threeHopNetwork{
|
||||||
|
aliceServer: aliceServer,
|
||||||
|
aliceChannelLink: aliceChannelLink.(*channelLink),
|
||||||
|
firstBobChannelLink: firstBobChannelLink.(*channelLink),
|
||||||
|
bobServer: bobServer,
|
||||||
|
secondBobChannelLink: secondBobChannelLink.(*channelLink),
|
||||||
|
carolChannelLink: carolChannelLink.(*channelLink),
|
||||||
|
carolServer: carolServer,
|
||||||
|
|
||||||
|
firstChannelCleanup: fCleanUp,
|
||||||
|
secondChannelCleanup: sCleanUp,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user