// logEntryToHtlcPkt converts a particular Lightning Commitment Protocol (LCP) // log entry the corresponding htlcPacket with src/dest set along with the // proper wire message. This helepr method is provided in order to aide an // htlcManager in forwarding packets to the htlcSwitch. func logEntryToHtlcPkt(chanPoint wire.OutPoint, pd *lnwallet.PaymentDescriptor, onionPkt *sphinx.ProcessedPacket) (*htlcPacket, error) { pkt := &htlcPacket{} // TODO(roasbeef): alter after switch to log entry interface var msg lnwire.Message switch pd.EntryType { case lnwallet.Add: // TODO(roasbeef): timeout, onion blob, etc var b bytes.Buffer if err := onionPkt.Packet.Encode(&b); err != nil { return nil, err } msg = &lnwire.HTLCAddRequest{ Amount: lnwire.CreditsAmount(pd.Amount), RedemptionHashes: [][32]byte{pd.RHash}, OnionBlob: b.Bytes(), } case lnwallet.Settle: msg = &lnwire.HTLCSettleRequest{ RedemptionProofs: [][32]byte{pd.RPreimage}, } } pkt.amt = pd.Amount pkt.msg = msg pkt.srcLink = chanPoint pkt.onion = onionPkt return pkt, nil }
// SendPayment dispatches a bi-directional streaming RPC for sending payments // through the Lightning Network. A single RPC invocation creates a persistent // bi-directional stream allowing clients to rapidly send payments through the // Lightning Network with a single persistent connection. func (r *rpcServer) SendPayment(paymentStream lnrpc.Lightning_SendPaymentServer) error { const queryTimeout = time.Duration(time.Second * 10) errChan := make(chan error, 1) payChan := make(chan *lnrpc.SendRequest) // Launch a new goroutine to handle reading new payment requests from // the client. This way we can handle errors independently of blocking // and waiting for the next payment request to come through. go func() { for { select { case <-r.quit: errChan <- nil return default: // Receive the next pending payment within the // stream sent by the client. If we read the // EOF sentinel, then the client has closed the // stream, and we can exit normally. nextPayment, err := paymentStream.Recv() if err == io.EOF { errChan <- nil return } else if err != nil { errChan <- err return } payChan <- nextPayment } } }() for { select { case err := <-errChan: return err case nextPayment := <-payChan: // Query the routing table for a potential path to the // destination node. If a path is ultimately // unavailable, then an error will be returned. destNode := hex.EncodeToString(nextPayment.Dest) targetVertex := graph.NewID(destNode) path, err := r.server.routingMgr.FindPath(targetVertex, queryTimeout) if err != nil { return err } rpcsLog.Tracef("[sendpayment] selected route: %v", path) // If we're in debug HTLC mode, then all outgoing // HTLC's will pay to the same debug rHash. Otherwise, // we pay to the rHash specified within the RPC // request. var rHash [32]byte if cfg.DebugHTLC { rHash = debugHash } else { copy(rHash[:], nextPayment.PaymentHash) } // Generate the raw encoded sphinx packet to be // included along with the HTLC add message. We snip // off the first hop from the path as within the // routing table's star graph, we're always the first // hop. sphinxPacket, err := generateSphinxPacket(path[1:], rHash[:]) if err != nil { return err } // Craft an HTLC packet to send to the routing // sub-system. The meta-data within this packet will be // used to route the payment through the network. htlcAdd := &lnwire.HTLCAddRequest{ Amount: lnwire.CreditsAmount(nextPayment.Amt), RedemptionHashes: [][32]byte{rHash}, OnionBlob: sphinxPacket, } firstHopPub, err := hex.DecodeString(path[1].String()) if err != nil { return err } destAddr := wire.ShaHash(fastsha256.Sum256(firstHopPub)) htlcPkt := &htlcPacket{ dest: destAddr, msg: htlcAdd, } // TODO(roasbeef): semaphore to limit num outstanding // goroutines. go func() { // Finally, send this next packet to the // routing layer in order to complete the next // payment. // TODO(roasbeef): this should go through the // L3 router once multi-hop is in place. if err := r.server.htlcSwitch.SendHTLC(htlcPkt); err != nil { errChan <- err return } // TODO(roasbeef): proper responses resp := &lnrpc.SendResponse{} if err := paymentStream.Send(resp); err != nil { errChan <- err return } }() } } return nil }
func TestStateUpdatePersistence(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() if err := aliceChannel.channelState.FullSync(); err != nil { t.Fatalf("unable to sync alice's channel: %v", err) } if err := bobChannel.channelState.FullSync(); err != nil { t.Fatalf("unable to sync bob's channel: %v", err) } aliceStartingBalance := aliceChannel.channelState.OurBalance bobStartingBalance := bobChannel.channelState.OurBalance const numHtlcs = 4 // Alice adds 3 HTLC's to the update log, while Bob adds a single HTLC. var alicePreimage [32]byte copy(alicePreimage[:], bytes.Repeat([]byte{0xaa}, 32)) var bobPreimage [32]byte copy(bobPreimage[:], bytes.Repeat([]byte{0xbb}, 32)) for i := 0; i < 3; i++ { rHash := fastsha256.Sum256(alicePreimage[:]) h := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{rHash}, Amount: lnwire.CreditsAmount(1000), Expiry: uint32(10), } aliceChannel.AddHTLC(h) bobChannel.ReceiveHTLC(h) } rHash := fastsha256.Sum256(bobPreimage[:]) bobh := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{rHash}, Amount: lnwire.CreditsAmount(1000), Expiry: uint32(10), } bobChannel.AddHTLC(bobh) aliceChannel.ReceiveHTLC(bobh) // Next, Alice initiates a state transition to lock in the above HTLC's. if err := forceStateTransition(aliceChannel, bobChannel); err != nil { t.Fatalf("unable to lock in HTLC's: %v", err) } // The balances of both channels should be updated accordingly. aliceBalance := aliceChannel.channelState.OurBalance expectedAliceBalance := aliceStartingBalance - btcutil.Amount(3000) bobBalance := bobChannel.channelState.OurBalance expectedBobBalance := bobStartingBalance - btcutil.Amount(1000) if aliceBalance != expectedAliceBalance { t.Fatalf("expected %v alice balance, got %v", expectedAliceBalance, aliceBalance) } if bobBalance != expectedBobBalance { t.Fatalf("expected %v bob balance, got %v", expectedBobBalance, bobBalance) } // The latest commitment from both sides should have all the HTLC's. numAliceOutgoing := aliceChannel.localCommitChain.tail().outgoingHTLCs numAliceIncoming := aliceChannel.localCommitChain.tail().incomingHTLCs if len(numAliceOutgoing) != 3 { t.Fatalf("expected %v htlcs, instead got %v", 3, numAliceOutgoing) } if len(numAliceIncoming) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numAliceIncoming) } numBobOutgoing := bobChannel.localCommitChain.tail().outgoingHTLCs numBobIncoming := bobChannel.localCommitChain.tail().incomingHTLCs if len(numBobOutgoing) != 1 { t.Fatalf("expected %v htlcs, instead got %v", 1, numBobOutgoing) } if len(numBobIncoming) != 3 { t.Fatalf("expected %v htlcs, instead got %v", 3, numBobIncoming) } // Now fetch both of the channels created above from disk to simulate a // node restart with persistence. alicePub := aliceChannel.channelState.IdentityPub aliceChannels, err := aliceChannel.channelState.Db.FetchOpenChannels(alicePub) if err != nil { t.Fatalf("unable to fetch channel: %v", err) } bobPub := bobChannel.channelState.IdentityPub bobChannels, err := bobChannel.channelState.Db.FetchOpenChannels(bobPub) if err != nil { t.Fatalf("unable to fetch channel: %v", err) } notifier := aliceChannel.channelEvents aliceChannelNew, err := NewLightningChannel(aliceChannel.signer, nil, notifier, aliceChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } bobChannelNew, err := NewLightningChannel(bobChannel.signer, nil, notifier, bobChannels[0]) if err != nil { t.Fatalf("unable to create new channel: %v", err) } if err := initRevocationWindows(aliceChannelNew, bobChannelNew, 3); err != nil { t.Fatalf("unable to init revocation windows: %v", err) } // The state update logs of the new channels and the old channels // should now be identical other than the height the HTLC's were added. if aliceChannel.ourLogCounter != aliceChannelNew.ourLogCounter { t.Fatalf("alice log counter: expected %v, got %v", aliceChannel.ourLogCounter, aliceChannelNew.ourLogCounter) } if aliceChannel.theirLogCounter != aliceChannelNew.theirLogCounter { t.Fatalf("alice log counter: expected %v, got %v", aliceChannel.theirLogCounter, aliceChannelNew.theirLogCounter) } if aliceChannel.ourUpdateLog.Len() != aliceChannelNew.ourUpdateLog.Len() { t.Fatalf("alice log len: expected %v, got %v", aliceChannel.ourUpdateLog.Len(), aliceChannelNew.ourUpdateLog.Len()) } if aliceChannel.theirUpdateLog.Len() != aliceChannelNew.theirUpdateLog.Len() { t.Fatalf("alice log len: expected %v, got %v", aliceChannel.theirUpdateLog.Len(), aliceChannelNew.theirUpdateLog.Len()) } if bobChannel.ourLogCounter != bobChannelNew.ourLogCounter { t.Fatalf("bob log counter: expected %v, got %v", bobChannel.ourLogCounter, bobChannelNew.ourLogCounter) } if bobChannel.theirLogCounter != bobChannelNew.theirLogCounter { t.Fatalf("bob log counter: expected %v, got %v", bobChannel.theirLogCounter, bobChannelNew.theirLogCounter) } if bobChannel.ourUpdateLog.Len() != bobChannelNew.ourUpdateLog.Len() { t.Fatalf("bob log len: expected %v, got %v", bobChannelNew.ourUpdateLog.Len(), bobChannelNew.ourUpdateLog.Len()) } if bobChannel.theirUpdateLog.Len() != bobChannelNew.theirUpdateLog.Len() { t.Fatalf("bob log len: expected %v, got %v", bobChannel.theirUpdateLog.Len(), bobChannelNew.theirUpdateLog.Len()) } // Now settle all the HTLC's, then force a state update. The state // update should suceed as both sides have identical. for i := 0; i < 3; i++ { settleIndex, err := bobChannelNew.SettleHTLC(alicePreimage) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = aliceChannelNew.ReceiveHTLCSettle(alicePreimage, settleIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } } settleIndex, err := aliceChannelNew.SettleHTLC(bobPreimage) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } err = bobChannelNew.ReceiveHTLCSettle(bobPreimage, settleIndex) if err != nil { t.Fatalf("unable to settle htlc: %v", err) } if err := forceStateTransition(aliceChannelNew, bobChannelNew); err != nil { t.Fatalf("unable to update commitments: %v", err) } // The balances of both sides should have been updated accordingly. aliceBalance = aliceChannelNew.channelState.OurBalance expectedAliceBalance = aliceStartingBalance - btcutil.Amount(2000) bobBalance = bobChannelNew.channelState.OurBalance expectedBobBalance = bobStartingBalance + btcutil.Amount(2000) if aliceBalance != expectedAliceBalance { t.Fatalf("expected %v alice balance, got %v", expectedAliceBalance, aliceBalance) } if bobBalance != expectedBobBalance { t.Fatalf("expected %v bob balance, got %v", expectedBobBalance, bobBalance) } }
// TestSimpleAddSettleWorkflow tests a simple channel scenario wherein the // 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 exercise various states of // the state machine // * DSL language perhaps? // * constructed via input/output files func TestSimpleAddSettleWorkflow(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) if err != nil { t.Fatalf("unable to create test channels: %v", err) } defer cleanUp() // The edge of the revocation window for both sides should be 3 at this // point. if aliceChannel.revocationWindowEdge != 3 { t.Fatalf("alice revocation window not incremented, is %v should be %v", aliceChannel.revocationWindowEdge, 3) } if bobChannel.revocationWindowEdge != 3 { t.Fatalf("alice revocation window not incremented, is %v should be %v", bobChannel.revocationWindowEdge, 3) } paymentPreimage := bytes.Repeat([]byte{1}, 32) paymentHash := fastsha256.Sum256(paymentPreimage) htlc := &lnwire.HTLCAddRequest{ RedemptionHashes: [][32]byte{paymentHash}, // TODO(roasbeef): properly switch to credits: (1 msat) Amount: lnwire.CreditsAmount(1e8), Expiry: uint32(5), } // First Alice adds the outgoing HTLC to her local channel's state // update log. Then Alice sends this wire message over to Bob who also // adds this htlc to his local state update log. aliceChannel.AddHTLC(htlc) bobChannel.ReceiveHTLC(htlc) // Next alice commits this change by sending a signature message. aliceSig, bobLogIndex, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("alice unable to sign commitment: %v", err) } // 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 { t.Fatalf("bob unable to process alice's new commitment: %v", err) } bobSig, aliceLogIndex, err := bobChannel.SignNextCommitment() if err != nil { t.Fatalf("bob unable to sign alice's commitment: %v", err) } bobRevocation, err := bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("unable to generate bob revocation: %v", err) } // 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 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 { t.Fatalf("alice unable to rocess bob's revocation: %v", err) } else if len(htlcs) != 0 { t.Fatalf("alice forwards %v htlcs, should forward none: ", len(htlcs)) } aliceRevocation, err := aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("unable to revoke alice channel: %v", err) } // Finally Bob processes Alice's revocation, at this point the new HTLC // is fully locked in within both commitment transactions. Bob should // also be able to forward an HTLC now that the HTLC has been locked // into both commitment transactions. if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation); err != nil { t.Fatalf("bob unable to process alive's revocation: %v", err) } else if len(htlcs) != 1 { t.Fatalf("bob should be able to forward an HTLC, instead can "+ "forward %v", len(htlcs)) } // At this point, both sides should have the proper balance, and // commitment height updated within their local channel state. aliceBalance := btcutil.Amount(4 * 1e8) bobBalance := btcutil.Amount(5 * 1e8) if aliceChannel.channelState.OurBalance != aliceBalance { t.Fatalf("alice has incorrect local balance %v vs %v", aliceChannel.channelState.OurBalance, aliceBalance) } if aliceChannel.channelState.TheirBalance != bobBalance { t.Fatalf("alice has incorrect remote balance %v vs %v", aliceChannel.channelState.TheirBalance, bobBalance) } if bobChannel.channelState.OurBalance != bobBalance { t.Fatalf("bob has incorrect local balance %v vs %v", bobChannel.channelState.OurBalance, bobBalance) } if bobChannel.channelState.TheirBalance != aliceBalance { t.Fatalf("bob has incorrect remote balance %v vs %v", bobChannel.channelState.TheirBalance, aliceBalance) } if bobChannel.currentHeight != 1 { t.Fatalf("bob has incorrect commitment height, %v vs %v", bobChannel.currentHeight, 1) } if aliceChannel.currentHeight != 1 { t.Fatalf("alice has incorrect commitment height, %v vs %v", aliceChannel.currentHeight, 1) } // Alice's revocation window should now be one beyond the size of the // intial window. Same goes for Bob. if aliceChannel.revocationWindowEdge != 4 { t.Fatalf("alice revocation window not incremented, is %v should be %v", aliceChannel.revocationWindowEdge, 4) } if bobChannel.revocationWindowEdge != 4 { t.Fatalf("alice revocation window not incremented, is %v should be %v", bobChannel.revocationWindowEdge, 4) } // Now we'll repeat a similar exchange, this time with Bob settling the // HTLC once he learns of the preimage. var preimage [32]byte copy(preimage[:], paymentPreimage) settleIndex, err := bobChannel.SettleHTLC(preimage) if err != nil { t.Fatalf("bob unable to settle inbound htlc: %v", err) } if err := aliceChannel.ReceiveHTLCSettle(preimage, settleIndex); err != nil { t.Fatalf("alice unable to accept settle of outbound htlc: %v", err) } bobSig2, aliceIndex2, err := bobChannel.SignNextCommitment() if err != nil { t.Fatalf("bob unable to sign settle commitment: %v", err) } if err := aliceChannel.ReceiveNewCommitment(bobSig2, aliceIndex2); err != nil { t.Fatalf("alice unable to process bob's new commitment: %v", err) } aliceSig2, bobLogIndex2, err := aliceChannel.SignNextCommitment() if err != nil { t.Fatalf("alice unable to sign new commitment: %v", err) } aliceRevocation2, err := aliceChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("alice unable to generate revoation: %v", err) } if err := bobChannel.ReceiveNewCommitment(aliceSig2, bobLogIndex2); err != nil { t.Fatalf("bob unable to process alice's new commitment: %v", err) } bobRevocation2, err := bobChannel.RevokeCurrentCommitment() if err != nil { t.Fatalf("bob unable to revoke commitment: %v", err) } if htlcs, err := bobChannel.ReceiveRevocation(aliceRevocation2); err != nil { t.Fatalf("bob unable to process alice's revocation: %v", err) } else if len(htlcs) != 0 { t.Fatalf("bob shouldn't forward any HTLC's after outgoing settle, "+ "instead can forward: %v", spew.Sdump(htlcs)) } if htlcs, err := aliceChannel.ReceiveRevocation(bobRevocation2); err != nil { t.Fatalf("alice unable to process bob's revocation: %v", err) } else if len(htlcs) != 1 { // Alice should now be able to forward the settlement HTLC to // any down stream peers. t.Fatalf("alice should be able to forward a single HTLC, "+ "instead can forward %v: %v", len(htlcs), spew.Sdump(htlcs)) } // At this point, bob should have 6BTC settled, with Alice still having // 4 BTC. They should also be at a commitment height at two, with the // revocation window extended by by 1 (5). aliceSettleBalance := btcutil.Amount(4 * 1e8) bobSettleBalance := btcutil.Amount(6 * 1e8) if aliceChannel.channelState.OurBalance != aliceSettleBalance { t.Fatalf("alice has incorrect local balance %v vs %v", aliceChannel.channelState.OurBalance, aliceSettleBalance) } if aliceChannel.channelState.TheirBalance != bobSettleBalance { t.Fatalf("alice has incorrect remote balance %v vs %v", aliceChannel.channelState.TheirBalance, bobSettleBalance) } if bobChannel.channelState.OurBalance != bobSettleBalance { t.Fatalf("bob has incorrect local balance %v vs %v", bobChannel.channelState.OurBalance, bobSettleBalance) } if bobChannel.channelState.TheirBalance != aliceSettleBalance { t.Fatalf("bob has incorrect remote balance %v vs %v", bobChannel.channelState.TheirBalance, aliceSettleBalance) } if bobChannel.currentHeight != 2 { t.Fatalf("bob has incorrect commitment height, %v vs %v", bobChannel.currentHeight, 2) } if aliceChannel.currentHeight != 2 { t.Fatalf("alice has incorrect commitment height, %v vs %v", aliceChannel.currentHeight, 2) } if aliceChannel.revocationWindowEdge != 5 { t.Fatalf("alice revocation window not incremented, is %v should be %v", aliceChannel.revocationWindowEdge, 5) } if bobChannel.revocationWindowEdge != 5 { t.Fatalf("alice revocation window not incremented, is %v should be %v", bobChannel.revocationWindowEdge, 5) } // The logs of both sides should now be cleared since the entry adding // the HTLC should have been removed once both sides recieve the // revocation. if aliceChannel.ourUpdateLog.Len() != 0 { t.Fatalf("alice's local not updated, should be empty, has %v entries "+ "instead", aliceChannel.ourUpdateLog.Len()) } if aliceChannel.theirUpdateLog.Len() != 0 { t.Fatalf("alice's remote not updated, should be empty, has %v entries "+ "instead", aliceChannel.theirUpdateLog.Len()) } if len(aliceChannel.ourLogIndex) != 0 { t.Fatalf("alice's local log index not cleared, should be empty but "+ "has %v entries", len(aliceChannel.ourLogIndex)) } if len(aliceChannel.theirLogIndex) != 0 { t.Fatalf("alice's remote log index not cleared, should be empty but "+ "has %v entries", len(aliceChannel.theirLogIndex)) } if bobChannel.ourUpdateLog.Len() != 0 { t.Fatalf("bob's local log not updated, should be empty, has %v entries "+ "instead", bobChannel.ourUpdateLog.Len()) } if bobChannel.theirUpdateLog.Len() != 0 { t.Fatalf("bob's remote log not updated, should be empty, has %v entries "+ "instead", bobChannel.theirUpdateLog.Len()) } if len(bobChannel.ourLogIndex) != 0 { t.Fatalf("bob's local log index not cleared, should be empty but "+ "has %v entries", len(bobChannel.ourLogIndex)) } if len(bobChannel.theirLogIndex) != 0 { t.Fatalf("bob's remote log index not cleared, should be empty but "+ "has %v entries", len(bobChannel.theirLogIndex)) } }