From 14c6770b769c43bf4967da96480275159f2f58d1 Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Sat, 15 Oct 2016 16:18:38 +0300 Subject: [PATCH 1/5] general: fix typos --- fundingmanager.go | 9 +++++---- lnwallet/channel.go | 10 +++++----- lnwallet/channel_test.go | 8 ++++---- lnwallet/wallet.go | 28 +++++++++++++-------------- lnwire/error_generic.go | 6 +++--- lnwire/single_funding_open_proof.go | 2 +- lnwire/single_funding_signcomplete.go | 2 +- networktest.go | 6 +++--- peer.go | 12 ++++++------ 9 files changed, 42 insertions(+), 41 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index b8d3d0146..c8cfbe087 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -24,7 +24,7 @@ const ( // reservationWithCtx encapsulates a pending channel reservation. This wrapper // struct is used internally within the funding manager to track and progress -// the funding workflow initiated by incoming/outgoing meethods from the target +// the funding workflow initiated by incoming/outgoing methods from the target // peer. Additionally, this struct houses a response and error channel which is // used to respond to the caller in the case a channel workflow is initiated // via a local signal such as RPC. @@ -121,7 +121,7 @@ type fundingManager struct { // state of the funding manager. queries chan interface{} - // fundingRequests is a channel used to recieve channel initiation + // fundingRequests is a channel used to receive channel initiation // requests from a local sub-system within the daemon. fundingRequests chan *initFundingMsg @@ -286,7 +286,7 @@ func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) { } // processFundingRequest sends a message to the fundingManager allowing it to -// intiate the new funding workflow with the source peer. +// initiate the new funding workflow with the source peer. func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, peer *peer) { f.fundingMsgs <- &fundingRequestMsg{msg, peer} } @@ -319,7 +319,7 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) { return } - // Once the reservation has been created succesfully, we add it to this + // Once the reservation has been created successfully, we add it to this // peers map of pending reservations to track this particular reservation // until either abort or completion. f.resMtx.Lock() @@ -719,6 +719,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { if _, ok := f.activeReservations[msg.peer.id]; !ok { f.activeReservations[msg.peer.id] = make(pendingChannels) } + f.activeReservations[msg.peer.id][chanID] = &reservationWithCtx{ reservation: reservation, peer: msg.peer, diff --git a/lnwallet/channel.go b/lnwallet/channel.go index e36a1b912..1adff1575 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -152,8 +152,8 @@ type PaymentDescriptor struct { // commitment represents a commitment to a new state within an active channel. // New commitments can be initiated by either side. Commitments are ordered // into a commitment chain, with one existing for both parties. Each side can -// independatly extend the other side's commitment chain, up to a certain -// "revocation window", which once reached, dissallows new commitments until +// independently extend the other side's commitment chain, up to a certain +// "revocation window", which once reached, disallows new commitments until // the local nodes receives the revocation for the remote node's chain tail. type commitment struct { // height represents the commitment height of this commitment, or the @@ -162,7 +162,7 @@ type commitment struct { // [our|their]MessageIndex are indexes into the HTLC log, up to which // this commitment transaction includes. These indexes allow both sides - // to independantly, and concurrent send create new commitments. Each + // to independently, and concurrent send create new commitments. Each // new commitment sent to the remote party includes an index in the // shared log which details which of their updates we're including in // this new commitment. @@ -232,7 +232,7 @@ func (c *commitment) toChannelDelta() (*channeldb.ChannelDelta, error) { // commitmentChain represents a chain of unrevoked commitments. The tail of the // chain is the latest fully signed, yet unrevoked commitment. Two chains are // tracked, one for the local node, and another for the remote node. New -// commitmetns we create locally extend the remote node's chain, and vice +// commitments we create locally extend the remote node's chain, and vice // versa. Commitment chains are allowed to grow to a bounded length, after // which the tail needs to be "dropped" before new commitments can be received. // The tail is "dropped" when the owner of the chain sends a revocation for the @@ -1278,7 +1278,7 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.HTLCAddRequest) uint32 { return pd.Index } -// SettleHTLC attempst to settle an existing outstanding received HTLC. The +// SettleHTLC attempts to settle an existing outstanding received HTLC. The // remote log index of the HTLC settled is returned in order to facilitate // creating the corresponding wire message. In the case the supplied pre-image // is invalid, an error is returned. diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index b5645c651..55be46048 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -302,7 +302,7 @@ func createTestChannels(revocationWindow int) (*LightningChannel, *LightningChan // local node (Alice in this case) creates a new outgoing HTLC to bob, commits // this change, then bob immediately commits a settlement of the HTLC after the // initial add is fully commited in both commit chains. -// TODO(roasbeef): write higher level framework to excercise various states of +// TODO(roasbeef): write higher level framework to exercise various states of // the state machine // * DSL language perhaps? // * constructed via input/output files @@ -348,7 +348,7 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { t.Fatalf("alice unable to sign commitment: %v", err) } - // Bob recieves this signature message, then generates a signature for + // Bob receives this signature message, then generates a signature for // Alice's commitment transaction, and the revocation to his prior // commitment transaction. if err := bobChannel.ReceiveNewCommitment(aliceSig, bobLogIndex); err != nil { @@ -363,12 +363,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { t.Fatalf("unable to generate bob revocation: %v", err) } - // Alice then proceses bob's signature, and generates a revocation for + // Alice then processes bob's signature, and generates a revocation for // bob. if err := aliceChannel.ReceiveNewCommitment(bobSig, aliceLogIndex); err != nil { t.Fatalf("alice unable to process bob's new commitment: %v", err) } - // Alice then processes this revocation, sending her own revovation for + // Alice then processes this revocation, sending her own recovation for // her prior commitment transaction. Alice shouldn't have any HTLC's to // forward since she's sending anoutgoing HTLC. if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation); err != nil { diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 821960102..862dbcbbd 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -62,8 +62,8 @@ func (e *ErrInsufficientFunds) Error() string { // initFundingReserveReq is the first message sent to initiate the workflow // required to open a payment channel with a remote peer. The initial required -// paramters are configurable accross channels. These paramters are to be chosen -// depending on the fee climate within the network, and time value of funds to +// parameters are configurable across channels. These parameters are to be +// chosen depending on the fee climate within the network, and time value of funds to // be locked up within the channel. Upon success a ChannelReservation will be // created in order to track the lifetime of this pending channel. Outputs // selected will be 'locked', making them unavailable, for any other pending @@ -239,7 +239,7 @@ type LightningWallet struct { // This mutex MUST be held when performing coin selection in order to // avoid inadvertently creating multiple funding transaction which - // double spend inputs accross each other. + // double spend inputs across each other. coinSelectMtx sync.RWMutex // A wrapper around a namespace within boltdb reserved for ln-based @@ -263,14 +263,14 @@ type LightningWallet struct { Signer Signer // chainIO is an instance of the BlockChainIO interface. chainIO is - // used to lookup the existance of outputs within the utxo set. + // used to lookup the existence of outputs within the UTXO set. chainIO BlockChainIO - // rootKey is the root HD key dervied from a WalletController private + // rootKey is the root HD key derived from a WalletController private // key. This rootKey is used to derive all LN specific secrets. rootKey *hdkeychain.ExtendedKey - // All messages to the wallet are to be sent accross this channel. + // All messages to the wallet are to be sent across this channel. msgChan chan interface{} // Incomplete payment channels are stored in the map below. An intent @@ -423,7 +423,7 @@ func (l *LightningWallet) GetIdentitykey() (*btcec.PrivateKey, error) { return identityKey.ECPrivKey() } -// requestHandler is the primary goroutine(s) resposible for handling, and +// requestHandler is the primary goroutine(s) responsible for handling, and // dispatching relies to all messages. func (l *LightningWallet) requestHandler() { out: @@ -455,15 +455,15 @@ out: l.wg.Done() } -// InitChannelReservation kicks off the 3-step workflow required to succesfully +// InitChannelReservation kicks off the 3-step workflow required to successfully // open a payment channel with a remote node. As part of the funding // reservation, the inputs selected for the funding transaction are 'locked'. // This ensures that multiple channel reservations aren't double spending the // same inputs in the funding transaction. If reservation initialization is -// succesful, a ChannelReservation containing our completed contribution is -// returned. Our contribution contains all the items neccessary to allow the +// successful, a ChannelReservation containing our completed contribution is +// returned. Our contribution contains all the items necessary to allow the // counter party to build the funding transaction, and both versions of the -// commitment transaction. Otherwise, an error occured a nil pointer along with +// commitment transaction. Otherwise, an error occurred a nil pointer along with // an error are returned. // // Once a ChannelReservation has been obtained, two additional steps must be @@ -500,7 +500,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg reservation := NewChannelReservation(totalCapacity, req.fundingAmount, req.minFeeRate, l, id, req.numConfs) - // Grab the mutex on the ChannelReservation to ensure thead-safety + // Grab the mutex on the ChannelReservation to ensure thread-safety reservation.Lock() defer reservation.Unlock() @@ -568,9 +568,9 @@ func (l *LightningWallet) handleFundingReserveRequest(req *initFundingReserveMsg l.fundingLimbo[id] = reservation l.limboMtx.Unlock() - // Funding reservation request succesfully handled. The funding inputs + // Funding reservation request successfully handled. The funding inputs // will be marked as unavailable until the reservation is either - // completed, or cancecled. + // completed, or canceled. req.resp <- reservation req.err <- nil } diff --git a/lnwire/error_generic.go b/lnwire/error_generic.go index 70dcfdf25..1a670e24b 100644 --- a/lnwire/error_generic.go +++ b/lnwire/error_generic.go @@ -8,11 +8,11 @@ import ( ) // ErrorGeneric represents a generic error bound to an exact channel. The -// message format is purposefully general in order to allow expressino of a wide +// message format is purposefully general in order to allow expression of a wide // array of possible errors. Each ErrorGeneric message is directed at a particular // open channel referenced by ChannelPoint. type ErrorGeneric struct { - // ChannelPoint references the active channel in which the error occured + // ChannelPoint references the active channel in which the error occurred // within. A ChannelPoint of zeroHash:0 denotes this error applies to // the entire established connection. ChannelPoint *wire.OutPoint @@ -22,7 +22,7 @@ type ErrorGeneric struct { ErrorID uint16 // Problem is a human-readable string further elaborating upon the - // nature of the exact error. The maxmium allowed length of this + // nature of the exact error. The maximum allowed length of this // message is 8192 bytes. Problem string } diff --git a/lnwire/single_funding_open_proof.go b/lnwire/single_funding_open_proof.go index 7a53ddded..aacbdfcaf 100644 --- a/lnwire/single_funding_open_proof.go +++ b/lnwire/single_funding_open_proof.go @@ -9,7 +9,7 @@ import ( // responder after the previously constructed funding transaction has achieved // a sufficient number of confirmations. It is the initiator's duty to present // a proof of an open channel to the responder. Otherwise, responding node may -// be vulernable to a resource exhasution attack wherein the a requesting node +// be vulnerable to a resource exhaustion attack wherein the a requesting node // repeatedly negotiates funding transactions which are never broadcast. If the // responding node commits resources to watch the chain for each funding // transaction, then this attack is very cheap for the initiator. diff --git a/lnwire/single_funding_signcomplete.go b/lnwire/single_funding_signcomplete.go index 3d69ecb2b..15718b718 100644 --- a/lnwire/single_funding_signcomplete.go +++ b/lnwire/single_funding_signcomplete.go @@ -8,7 +8,7 @@ import ( ) // SingleFundingSignComplete is the message Bob sends to Alice which delivers -// a signature for Alice's version of the commitment transaction. After thie +// a signature for Alice's version of the commitment transaction. After this // message is received and processed by Alice, she is free to broadcast the // funding transaction. type SingleFundingSignComplete struct { diff --git a/networktest.go b/networktest.go index c78499761..c87879687 100644 --- a/networktest.go +++ b/networktest.go @@ -69,7 +69,7 @@ func generateListeningPorts() (int, int) { // lightningNode represents an instance of lnd running within our test network // harness. Each lightningNode instance also fully embedds an RPC client in -// order to programatically drive the node. +// order to pragmatically drive the node. type lightningNode struct { cfg *config @@ -573,9 +573,9 @@ func (n *networkHarness) WaitForTxBroadcast(ctx context.Context, txid wire.ShaHa } } -// OpenChannel attemps to open a channel between srcNode and destNode with the +// OpenChannel attempts to open a channel between srcNode and destNode with the // passed channel funding parameters. If the passed context has a timeout, then -// if the timeout is reeached before the channel pending notification is +// if the timeout is reached before the channel pending notification is // received, an error is returned. func (n *networkHarness) OpenChannel(ctx context.Context, srcNode, destNode *lightningNode, amt btcutil.Amount, diff --git a/peer.go b/peer.go index 218a8f05f..a996c4be5 100644 --- a/peer.go +++ b/peer.go @@ -354,7 +354,7 @@ out: break out } - var isChanUpate bool + var isChanUpdate bool var targetChan *wire.OutPoint switch msg := nextMsg.(type) { @@ -374,16 +374,16 @@ out: // TODO(roasbeef): interface for htlc update msgs // * .(CommitmentUpdater) case *lnwire.HTLCAddRequest: - isChanUpate = true + isChanUpdate = true targetChan = msg.ChannelPoint case *lnwire.HTLCSettleRequest: - isChanUpate = true + isChanUpdate = true targetChan = msg.ChannelPoint case *lnwire.CommitRevocation: - isChanUpate = true + isChanUpdate = true targetChan = msg.ChannelPoint case *lnwire.CommitSignature: - isChanUpate = true + isChanUpdate = true targetChan = msg.ChannelPoint case *lnwire.NeighborAckMessage, *lnwire.NeighborHelloMessage, @@ -395,7 +395,7 @@ out: p.server.routingMgr.ReceiveRoutingMessage(msg, graph.NewID(([32]byte)(p.lightningID))) } - if isChanUpate { + if isChanUpdate { // We might be receiving an update to a newly funded // channel in which we were the responder. Therefore // we need to possibly block until the new channel has From b0525cf478a9226d4ac59b9f525dc3d5d07d441c Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Sat, 15 Oct 2016 16:19:11 +0300 Subject: [PATCH 2/5] wire: add PendingID in ErrorGeneric --- lnwire/error_generic.go | 10 ++++++++++ lnwire/error_generic_test.go | 1 + 2 files changed, 11 insertions(+) diff --git a/lnwire/error_generic.go b/lnwire/error_generic.go index 1a670e24b..c5ff2f0de 100644 --- a/lnwire/error_generic.go +++ b/lnwire/error_generic.go @@ -25,6 +25,13 @@ type ErrorGeneric struct { // nature of the exact error. The maximum allowed length of this // message is 8192 bytes. Problem string + + // PendingChannelID allows peers communicate errors in the context of a + // particular pending channel. With this field, once a peer reads an + // ErrorGeneric message with the PendingChannelID field set, then they + // can forward the error to the fundingManager who can handle it + // properly. + PendingChannelID uint64 } // NewErrorGeneric creates a new ErrorGeneric message. @@ -47,6 +54,7 @@ func (c *ErrorGeneric) Decode(r io.Reader, pver uint32) error { &c.ChannelPoint, &c.ErrorID, &c.Problem, + &c.PendingChannelID, ) if err != nil { return err @@ -64,6 +72,7 @@ func (c *ErrorGeneric) Encode(w io.Writer, pver uint32) error { c.ChannelPoint, c.ErrorID, c.Problem, + c.PendingChannelID, ) if err != nil { return err @@ -110,5 +119,6 @@ func (c *ErrorGeneric) String() string { fmt.Sprintf("ChannelPoint:\t%d\n", c.ChannelPoint) + fmt.Sprintf("ErrorID:\t%d\n", c.ErrorID) + fmt.Sprintf("Problem:\t%s\n", c.Problem) + + fmt.Sprintf("PendingChannelID:\t%s\n", c.PendingChannelID) + fmt.Sprintf("--- End ErrorGeneric ---\n") } diff --git a/lnwire/error_generic_test.go b/lnwire/error_generic_test.go index a07e8a605..37519785b 100644 --- a/lnwire/error_generic_test.go +++ b/lnwire/error_generic_test.go @@ -11,6 +11,7 @@ func TestErrorGenericEncodeDecode(t *testing.T) { ChannelPoint: outpoint1, ErrorID: 99, Problem: "Hello world!", + PendingChannelID: 1, } // Next encode the EG message into an empty bytes buffer. From e6f45a948ecdeac3262199caf6f438f994cf1bc7 Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Sat, 15 Oct 2016 16:47:09 +0300 Subject: [PATCH 3/5] testing: add CT (Custom Testing) structure; create uniq point for 'lnd process errors' and 'test panic/failed errors' handling --- glide.lock | 3 + glide.yaml | 1 + lnd_test.go | 391 ++++++++++++++++++++++++++++--------------------- networktest.go | 37 ++++- 4 files changed, 264 insertions(+), 168 deletions(-) diff --git a/glide.lock b/glide.lock index 81a55faa7..d593f7e7c 100644 --- a/glide.lock +++ b/glide.lock @@ -155,4 +155,7 @@ imports: - naming - transport - peer +- name: github.com/go-errors/errors + version: a41850380601eeb43f4350f7d17c6bbd8944aaf8 + testImports: [] diff --git a/glide.yaml b/glide.yaml index 5ee80dad7..332176845 100644 --- a/glide.yaml +++ b/glide.yaml @@ -60,3 +60,4 @@ import: - package: github.com/grpc-ecosystem/grpc-gateway version: ^1.1.0 - package: github.com/aead/chacha20 +- package: github.com/go-errors/errors diff --git a/lnd_test.go b/lnd_test.go index c0349da55..43c0a4c98 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -3,42 +3,100 @@ package main import ( "bytes" "fmt" - "runtime/debug" - "sync" - "testing" - "time" - "golang.org/x/net/context" - + "sync" + "time" "github.com/davecgh/go-spew/spew" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" "github.com/roasbeef/btcd/rpctest" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcrpcclient" "github.com/roasbeef/btcutil" + "testing" ) -func assertTxInBlock(block *btcutil.Block, txid *wire.ShaHash, t *testing.T) { +// CT is needed for: +// - have uniform way of handling panic and fatal error from test cases. +// - have ability to properly wrap errors in order to see stack trace. +// - have nice and elegant way to handle lnd process errors in one +// select structure with test cases. +type CT struct { + *testing.T + + // Channel for sending retransmitted panic errors and fatal error which + // happens in test case. + errChan chan error +} + +func NewCT(t *testing.T) *CT { + return &CT{t, nil} +} + +func (ct *CT) Error(err error) { + if ct.errChan != nil { + ct.errChan <- fmt.Errorf(errors.Wrap(err, 1).ErrorStack()) + ct.FailNow() + } else { + ct.Fatal("can't sen error when test isn't running") + } +} + +// Errorf create and send the description about the error in the error channel +// and exit. +func (ct *CT) Errorf(format string, a ...interface{}) { + if ct.errChan != nil { + description := fmt.Sprintf(format, a...) + ct.errChan <- fmt.Errorf(errors.Wrap(description, 1).ErrorStack()) + ct.FailNow() + } else { + ct.Fatal("can't sen error when test isn't running") + } + +} + +// RunTest wraps test case function in goroutine and also redirects the panic +// error from test case into error channel. +func (ct *CT) RunTest(net *networkHarness, test testCase) chan error { + // a channel to signal that test was exited with error + ct.errChan = make(chan error) + + go func() { + defer func() { + if err := recover(); err != nil { + // Retransmit test panic into main "process" + ct.errChan <- fmt.Errorf(err.(string)) + } + close(ct.errChan) + ct.errChan = nil + }() + test(net, ct) + }() + + return ct.errChan +} + +func assertTxInBlock(ct *CT, block *btcutil.Block, txid *wire.ShaHash) { for _, tx := range block.Transactions() { if bytes.Equal(txid[:], tx.Sha()[:]) { return } } - t.Fatalf("funding tx was not included in block") + ct.Errorf("funding tx was not included in block") } // openChannelAndAssert attempts to open a channel with the specified // parameters extended from Alice to Bob. Additionally, two items are asserted -// after the channel is considered open: the funding transactino should be +// after the channel is considered open: the funding transaction should be // found within a block, and that Alice can report the status of the new // channel. -func openChannelAndAssert(t *testing.T, net *networkHarness, ctx context.Context, +func openChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context, alice, bob *lightningNode, amount btcutil.Amount) *lnrpc.ChannelPoint { chanOpenUpdate, err := net.OpenChannel(ctx, alice, bob, amount, 1) if err != nil { - t.Fatalf("unable to open channel: %v", err) + ct.Errorf("unable to open channel: %v", err) } // Mine a block, then wait for Alice's node to notify us that the @@ -46,21 +104,21 @@ func openChannelAndAssert(t *testing.T, net *networkHarness, ctx context.Context // within the newly mined block. blockHash, err := net.Miner.Node.Generate(1) if err != nil { - t.Fatalf("unable to generate block: %v", err) + ct.Errorf("unable to generate block: %v", err) } block, err := net.Miner.Node.GetBlock(blockHash[0]) if err != nil { - t.Fatalf("unable to get block: %v", err) + ct.Errorf("unable to get block: %v", err) } fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate) if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) + ct.Errorf("error while waiting for channel open: %v", err) } fundingTxID, err := wire.NewShaHash(fundingChanPoint.FundingTxid) if err != nil { - t.Fatalf("unable to create sha hash: %v", err) + ct.Errorf("unable to create sha hash: %v", err) } - assertTxInBlock(block, fundingTxID, t) + assertTxInBlock(ct, block, fundingTxID) // The channel should be listed in the peer information returned by // both peers. @@ -68,27 +126,25 @@ func openChannelAndAssert(t *testing.T, net *networkHarness, ctx context.Context Hash: *fundingTxID, Index: fundingChanPoint.OutputIndex, } - err = net.AssertChannelExists(ctx, alice, &chanPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) + if err := net.AssertChannelExists(ctx, alice, &chanPoint); err != nil { + ct.Errorf("unable to assert channel existence: %v", err) } return fundingChanPoint } -// closeChannelAndAssert attemps to close a channel identified by the passed +// closeChannelAndAssert attempts to close a channel identified by the passed // channel point owned by the passed lighting node. A fully blocking channel // closure is attempted, therefore the passed context should be a child derived // via timeout from a base parent. Additionally, once the channel has been // detected as closed, an assertion checks that the transaction is found within // a block. -func closeChannelAndAssert(t *testing.T, net *networkHarness, - ctx context.Context, node *lightningNode, - fundingChanPoint *lnrpc.ChannelPoint) { +func closeChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context, + node *lightningNode, fundingChanPoint *lnrpc.ChannelPoint) { closeUpdates, err := net.CloseChannel(ctx, node, fundingChanPoint, false) if err != nil { - t.Fatalf("unable to close channel: %v", err) + ct.Errorf("unable to close channel: %v", err) } // Finally, generate a single block, wait for the final close status @@ -96,19 +152,19 @@ func closeChannelAndAssert(t *testing.T, net *networkHarness, // block. blockHash, err := net.Miner.Node.Generate(1) if err != nil { - t.Fatalf("unable to generate block: %v", err) + ct.Errorf("unable to generate block: %v", err) } block, err := net.Miner.Node.GetBlock(blockHash[0]) if err != nil { - t.Fatalf("unable to get block: %v", err) + ct.Errorf("unable to get block: %v", err) } closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates) if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) + ct.Errorf("error while waiting for channel close: %v", err) } - assertTxInBlock(block, closingTxid, t) + assertTxInBlock(ct, block, closingTxid) } // testBasicChannelFunding performs a test exercising expected behavior from a @@ -116,7 +172,7 @@ func closeChannelAndAssert(t *testing.T, net *networkHarness, // Bob, then immediately closes the channel after asserting some expected post // conditions. Finally, the chain itself is checked to ensure the closing // transaction was mined. -func testBasicChannelFunding(net *networkHarness, t *testing.T) { +func testBasicChannelFunding(net *networkHarness, ct *CT) { timeout := time.Duration(time.Second * 5) ctxb := context.Background() @@ -128,40 +184,44 @@ func testBasicChannelFunding(net *networkHarness, t *testing.T) { // assertions will be executed to ensure the funding process completed // successfully. ctxt, _ := context.WithTimeout(ctxb, timeout) - chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, chanAmt) + chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob, chanAmt) // Finally, immediately close the channel. This function will also // block until the channel is closed and will additionally assert the // relevant channel closing post conditions. ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint) + closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint) } // testChannelBalance creates a new channel between Alice and Bob, then // checks channel balance to be equal amount specified while creation of channel. -func testChannelBalance(net *networkHarness, t *testing.T) { +func testChannelBalance(net *networkHarness, ct *CT) { timeout := time.Duration(time.Second * 5) - ctxb := context.Background() - - // Creates a helper closure to be used below which asserts the proper - // response to a channel balance RPC. - checkChannelBalance := func(node lnrpc.LightningClient, amount btcutil.Amount) { - response, err := node.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{}) - if err != nil { - t.Fatalf("unable to get channel balance: %v", err) - } - - balance := btcutil.Amount(response.Balance) - if balance != amount { - t.Fatalf("channel balance wrong: %v != %v", balance, amount) - } - } // Open a channel with 0.5 BTC between Alice and Bob, ensuring the // channel has been opened properly. amount := btcutil.Amount(btcutil.SatoshiPerBitcoin / 2) - ctxt, _ := context.WithTimeout(ctxb, timeout) - chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, amount) + ctx, _ := context.WithTimeout(context.Background(), timeout) + + // Creates a helper closure to be used below which asserts the proper + // response to a channel balance RPC. + checkChannelBalance := func(node lnrpc.LightningClient, + amount btcutil.Amount) { + + response, err := node.ChannelBalance(ctx, &lnrpc.ChannelBalanceRequest{}) + if err != nil { + ct.Errorf("unable to get channel balance: %v", err) + } + + balance := btcutil.Amount(response.Balance) + if balance != amount { + ct.Errorf("channel balance wrong: %v != %v", balance, + amount) + } + } + + chanPoint := openChannelAndAssert(ct, net, ctx, net.Alice, net.Bob, + amount) // As this is a single funder channel, Alice's balance should be // exactly 0.5 BTC since now state transitions have taken place yet. @@ -180,8 +240,8 @@ func testChannelBalance(net *networkHarness, t *testing.T) { // Finally close the channel between Alice and Bob, asserting that the // channel has been properly closed on-chain. - ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint) + ctx, _ = context.WithTimeout(context.Background(), timeout) + closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint) } // testChannelForceClosure performs a test to exercise the behavior of "force" @@ -193,7 +253,7 @@ func testChannelBalance(net *networkHarness, t *testing.T) { // once the output(s) become mature. // // TODO(roabeef): also add an unsettled HTLC before force closing. -func testChannelForceClosure(net *networkHarness, t *testing.T) { +func testChannelForceClosure(net *networkHarness, ct *CT) { timeout := time.Duration(time.Second * 5) ctxb := context.Background() @@ -204,15 +264,17 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) { chanOpenUpdate, err := net.OpenChannel(ctxb, net.Alice, net.Bob, chanAmt, numFundingConfs) if err != nil { - t.Fatalf("unable to open channel: %v", err) + ct.Errorf("unable to open channel: %v", err) } + if _, err := net.Miner.Node.Generate(numFundingConfs); err != nil { - t.Fatalf("unable to mine block: %v", err) + ct.Errorf("unable to mine block: %v", err) } + ctxt, _ := context.WithTimeout(ctxb, timeout) chanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) if err != nil { - t.Fatalf("error while waiting for channel to open: %v", err) + ct.Errorf("error while waiting for channel to open: %v", err) } // Now that the channel is open, immediately execute a force closure of @@ -221,18 +283,18 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) { // request. closeUpdate, err := net.CloseChannel(ctxb, net.Alice, chanPoint, true) if err != nil { - t.Fatalf("unable to execute force channel closure: %v", err) + ct.Errorf("unable to execute force channel closure: %v", err) } // Mine a block which should confirm the commitment transaction // broadcast as a result of the force closure. if _, err := net.Miner.Node.Generate(1); err != nil { - t.Fatalf("unable to generate block: %v", err) + ct.Errorf("unable to generate block: %v", err) } ctxt, _ = context.WithTimeout(ctxb, timeout) closingTxID, err := net.WaitForChannelClose(ctxt, closeUpdate) if err != nil { - t.Fatalf("error while waiting for channel close: %v", err) + ct.Errorf("error while waiting for channel close: %v", err) } // Currently within the codebase, the default CSV is 4 relative blocks. @@ -241,7 +303,7 @@ func testChannelForceClosure(net *networkHarness, t *testing.T) { // or make delay a param const defaultCSV = 4 if _, err := net.Miner.Node.Generate(defaultCSV); err != nil { - t.Fatalf("unable to mine blocks: %v", err) + ct.Errorf("unable to mine blocks: %v", err) } // At this point, the sweeping transaction should now be broadcast. So @@ -253,11 +315,11 @@ mempoolPoll: for { select { case <-time.After(time.Second * 5): - t.Fatalf("sweep tx not found in mempool") + ct.Errorf("sweep tx not found in mempool") default: mempool, err = net.Miner.Node.GetRawMempool() if err != nil { - t.Fatalf("unable to fetch node's mempool: %v", err) + ct.Errorf("unable to fetch node's mempool: %v", err) } if len(mempool) == 0 { continue @@ -271,7 +333,7 @@ mempoolPoll: // TODO(roasbeef): assertion may not necessarily hold with concurrent // test executions if len(mempool) != 1 { - t.Fatalf("node's mempool is wrong size, expected 1 got %v", + ct.Errorf("node's mempool is wrong size, expected 1 got %v", len(mempool)) } sweepingTXID = mempool[0] @@ -280,11 +342,11 @@ mempoolPoll: // the commitment transaction which was broadcast on-chain. sweepTx, err := net.Miner.Node.GetRawTransaction(sweepingTXID) if err != nil { - t.Fatalf("unable to fetch sweep tx: %v", err) + ct.Errorf("unable to fetch sweep tx: %v", err) } for _, txIn := range sweepTx.MsgTx().TxIn { if !closingTxID.IsEqual(&txIn.PreviousOutPoint.Hash) { - t.Fatalf("sweep transaction not spending from commit "+ + ct.Errorf("sweep transaction not spending from commit "+ "tx %v, instead spending %v", closingTxID, txIn.PreviousOutPoint) } @@ -295,16 +357,17 @@ mempoolPoll: // inputs should be properly met. blockHash, err := net.Miner.Node.Generate(1) if err != nil { - t.Fatalf("unable to generate block: %v", err) + ct.Errorf("unable to generate block: %v", err) } block, err := net.Miner.Node.GetBlock(blockHash[0]) if err != nil { - t.Fatalf("unable to get block: %v", err) + ct.Errorf("unable to get block: %v", err) } - assertTxInBlock(block, sweepTx.Sha(), t) + + assertTxInBlock(ct, block, sweepTx.Sha()) } -func testSingleHopInvoice(net *networkHarness, t *testing.T) { +func testSingleHopInvoice(net *networkHarness, ct *CT) { ctxb := context.Background() timeout := time.Duration(time.Second * 5) @@ -312,7 +375,7 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) { // the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, timeout) chanAmt := btcutil.Amount(100000) - chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, chanAmt) + chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob, chanAmt) // Now that the channel is open, create an invoice for Bob which // expects a payment of 1000 satoshis from Alice paid via a particular @@ -326,14 +389,14 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) { } invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice) if err != nil { - t.Fatalf("unable to add invoice: %v", err) + ct.Errorf("unable to add invoice: %v", err) } // With the invoice for Bob added, send a payment towards Alice paying // to the above generated invoice. sendStream, err := net.Alice.SendPayment(ctxb) if err != nil { - t.Fatalf("unable to create alice payment stream: %v", err) + ct.Errorf("unable to create alice payment stream: %v", err) } sendReq := &lnrpc.SendRequest{ PaymentHash: invoiceResp.RHash, @@ -341,10 +404,10 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) { Amt: paymentAmt, } if err := sendStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) + ct.Errorf("unable to send payment: %v", err) } if _, err := sendStream.Recv(); err != nil { - t.Fatalf("error when attempting recv: %v", err) + ct.Errorf("error when attempting recv: %v", err) } // Bob's invoice should now be found and marked as settled. @@ -355,37 +418,38 @@ func testSingleHopInvoice(net *networkHarness, t *testing.T) { } dbInvoice, err := net.Bob.LookupInvoice(ctxb, payHash) if err != nil { - t.Fatalf("unable to lookup invoice: %v", err) + ct.Errorf("unable to lookup invoice: %v", err) } if !dbInvoice.Settled { - t.Fatalf("bob's invoice should be marked as settled: %v", + ct.Errorf("bob's invoice should be marked as settled: %v", spew.Sdump(dbInvoice)) } // The balances of Alice and Bob should be updated accordingly. aliceBalance, err := net.Alice.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{}) if err != nil { - t.Fatalf("unable to query for alice's balance: %v", err) + ct.Errorf("unable to query for alice's balance: %v", err) } bobBalance, err := net.Bob.ChannelBalance(ctxb, &lnrpc.ChannelBalanceRequest{}) if err != nil { - t.Fatalf("unable to query for bob's balance: %v", err) + ct.Errorf("unable to query for bob's balance: %v", err) } if aliceBalance.Balance != int64(chanAmt-paymentAmt) { - t.Fatalf("Alice's balance is incorrect got %v, expected %v", + ct.Errorf("Alice's balance is incorrect got %v, expected %v", aliceBalance, int64(chanAmt-paymentAmt)) } if bobBalance.Balance != paymentAmt { - t.Fatalf("Bob's balance is incorrect got %v, expected %v", + ct.Errorf("Bob's balance is incorrect got %v, expected %v", bobBalance, paymentAmt) } ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint) + closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint) } -func testMultiHopPayments(net *networkHarness, t *testing.T) { +func testMultiHopPayments(net *networkHarness, ct *CT) { + const chanAmt = btcutil.Amount(100000) ctxb := context.Background() timeout := time.Duration(time.Second * 5) @@ -393,11 +457,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { // Open a channel with 100k satoshis between Alice and Bob with Alice // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, timeout) - chanPointAlice := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, - chanAmt) + chanPointAlice := openChannelAndAssert(ct, net, ctxt, net.Alice, + net.Bob, chanAmt) + aliceChanTXID, err := wire.NewShaHash(chanPointAlice.FundingTxid) if err != nil { - t.Fatalf("unable to create sha hash: %v", err) + ct.Errorf("unable to create sha hash: %v", err) } aliceFundPoint := wire.OutPoint{ Hash: *aliceChanTXID, @@ -411,21 +476,22 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { // The network topology should now look like: Carol -> Alice -> Bob carol, err := net.NewNode(nil) if err != nil { - t.Fatalf("unable to create new nodes: %v", err) + ct.Errorf("unable to create new nodes: %v", err) } if err := net.ConnectNodes(ctxb, carol, net.Alice); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + ct.Errorf("unable to connect carol to alice: %v", err) } err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, carol) if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) + ct.Errorf("unable to send coins to carol: %v", err) } ctxt, _ = context.WithTimeout(ctxb, timeout) - chanPointCarol := openChannelAndAssert(t, net, ctxt, carol, net.Alice, - chanAmt) + chanPointCarol := openChannelAndAssert(ct, net, ctxt, carol, + net.Alice, chanAmt) + carolChanTXID, err := wire.NewShaHash(chanPointCarol.FundingTxid) if err != nil { - t.Fatalf("unable to create sha hash: %v", err) + ct.Errorf("unable to create sha hash: %v", err) } carolFundPoint := wire.OutPoint{ Hash: *carolChanTXID, @@ -446,7 +512,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { } resp, err := net.Bob.AddInvoice(ctxb, invoice) if err != nil { - t.Fatalf("unable to add invoice: %v", err) + ct.Errorf("unable to add invoice: %v", err) } rHashes[i] = resp.RHash @@ -459,10 +525,10 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { req := &lnrpc.ShowRoutingTableRequest{} routingResp, err := carol.ShowRoutingTable(ctxb, req) if err != nil { - t.Fatalf("unable to query for carol's routing table: %v", err) + ct.Errorf("unable to query for carol's routing table: %v", err) } if len(routingResp.Channels) != 2 { - t.Fatalf("only two channels should be seen as active in the "+ + ct.Errorf("only two channels should be seen as active in the "+ "network, instead %v are", len(routingResp.Channels)) } for _, link := range routingResp.Channels { @@ -476,7 +542,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { link.Id2 == net.Alice.PubKeyStr: continue default: - t.Fatalf("unkown link within routing "+ + ct.Errorf("unkown link within routing "+ "table: %v", spew.Sdump(link)) } case link.Outpoint == carolFundPoint.String(): @@ -488,11 +554,11 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { link.Id2 == net.Alice.PubKeyStr: continue default: - t.Fatalf("unkown link within routing "+ + ct.Errorf("unkown link within routing "+ "table: %v", spew.Sdump(link)) } default: - t.Fatalf("unkown channel %v found in routing table, "+ + ct.Errorf("unkown channel %v found in routing table, "+ "only %v and %v should exist", link.Outpoint, aliceFundPoint, carolFundPoint) } @@ -501,7 +567,7 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { // Using Carol as the source, pay to the 5 invoices from Bob created above. carolPayStream, err := carol.SendPayment(ctxb) if err != nil { - t.Fatalf("unable to create payment stream for carol: %v", err) + ct.Errorf("unable to create payment stream for carol: %v", err) } // Concurrently pay off all 5 of Bob's invoices. Each of the goroutines @@ -518,10 +584,10 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { wg.Add(1) go func() { if err := carolPayStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) + ct.Errorf("unable to send payment: %v", err) } if _, err := carolPayStream.Recv(); err != nil { - t.Fatalf("unable to recv pay resp: %v", err) + ct.Errorf("unable to recv pay resp: %v", err) } wg.Done() }() @@ -535,16 +601,18 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { select { case <-time.After(time.Second * 10): - t.Fatalf("HLTC's not cleared after 10 seconds") + ct.Errorf("HLTC's not cleared after 10 seconds") case <-finClear: } assertAsymmetricBalance := func(node *lightningNode, - chanPoint *wire.OutPoint, localBalance, remoteBalance int64) { + chanPoint *wire.OutPoint, localBalance, + remoteBalance int64) { + listReq := &lnrpc.ListChannelsRequest{} resp, err := node.ListChannels(ctxb, listReq) if err != nil { - t.Fatalf("unable to for node's channels: %v", err) + ct.Errorf("unable to for node's channels: %v", err) } for _, channel := range resp.Channels { if channel.ChannelPoint != chanPoint.String() { @@ -553,12 +621,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { if channel.LocalBalance != localBalance || channel.RemoteBalance != remoteBalance { - t.Fatalf("incorrect balances: %v", + ct.Errorf("incorrect balances: %v", spew.Sdump(channel)) } return } - t.Fatalf("channel not found") + ct.Errorf("channel not found") } // At this point all the channels within our proto network should be @@ -575,12 +643,12 @@ func testMultiHopPayments(net *networkHarness, t *testing.T) { assertAsymmetricBalance(net.Bob, &aliceFundPoint, sinkBal, sourceBal) ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, net.Alice, chanPointAlice) + closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPointAlice) ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, carol, chanPointCarol) + closeChannelAndAssert(ct, net, ctxt, carol, chanPointCarol) } -func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { +func testInvoiceSubscriptions(net *networkHarness, ct *CT) { const chanAmt = btcutil.Amount(500000) ctxb := context.Background() timeout := time.Duration(time.Second * 5) @@ -588,7 +656,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { // Open a channel with 500k satoshis between Alice and Bob with Alice // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, timeout) - chanPoint := openChannelAndAssert(t, net, ctxt, net.Alice, net.Bob, + chanPoint := openChannelAndAssert(ct, net, ctxt, net.Alice, net.Bob, chanAmt) // Next create a new invoice for Bob requesting 1k satoshis. @@ -601,7 +669,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { } invoiceResp, err := net.Bob.AddInvoice(ctxb, invoice) if err != nil { - t.Fatalf("unable to add invoice: %v", err) + ct.Errorf("unable to add invoice: %v", err) } // Create a new invoice subscription client for Bob, the notification @@ -609,23 +677,23 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { req := &lnrpc.InvoiceSubscription{} bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctxb, req) if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) + ct.Errorf("unable to subscribe to bob's invoice updates: %v", err) } updateSent := make(chan struct{}) go func() { invoiceUpdate, err := bobInvoiceSubscription.Recv() if err != nil { - t.Fatalf("unable to recv invoice update: %v", err) + ct.Errorf("unable to recv invoice update: %v", err) } // The invoice update should exactly match the invoice created // above, but should now be settled. if !invoiceUpdate.Settled { - t.Fatalf("invoice not settled but shoudl be") + ct.Errorf("invoice not settled but shoudl be") } if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) { - t.Fatalf("payment preimages don't match: expected %v, got %v", + ct.Errorf("payment preimages don't match: expected %v, got %v", invoice.RPreimage, invoiceUpdate.RPreimage) } @@ -636,7 +704,7 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { // which should finalize and settle the invoice. sendStream, err := net.Alice.SendPayment(ctxb) if err != nil { - t.Fatalf("unable to create alice payment stream: %v", err) + ct.Errorf("unable to create alice payment stream: %v", err) } sendReq := &lnrpc.SendRequest{ PaymentHash: invoiceResp.RHash, @@ -644,24 +712,24 @@ func testInvoiceSubscriptions(net *networkHarness, t *testing.T) { Amt: paymentAmt, } if err := sendStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) + ct.Errorf("unable to send payment: %v", err) } if _, err := sendStream.Recv(); err != nil { - t.Fatalf("error when attempting recv: %v", err) + ct.Errorf("error when attempting recv: %v", err) } select { case <-time.After(time.Second * 5): - t.Fatalf("update not sent after 5 seconds") + ct.Errorf("update not sent after 5 seconds") case <-updateSent: // Fall through on success } ctxt, _ = context.WithTimeout(ctxb, timeout) - closeChannelAndAssert(t, net, ctxt, net.Alice, chanPoint) + closeChannelAndAssert(ct, net, ctxt, net.Alice, chanPoint) } // testBasicChannelCreation test multiple channel opening and closing. -func testBasicChannelCreation(net *networkHarness, t *testing.T) { +func testBasicChannelCreation(net *networkHarness, ct *CT) { timeout := time.Duration(time.Second * 5) ctx, _ := context.WithTimeout(context.Background(), timeout) @@ -673,20 +741,20 @@ func testBasicChannelCreation(net *networkHarness, t *testing.T) { // channel has been properly open on-chain. chanPoints := make([]*lnrpc.ChannelPoint, num) for i := 0; i < num; i++ { - chanPoints[i] = openChannelAndAssert(t, net, ctx, net.Alice, + chanPoints[i] = openChannelAndAssert(ct, net, ctx, net.Alice, net.Bob, amount) } // Close the channel between Alice and Bob, asserting that the // channel has been properly closed on-chain. for _, chanPoint := range chanPoints { - closeChannelAndAssert(t, net, ctx, net.Alice, chanPoint) + closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint) } } -type lndTestCase func(net *networkHarness, t *testing.T) +type testCase func(net *networkHarness, t *testing.T) -var lndTestCases = map[string]lndTestCase{ +var testCases = map[string]testCase{ "basic funding flow": testBasicChannelFunding, "channel force closure": testChannelForceClosure, "channel balance": testChannelBalance, @@ -699,72 +767,69 @@ var lndTestCases = map[string]lndTestCase{ // TestLightningNetworkDaemon performs a series of integration tests amongst a // programmatically driven network of lnd nodes. func TestLightningNetworkDaemon(t *testing.T) { - var ( - btcdHarness *rpctest.Harness - lightningNetwork *networkHarness - currentTest string - err error - ) - - defer func() { - // If one of the integration tests caused a panic within the main - // goroutine, then tear down all the harnesses in order to avoid - // any leaked processes. - if r := recover(); r != nil { - fmt.Println("recovering from test panic: ", r) - if err := btcdHarness.TearDown(); err != nil { - fmt.Println("unable to tear btcd harnesses: ", err) - } - if err := lightningNetwork.TearDownAll(); err != nil { - fmt.Println("unable to tear lnd harnesses: ", err) - } - t.Fatalf("test %v panicked: %s", currentTest, debug.Stack()) - } - }() + ct := NewCT(t) // First create the network harness to gain access to its // 'OnTxAccepted' call back. - lightningNetwork, err = newNetworkHarness() + lndHarness, err := newNetworkHarness() if err != nil { - t.Fatalf("unable to create lightning network harness: %v", err) + ct.Fatalf("unable to create lightning network harness: %v", err) } - defer lightningNetwork.TearDownAll() + defer lndHarness.TearDownAll() handlers := &btcrpcclient.NotificationHandlers{ - OnTxAccepted: lightningNetwork.OnTxAccepted, + OnTxAccepted: lndHarness.OnTxAccepted, } // First create an instance of the btcd's rpctest.Harness. This will be // used to fund the wallets of the nodes within the test network and to // drive blockchain related events within the network. - btcdHarness, err = rpctest.New(harnessNetParams, handlers, nil) + btcdHarness, err := rpctest.New(harnessNetParams, handlers, nil) if err != nil { - t.Fatalf("unable to create mining node: %v", err) + ct.Fatalf("unable to create mining node: %v", err) } defer btcdHarness.TearDown() - if err = btcdHarness.SetUp(true, 50); err != nil { - t.Fatalf("unable to set up mining node: %v", err) + if err := btcdHarness.SetUp(true, 50); err != nil { + ct.Fatalf("unable to set up mining node: %v", err) } if err := btcdHarness.Node.NotifyNewTransactions(false); err != nil { - t.Fatalf("unable to request transaction notifications: %v", err) + ct.Fatalf("unable to request transaction notifications: %v", err) } // With the btcd harness created, we can now complete the // initialization of the network. args - list of lnd arguments, // example: "--debuglevel=debug" // TODO(roasbeef): create master balanced channel with all the monies? - if err := lightningNetwork.InitializeSeedNodes(btcdHarness, nil); err != nil { - t.Fatalf("unable to initialize seed nodes: %v", err) + if err := lndHarness.InitializeSeedNodes(btcdHarness, nil); err != nil { + ct.Fatalf("unable to initialize seed nodes: %v", err) } - if err = lightningNetwork.SetUp(); err != nil { - t.Fatalf("unable to set up test lightning network: %v", err) + if err = lndHarness.SetUp(); err != nil { + ct.Fatalf("unable to set up test lightning network: %v", err) } - t.Logf("Running %v integration tests", len(lndTestCases)) - for testName, lnTest := range lndTestCases { - t.Logf("Executing test %v", testName) + ct.Logf("Running %v integration tests", len(testCases)) - currentTest = testName - lnTest(lightningNetwork, t) + for name, test := range testCases { + errChan := ct.RunTest(lndHarness, test) + + select { + // Receive both types of err - panic and fatal from + // one channel and raise the fatal in main goroutine. + case err := <-errChan: + if err != nil { + ct.Fatalf("Fail: (%v): exited with error: \n%v", + name, err) + } + ct.Logf("Successed: (%v)", name) + + // In this case lightning node process finished with error + // status. It might be because of wrong flag, or it might + // be because of nil pointer access. Who knows!? Who knows... + // TODO(andrew.shvv) When two nodes are closing simultanisly + // it leads to panic - 'sending to the closed channel'. Fix it? + case err := <-lndHarness.lndErrorChan: + ct.Fatalf("Fail: (%v): lnd finished with error "+ + "(stderr): \n%v", name, err) + } } } diff --git a/networktest.go b/networktest.go index c87879687..0a17054dc 100644 --- a/networktest.go +++ b/networktest.go @@ -19,6 +19,8 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/grpclog" + "bytes" + "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/lnrpc" "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcd/rpctest" @@ -159,14 +161,27 @@ func (l *lightningNode) genArgs() []string { // start launches a new process running lnd. Additionally, the PID of the // launched process is saved in order to possibly kill the process forcibly // later. -func (l *lightningNode) start() error { +func (l *lightningNode) start(lndError chan error) error { args := l.genArgs() l.cmd = exec.Command("lnd", args...) + + // Redirect stderr output to buffer + var errb bytes.Buffer + l.cmd.Stderr = &errb + if err := l.cmd.Start(); err != nil { return err } + go func() { + // If lightning node process exited with error status + // then we should transmit stderr output in main process. + if err := l.cmd.Wait(); err != nil { + lndError <- errors.New(errb.String()) + } + }() + pid, err := os.Create(filepath.Join(l.cfg.DataDir, fmt.Sprintf("%s.pid", l.nodeId))) if err != nil { @@ -234,7 +249,14 @@ func (l *lightningNode) cleanup() error { // stop attempts to stop the active lnd process. func (l *lightningNode) stop() error { - if l.cmd == nil || l.cmd.Process == nil { + + // We should skip node stop in case: + // - start of the node wasn't initiated + // - process wasn't spawned + // - process already finished + if l.cmd == nil || + l.cmd.Process == nil || + (l.cmd.ProcessState != nil && l.cmd.ProcessState.Exited()) { return nil } @@ -276,6 +298,10 @@ type networkHarness struct { seenTxns chan wire.ShaHash watchRequests chan *watchRequest + // Channel for transmitting stderr output from failed lightning node + // to main process. + lndErrorChan chan error + sync.Mutex } @@ -288,6 +314,7 @@ func newNetworkHarness() (*networkHarness, error) { activeNodes: make(map[int]*lightningNode), seenTxns: make(chan wire.ShaHash), watchRequests: make(chan *watchRequest), + lndErrorChan: make(chan error), }, nil } @@ -345,7 +372,7 @@ func (n *networkHarness) SetUp() error { go func() { var err error defer wg.Done() - if err = n.Alice.start(); err != nil { + if err = n.Alice.start(n.lndErrorChan); err != nil { errChan <- err return } @@ -353,7 +380,7 @@ func (n *networkHarness) SetUp() error { go func() { var err error defer wg.Done() - if err = n.Bob.start(); err != nil { + if err = n.Bob.start(n.lndErrorChan); err != nil { errChan <- err return } @@ -462,7 +489,7 @@ func (n *networkHarness) NewNode(extraArgs []string) (*lightningNode, error) { return nil, err } - if err := node.start(); err != nil { + if err := node.start(n.lndErrorChan); err != nil { return nil, err } From 19d8abade8034040206724ff9b6281c7d207326d Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Sat, 15 Oct 2016 16:45:51 +0300 Subject: [PATCH 4/5] config: add --pendingchannel lnd flag --- config.go | 65 ++++++++++++++++++++++++---------------------- lnd.go | 30 ++++++++++----------- lnwallet/config.go | 3 ++- 3 files changed, 51 insertions(+), 47 deletions(-) diff --git a/config.go b/config.go index cdf36c86a..1b8452703 100644 --- a/config.go +++ b/config.go @@ -13,18 +13,19 @@ import ( ) const ( - defaultConfigFilename = "lnd.conf" - defaultDataDirname = "data" - defaultLogLevel = "info" - defaultLogDirname = "logs" - defaultLogFilename = "lnd.log" - defaultRPCPort = 10009 - defaultSPVMode = false - defaultPeerPort = 10011 - defaultRPCHost = "localhost" - defaultRPCUser = "user" - defaultRPCPass = "passwd" - defaultSPVHostAdr = "localhost:18333" + defaultConfigFilename = "lnd.conf" + defaultDataDirname = "data" + defaultLogLevel = "info" + defaultLogDirname = "logs" + defaultLogFilename = "lnd.log" + defaultRPCPort = 10009 + defaultSPVMode = false + defaultPeerPort = 10011 + defaultRPCHost = "localhost" + defaultRPCUser = "user" + defaultRPCPass = "passwd" + defaultSPVHostAdr = "localhost:18333" + defaultMaxPendingChannels = 1 ) var ( @@ -62,13 +63,14 @@ type config struct { RPCUser string `short:"u" long:"rpcuser" description:"Username for RPC connections"` RPCPass string `short:"P" long:"rpcpass" default-mask:"-" description:"Password for RPC connections"` - RPCCert string `long:"rpccert" description:"File containing btcd's certificate file"` - RawRPCCert string `long:"rawrpccert" description:"The raw bytes of btcd's PEM-encoded certificate chain which will be used to authenticate the RPC connection."` - SPVHostAdr string `long:"spvhostadr" description:"Address of full bitcoin node. It is used in SPV mode."` - TestNet3 bool `long:"testnet" description:"Use the test network"` - SimNet bool `long:"simnet" description:"Use the simulation test network"` - SegNet bool `long:"segnet" description:"Use the segragated witness test network"` - DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLC's sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."` + RPCCert string `long:"rpccert" description:"File containing btcd's certificate file"` + RawRPCCert string `long:"rawrpccert" description:"The raw bytes of btcd's PEM-encoded certificate chain which will be used to authenticate the RPC connection."` + SPVHostAdr string `long:"spvhostadr" description:"Address of full bitcoin node. It is used in SPV mode."` + TestNet3 bool `long:"testnet" description:"Use the test network"` + SimNet bool `long:"simnet" description:"Use the simulation test network"` + SegNet bool `long:"segnet" description:"Use the segragated witness test network"` + DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLC's sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."` + MaxPendingChannels int `long:"maxpendingchannels" description:"The maximum number of incoming pending channels permitted per peer."` } // loadConfig initializes and parses the config using a config file and command @@ -81,18 +83,19 @@ type config struct { // 4) Parse CLI options and overwrite/add any specified options func loadConfig() (*config, error) { defaultCfg := config{ - ConfigFile: defaultConfigFile, - DataDir: defaultDataDir, - DebugLevel: defaultLogLevel, - LogDir: defaultLogDir, - PeerPort: defaultPeerPort, - RPCPort: defaultRPCPort, - SPVMode: defaultSPVMode, - RPCHost: defaultRPCHost, - RPCUser: defaultRPCUser, - RPCPass: defaultRPCPass, - RPCCert: defaultRPCCertFile, - SPVHostAdr: defaultSPVHostAdr, + ConfigFile: defaultConfigFile, + DataDir: defaultDataDir, + DebugLevel: defaultLogLevel, + LogDir: defaultLogDir, + PeerPort: defaultPeerPort, + RPCPort: defaultRPCPort, + SPVMode: defaultSPVMode, + RPCHost: defaultRPCHost, + RPCUser: defaultRPCUser, + RPCPass: defaultRPCPass, + RPCCert: defaultRPCCertFile, + SPVHostAdr: defaultSPVHostAdr, + MaxPendingChannels: defaultMaxPendingChannels, } // Pre-parse the command line options to pick up an alternative config diff --git a/lnd.go b/lnd.go index 2d032b7ad..9b8f03ffb 100644 --- a/lnd.go +++ b/lnd.go @@ -47,8 +47,8 @@ func lndMain() error { // Show version at startup. ltndLog.Infof("Version %s", version()) - if loadedConfig.SPVMode == true { - shell(loadedConfig.SPVHostAdr, activeNetParams.Params) + if cfg.SPVMode == true { + shell(cfg.SPVHostAdr, activeNetParams.Params) return err } @@ -65,7 +65,7 @@ func lndMain() error { // Open the channeldb, which is dedicated to storing channel, and // network related meta-data. - chanDB, err := channeldb.Open(loadedConfig.DataDir, activeNetParams.Params) + chanDB, err := channeldb.Open(cfg.DataDir, activeNetParams.Params) if err != nil { fmt.Println("unable to open channeldb: ", err) return err @@ -76,13 +76,13 @@ func lndMain() error { // specified in the config, then we'll se that directly. Otherwise, we // attempt to read the cert from the path specified in the config. var rpcCert []byte - if loadedConfig.RawRPCCert != "" { - rpcCert, err = hex.DecodeString(loadedConfig.RawRPCCert) + if cfg.RawRPCCert != "" { + rpcCert, err = hex.DecodeString(cfg.RawRPCCert) if err != nil { return err } } else { - certFile, err := os.Open(loadedConfig.RPCCert) + certFile, err := os.Open(cfg.RPCCert) if err != nil { return err } @@ -95,15 +95,15 @@ func lndMain() error { } } - rpcIP, err := net.LookupHost(loadedConfig.RPCHost) + rpcIP, err := net.LookupHost(cfg.RPCHost) if err != nil { fmt.Printf("unable to resolve rpc host: %v", err) return err } - btcdHost := fmt.Sprintf("%v:%v", loadedConfig.RPCHost, activeNetParams.rpcPort) - btcdUser := loadedConfig.RPCUser - btcdPass := loadedConfig.RPCPass + btcdHost := fmt.Sprintf("%v:%v", cfg.RPCHost, activeNetParams.rpcPort) + btcdUser := cfg.RPCUser + btcdPass := cfg.RPCPass // TODO(roasbeef): parse config here and select chosen notifier instead rpcConfig := &btcrpcclient.ConnConfig{ @@ -121,13 +121,13 @@ func lndMain() error { return err } - // TODO(roasbeef): paarse config here select chosen WalletController + // TODO(roasbeef): parse config here select chosen WalletController walletConfig := &btcwallet.Config{ PrivatePass: []byte("hello"), - DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"), + DataDir: filepath.Join(cfg.DataDir, "lnwallet"), RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort), - RpcUser: loadedConfig.RPCUser, - RpcPass: loadedConfig.RPCPass, + RpcUser: cfg.RPCUser, + RpcPass: cfg.RPCPass, CACert: rpcCert, NetParams: activeNetParams.Params, } @@ -156,7 +156,7 @@ func lndMain() error { // Set up the core server which will listen for incoming peer // connections. defaultListenAddrs := []string{ - net.JoinHostPort("", strconv.Itoa(loadedConfig.PeerPort)), + net.JoinHostPort("", strconv.Itoa(cfg.PeerPort)), } server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB) if err != nil { diff --git a/lnwallet/config.go b/lnwallet/config.go index f579fde55..837e543a4 100644 --- a/lnwallet/config.go +++ b/lnwallet/config.go @@ -1,6 +1,7 @@ package lnwallet -// Config.. +// Config is a struct which houses configuration parameters which modify the +// behaviour of LightningWallet. type Config struct { // default csv time // default cltv time From 7196c4bb1c8327f9f257af764aa4991193d039f8 Mon Sep 17 00:00:00 2001 From: Andrey Samokhvalov Date: Sat, 15 Oct 2016 16:24:56 +0300 Subject: [PATCH 5/5] fundingmanager: add max pending channel check --- fundingmanager.go | 73 ++++++++++++++++++++ lnd_test.go | 145 +++++++++++++++++++++++++++++++++++----- lnwire/error_generic.go | 6 ++ peer.go | 10 +++ rpcserver.go | 4 ++ 5 files changed, 220 insertions(+), 18 deletions(-) diff --git a/fundingmanager.go b/fundingmanager.go index c8cfbe087..6251cfdea 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -13,8 +13,10 @@ import ( "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" + "fmt" "github.com/BitfuryLightning/tools/rt" "github.com/BitfuryLightning/tools/rt/graph" + "google.golang.org/grpc" ) const ( @@ -88,6 +90,14 @@ type fundingOpenMsg struct { peer *peer } +// fundingErrorMsg couples an lnwire.ErrorGeneric message +// with the peer who sent the message. This allows the funding +// manager properly process the error. +type fundingErrorMsg struct { + err *lnwire.ErrorGeneric + peer *peer +} + // pendingChannels is a map instantiated per-peer which tracks all active // pending single funded channels indexed by their pending channel identifier. type pendingChannels map[uint64]*reservationWithCtx @@ -232,6 +242,8 @@ out: f.handleFundingSignComplete(fmsg) case *fundingOpenMsg: f.handleFundingOpen(fmsg) + case *fundingErrorMsg: + f.handleErrorGenericMsg(fmsg) } case req := <-f.fundingRequests: f.handleInitFundingMsg(req) @@ -297,6 +309,23 @@ func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, // TODO(roasbeef): add error chan to all, let channelManager handle // error+propagate func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) { + + // Check number of pending channels to be smaller than maximum allowed + // number and send ErrorGeneric to remote peer if condition is violated. + if len(f.activeReservations[fmsg.peer.id]) >= cfg.MaxPendingChannels { + errMsg := &lnwire.ErrorGeneric{ + ChannelPoint: &wire.OutPoint{ + Hash: wire.ShaHash{}, + Index: 0, + }, + Problem: "Number of pending channels exceed maximum", + ErrorID: lnwire.ErrorMaxPendingChannels, + PendingChannelID: fmsg.msg.ChannelID, + } + fmsg.peer.queueMsg(errMsg, nil) + return + } + msg := fmsg.msg amt := msg.FundingAmount delay := msg.CsvDelay @@ -684,6 +713,7 @@ func (f *fundingManager) initFundingWorkflow(targetPeer *peer, req *openChanReq) // wallet, then sends a funding request to the remote peer kicking off the // funding workflow. func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { + nodeID := msg.peer.lightningID localAmt := msg.localFundingAmt @@ -755,3 +785,46 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { ) msg.peer.queueMsg(fundingReq, nil) } + +// processErrorGeneric sends a message to the fundingManager allowing it +// to process the occurred generic error. +func (f *fundingManager) processErrorGeneric(err *lnwire.ErrorGeneric, + peer *peer) { + f.fundingMsgs <- &fundingErrorMsg{err, peer} +} + +// handleErrorGenericMsg process the error which was received from remote peer, +// depends on the type of error we should do different clean up steps and +// inform user about it. +func (f *fundingManager) handleErrorGenericMsg(fmsg *fundingErrorMsg) { + if fmsg.err.ErrorID == lnwire.ErrorMaxPendingChannels { + peerID := fmsg.peer.id + chanID := fmsg.err.PendingChannelID + + f.resMtx.RLock() + resCtx, ok := f.activeReservations[peerID][chanID] + f.resMtx.RUnlock() + + if !ok { + resCtx.err <- fmt.Errorf("ErrorGeneric error " + + "was returned from remote peer for channel "+ + "(id: %v), but it can't be found and thereby "+ + "can't be canceled.", chanID) + } + + if err := resCtx.reservation.Cancel(); err != nil { + resCtx.err <- fmt.Errorf("Remote peer responded "+ + "with: Number of pending channels exceed "+ + "maximum, but we can't cancel the reservation "+ + "- %v", err) + } else { + resCtx.err <- grpc.Errorf(OpenChannelFundingError, + "Remote peer responded with: Number of "+ + "pending channels exceed maximum") + } + + f.resMtx.Lock() + delete(f.activeReservations[peerID], chanID) + f.resMtx.Unlock() + } +} diff --git a/lnd_test.go b/lnd_test.go index 43c0a4c98..5f72d1f6b 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -13,6 +13,7 @@ import ( "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcrpcclient" "github.com/roasbeef/btcutil" + "google.golang.org/grpc" "testing" ) @@ -86,6 +87,29 @@ func assertTxInBlock(ct *CT, block *btcutil.Block, txid *wire.ShaHash) { ct.Errorf("funding tx was not included in block") } +// mineBlocks mine 'num' of blocks and check that blocks are present in +// node blockchain. +func mineBlocks(ct *CT, net *networkHarness, num uint32) []*btcutil.Block { + blocks := make([]*btcutil.Block, num) + + blockHashes, err := net.Miner.Node.Generate(num) + if err != nil { + ct.Errorf("unable to generate blocks: %v", err) + } + + for i, blockHash := range blockHashes { + block, err := net.Miner.Node.GetBlock(blockHash) + if err != nil { + ct.Errorf("unable to get block: %v", err) + } + + blocks[i] = block + } + + return blocks +} + + // openChannelAndAssert attempts to open a channel with the specified // parameters extended from Alice to Bob. Additionally, two items are asserted // after the channel is considered open: the funding transaction should be @@ -102,14 +126,8 @@ func openChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context, // Mine a block, then wait for Alice's node to notify us that the // channel has been opened. The funding transaction should be found // within the newly mined block. - blockHash, err := net.Miner.Node.Generate(1) - if err != nil { - ct.Errorf("unable to generate block: %v", err) - } - block, err := net.Miner.Node.GetBlock(blockHash[0]) - if err != nil { - ct.Errorf("unable to get block: %v", err) - } + block := mineBlocks(ct, net, 1)[0] + fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate) if err != nil { ct.Errorf("error while waiting for channel open: %v", err) @@ -150,14 +168,7 @@ func closeChannelAndAssert(ct *CT, net *networkHarness, ctx context.Context, // Finally, generate a single block, wait for the final close status // update, then ensure that the closing transaction was included in the // block. - blockHash, err := net.Miner.Node.Generate(1) - if err != nil { - ct.Errorf("unable to generate block: %v", err) - } - block, err := net.Miner.Node.GetBlock(blockHash[0]) - if err != nil { - ct.Errorf("unable to get block: %v", err) - } + block := mineBlocks(ct, net, 1)[0] closingTxid, err := net.WaitForChannelClose(ctx, closeUpdates) if err != nil { @@ -752,16 +763,114 @@ func testBasicChannelCreation(net *networkHarness, ct *CT) { } } -type testCase func(net *networkHarness, t *testing.T) +// testMaxPendingChannels checks that error is returned from remote peer if +// max pending channel number was exceeded and that '--maxpendingchannels' flag +// exists and works properly. +func testMaxPendingChannels(net *networkHarness, ct *CT) { + maxPendingChannels := defaultMaxPendingChannels + 1 + amount := btcutil.Amount(btcutil.SatoshiPerBitcoin) + + timeout := time.Duration(time.Second * 10) + ctx, _ := context.WithTimeout(context.Background(), timeout) + + // Create a new node (Carol) with greater number of max pending + // channels. + args := []string{ + fmt.Sprintf("--maxpendingchannels=%v", maxPendingChannels), + } + + carol, err := net.NewNode(args) + + if err != nil { + ct.Errorf("unable to create new nodes: %v", err) + } + if err := net.ConnectNodes(ctx, net.Alice, carol); err != nil { + ct.Errorf("unable to connect carol to alice: %v", err) + } + + carolBalance := btcutil.Amount(maxPendingChannels) * amount + if err := net.SendCoins(ctx, carolBalance, carol); err != nil { + ct.Errorf("unable to send coins to carol: %v", err) + } + + // Send open channel requests without generating new blocks thereby + // increasing pool of pending channels. Then check that we can't + // open the channel if the number of pending channels exceed + // max value. + openStreams := make([]lnrpc.Lightning_OpenChannelClient, maxPendingChannels) + for i := 0; i < maxPendingChannels; i++ { + stream, err := net.OpenChannel(ctx, net.Alice, carol, amount, 1) + if err != nil { + ct.Errorf("unable to open channel: %v", err) + } + openStreams[i] = stream + } + + // Carol exhausted available amount of pending channels, next open + // channel request should cause ErrorGeneric to be sent back to Alice. + _, err = net.OpenChannel(ctx, net.Alice, carol, amount, 1) + if err == nil { + ct.Errorf("error wasn't received") + } else if grpc.Code(err) != OpenChannelFundingError { + ct.Errorf("not expected error was received : %v", err) + } + + // For now our channels are in pending state, in order to not + // interfere with other tests we should clean up - complete opening + // of the channel and then close it. + + // Mine a block, then wait for node's to notify us that the channel + // has been opened. The funding transactions should be found within the + // newly mined block. + block := mineBlocks(ct, net, 1)[0] + + chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels) + + for i, stream := range openStreams { + fundingChanPoint, err := net.WaitForChannelOpen(ctx, stream) + if err != nil { + ct.Errorf("error while waiting for channel open: %v", err) + } + + fundingTxID, err := wire.NewShaHash(fundingChanPoint.FundingTxid) + if err != nil { + ct.Errorf("unable to create sha hash: %v", err) + } + + assertTxInBlock(ct, block, fundingTxID) + + // The channel should be listed in the peer information + // returned by both peers. + chanPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: fundingChanPoint.OutputIndex, + } + if err := net.AssertChannelExists(ctx, net.Alice, &chanPoint); err != nil { + ct.Errorf("unable to assert channel existence: %v", err) + } + + chanPoints[i] = fundingChanPoint + } + + // Finally close the channel between Alice and Carol, asserting that the + // channel has been properly closed on-chain. + for _, chanPoint := range chanPoints { + closeChannelAndAssert(ct, net, ctx, net.Alice, chanPoint) + } + +} + +type testCase func(net *networkHarness, ct *CT) var testCases = map[string]testCase{ "basic funding flow": testBasicChannelFunding, "channel force closure": testChannelForceClosure, "channel balance": testChannelBalance, "single hop invoice": testSingleHopInvoice, + "max pending channel": testMaxPendingChannels, "multi-hop payments": testMultiHopPayments, + "multiple channel creation": testBasicChannelCreation, "invoice update subscription": testInvoiceSubscriptions, - "multiple channel creation": testBasicChannelCreation, } // TestLightningNetworkDaemon performs a series of integration tests amongst a diff --git a/lnwire/error_generic.go b/lnwire/error_generic.go index c5ff2f0de..dea6f1d30 100644 --- a/lnwire/error_generic.go +++ b/lnwire/error_generic.go @@ -7,6 +7,12 @@ import ( "github.com/roasbeef/btcd/wire" ) +const ( + // Is returned by remote peer when number of pending channels exceed max + // value. + ErrorMaxPendingChannels = 1 +) + // ErrorGeneric represents a generic error bound to an exact channel. The // message format is purposefully general in order to allow expression of a wide // array of possible errors. Each ErrorGeneric message is directed at a particular diff --git a/peer.go b/peer.go index a996c4be5..f970c8d9e 100644 --- a/peer.go +++ b/peer.go @@ -373,6 +373,16 @@ out: p.remoteCloseChanReqs <- msg // TODO(roasbeef): interface for htlc update msgs // * .(CommitmentUpdater) + + case *lnwire.ErrorGeneric: + switch msg.ErrorID { + case lnwire.ErrorMaxPendingChannels: + p.server.fundingMgr.processErrorGeneric(msg, p) + default: + peerLog.Warn("ErrorGeneric(%v) handling isn't" + + " implemented.", msg.ErrorID) + } + case *lnwire.HTLCAddRequest: isChanUpdate = true targetChan = msg.ChannelPoint diff --git a/rpcserver.go b/rpcserver.go index c93b4be89..976deccd6 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -27,6 +27,10 @@ import ( "golang.org/x/net/context" ) +const ( + OpenChannelFundingError = 100 +) + var ( defaultAccount uint32 = waddrmgr.DefaultAccountNum )