// BUGS: // - MinimumRecentTransactions is ignored. // - Wrong error codes when a block height or hash is not recognized func (s *walletServer) GetTransactions(ctx context.Context, req *pb.GetTransactionsRequest) ( resp *pb.GetTransactionsResponse, err error) { var startBlock, endBlock *wallet.BlockIdentifier if req.StartingBlockHash != nil && req.StartingBlockHeight != 0 { return nil, errors.New( "starting block hash and height may not be specified simultaneously") } else if req.StartingBlockHash != nil { startBlockHash, err := wire.NewShaHash(req.StartingBlockHash) if err != nil { return nil, grpc.Errorf(codes.InvalidArgument, "%s", err.Error()) } startBlock = wallet.NewBlockIdentifierFromHash(startBlockHash) } else if req.StartingBlockHeight != 0 { startBlock = wallet.NewBlockIdentifierFromHeight(req.StartingBlockHeight) } if req.EndingBlockHash != nil && req.EndingBlockHeight != 0 { return nil, grpc.Errorf(codes.InvalidArgument, "ending block hash and height may not be specified simultaneously") } else if req.EndingBlockHash != nil { endBlockHash, err := wire.NewShaHash(req.EndingBlockHash) if err != nil { return nil, grpc.Errorf(codes.InvalidArgument, "%s", err.Error()) } endBlock = wallet.NewBlockIdentifierFromHash(endBlockHash) } else if req.EndingBlockHeight != 0 { endBlock = wallet.NewBlockIdentifierFromHeight(req.EndingBlockHeight) } var minRecentTxs int if req.MinimumRecentTransactions != 0 { if endBlock != nil { return nil, grpc.Errorf(codes.InvalidArgument, "ending block and minimum number of recent transactions "+ "may not be specified simultaneously") } minRecentTxs = int(req.MinimumRecentTransactions) if minRecentTxs < 0 { return nil, grpc.Errorf(codes.InvalidArgument, "minimum number of recent transactions may not be negative") } } _ = minRecentTxs gtr, err := s.wallet.GetTransactions(startBlock, endBlock, ctx.Done()) if err != nil { return nil, translateError(err) } return marshalGetTransactionsResult(gtr) }
// CloseChannel attempts to close an active channel identified by its channel // point. The actions of this method can additionally be augmented to attempt // a force close after a timeout period in the case of an inactive peer. func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, updateStream lnrpc.Lightning_CloseChannelServer) error { force := in.Force index := in.ChannelPoint.OutputIndex txid, err := wire.NewShaHash(in.ChannelPoint.FundingTxid) if err != nil { rpcsLog.Errorf("[closechannel] invalid txid: %v", err) return err } targetChannelPoint := wire.NewOutPoint(txid, index) rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v)", targetChannelPoint) updateChan, errChan := r.server.htlcSwitch.CloseLink(targetChannelPoint, force) out: for { select { case err := <-errChan: rpcsLog.Errorf("[closechannel] unable to close "+ "ChannelPoint(%v): %v", targetChannelPoint, err) return err case closingUpdate := <-updateChan: rpcsLog.Tracef("[closechannel] sending update: %v", closingUpdate) if err := updateStream.Send(closingUpdate); err != nil { return err } // If a final channel closing updates is being sent, // then we can break out of our dispatch loop as we no // longer need to process any further updates. switch closeUpdate := closingUpdate.Update.(type) { case *lnrpc.CloseStatusUpdate_ChanClose: h, _ := wire.NewShaHash(closeUpdate.ChanClose.ClosingTxid) rpcsLog.Infof("[closechannel] close completed: "+ "txid(%v)", h) break out } case <-r.quit: return nil } } return nil }
// WaitForChannelClose waits for a notification from the passed channel close // stream that the node has deemed the channel has been fully closed. If the // passed context has a timeout, then if the timeout is reached before the // notification is received then an error is returned. func (n *networkHarness) WaitForChannelClose(ctx context.Context, closeChanStream lnrpc.Lightning_CloseChannelClient) (*wire.ShaHash, error) { errChan := make(chan error) updateChan := make(chan *lnrpc.CloseStatusUpdate_ChanClose) go func() { closeResp, err := closeChanStream.Recv() if err != nil { errChan <- err return } closeFin, ok := closeResp.Update.(*lnrpc.CloseStatusUpdate_ChanClose) if !ok { errChan <- fmt.Errorf("expected channel close update, "+ "instead got %v", closeFin) return } updateChan <- closeFin }() // Wait until either the deadline for the context expires, an error // occurs, or the channel close update is received. select { case <-ctx.Done(): return nil, fmt.Errorf("timeout reached before update sent") case err := <-errChan: return nil, err case update := <-updateChan: return wire.NewShaHash(update.ChanClose.ClosingTxid) } }
// 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 // found within a block, and that Alice can report the status of the new // channel. func openChannelAndAssert(t *harnessTest, 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) } // 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. block := mineBlocks(t, net, 1)[0] fundingChanPoint, err := net.WaitForChannelOpen(ctx, chanOpenUpdate) if err != nil { t.Fatalf("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) } assertTxInBlock(t, 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, alice, &chanPoint); err != nil { t.Fatalf("unable to assert channel existence: %v", err) } return fundingChanPoint }
// CloseChannel close channel attempts to close the channel indicated by the // passed channel point, initiated by the passed lnNode. If the passed context // has a timeout, then if the timeout is reached before the channel close is // pending, then an error is returned. func (n *networkHarness) CloseChannel(ctx context.Context, lnNode *lightningNode, cp *lnrpc.ChannelPoint, force bool) (lnrpc.Lightning_CloseChannelClient, error) { closeReq := &lnrpc.CloseChannelRequest{ ChannelPoint: cp, Force: force, } closeRespStream, err := lnNode.CloseChannel(ctx, closeReq) if err != nil { return nil, fmt.Errorf("unable to close channel: %v", err) } errChan := make(chan error) fin := make(chan struct{}) go func() { // Consume the "channel close" update in order to wait for the closing // transaction to be broadcast, then wait for the closing tx to be seen // within the network. closeResp, err := closeRespStream.Recv() if err != nil { errChan <- err return } pendingClose, ok := closeResp.Update.(*lnrpc.CloseStatusUpdate_ClosePending) if !ok { errChan <- fmt.Errorf("expected channel close update, "+ "instead got %v", pendingClose) return } closeTxid, err := wire.NewShaHash(pendingClose.ClosePending.Txid) if err != nil { errChan <- err return } if err := n.WaitForTxBroadcast(ctx, *closeTxid); err != nil { errChan <- err return } close(fin) }() // Wait until either the deadline for the context expires, an error // occurs, or the channel close update is received. select { case <-ctx.Done(): return nil, fmt.Errorf("timeout reached before channel close " + "initiated") case err := <-errChan: return nil, err case <-fin: return closeRespStream, nil } }
// OpenChannel attempts to open a singly funded channel specified in the // request to a remote peer. func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, updateStream lnrpc.Lightning_OpenChannelServer) error { rpcsLog.Tracef("[openchannel] request to peerid(%v) "+ "allocation(us=%v, them=%v) numconfs=%v", in.TargetPeerId, in.LocalFundingAmount, in.RemoteFundingAmount, in.NumConfs) localFundingAmt := btcutil.Amount(in.LocalFundingAmount) remoteFundingAmt := btcutil.Amount(in.RemoteFundingAmount) nodepubKey, err := btcec.ParsePubKey(in.NodePubkey, btcec.S256()) if err != nil { return err } updateChan, errChan := r.server.OpenChannel(in.TargetPeerId, nodepubKey, localFundingAmt, remoteFundingAmt, in.NumConfs) var outpoint wire.OutPoint out: for { select { case err := <-errChan: rpcsLog.Errorf("unable to open channel to "+ "identityPub(%x) nor peerID(%v): %v", nodepubKey, in.TargetPeerId, err) return err case fundingUpdate := <-updateChan: rpcsLog.Tracef("[openchannel] sending update: %v", fundingUpdate) if err := updateStream.Send(fundingUpdate); err != nil { return err } // If a final channel open update is being sent, then // we can break out of our recv loop as we no longer // need to process any further updates. switch update := fundingUpdate.Update.(type) { case *lnrpc.OpenStatusUpdate_ChanOpen: chanPoint := update.ChanOpen.ChannelPoint h, _ := wire.NewShaHash(chanPoint.FundingTxid) outpoint = wire.OutPoint{ Hash: *h, Index: chanPoint.OutputIndex, } break out } case <-r.quit: return nil } } rpcsLog.Tracef("[openchannel] success peerid(%v), ChannelPoint(%v)", in.TargetPeerId, outpoint) return nil }
func closeChannel(ctx *cli.Context) error { ctxb := context.Background() client := getClient(ctx) txid, err := wire.NewShaHashFromStr(ctx.String("funding_txid")) if err != nil { return err } // TODO(roasbeef): implement time deadline within server req := &lnrpc.CloseChannelRequest{ ChannelPoint: &lnrpc.ChannelPoint{ FundingTxid: txid[:], OutputIndex: uint32(ctx.Int("output_index")), }, Force: ctx.Bool("force"), } stream, err := client.CloseChannel(ctxb, req) if err != nil { return err } if !ctx.Bool("block") { return nil } for { resp, err := stream.Recv() if err == io.EOF { return nil } else if err != nil { return err } switch update := resp.Update.(type) { case *lnrpc.CloseStatusUpdate_ChanClose: closingHash := update.ChanClose.ClosingTxid txid, err := wire.NewShaHash(closingHash) if err != nil { return err } printRespJson(struct { ClosingTXID string `json:"closing_txid"` }{ ClosingTXID: txid.String(), }) } } return nil }
func fetchChanElkremState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } elkremKey := make([]byte, len(elkremStateKey)+b.Len()) copy(elkremKey[:3], elkremStateKey) copy(elkremKey[3:], b.Bytes()) elkremStateBytes := bytes.NewReader(nodeChanBucket.Get(elkremKey)) revKeyBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") if err != nil { return err } channel.TheirCurrentRevocation, err = btcec.ParsePubKey(revKeyBytes, btcec.S256()) if err != nil { return err } if _, err := elkremStateBytes.Read(channel.TheirCurrentRevocationHash[:]); err != nil { return err } // TODO(roasbeef): should be rederiving on fly, or encrypting on disk. senderBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") if err != nil { return err } elkremRoot, err := wire.NewShaHash(senderBytes) if err != nil { return err } channel.LocalElkrem = elkrem.NewElkremSender(*elkremRoot) reciverBytes, err := wire.ReadVarBytes(elkremStateBytes, 0, 1000, "") if err != nil { return err } remoteE, err := elkrem.ElkremReceiverFromBytes(reciverBytes) if err != nil { return err } channel.RemoteElkrem = remoteE return nil }
address = pubKey // Delivery PkScript // Privkey: f2c00ead9cbcfec63098dc0a5f152c0165aff40a2ab92feb4e24869a284c32a7 // PKhash: n2fkWVphUzw3zSigzPsv9GuDyg9mohzKpz deliveryPkScript, _ = hex.DecodeString("76a914e8048c0fb75bdecc91ebfb99c174f4ece29ffbd488ac") // Change PkScript // Privkey: 5b18f5049efd9d3aff1fb9a06506c0b809fb71562b6ecd02f6c5b3ab298f3b0f // PKhash: miky84cHvLuk6jcT6GsSbgHR8d7eZCu9Qc changePkScript, _ = hex.DecodeString("76a914238ee44bb5c8c1314dd03974a17ec6c406fdcb8388ac") // echo -n | openssl sha256 // This stuff gets reversed!!! shaHash1Bytes, _ = hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") shaHash1, _ = wire.NewShaHash(shaHash1Bytes) outpoint1 = wire.NewOutPoint(shaHash1, 0) // echo | openssl sha256 // This stuff gets reversed!!! shaHash2Bytes, _ = hex.DecodeString("01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b") shaHash2, _ = wire.NewShaHash(shaHash2Bytes) outpoint2 = wire.NewOutPoint(shaHash2, 1) // create inputs from outpoint1 and outpoint2 inputs = []*wire.TxIn{wire.NewTxIn(outpoint1, nil, nil), wire.NewTxIn(outpoint2, nil, nil)} // Commitment Signature tx = wire.NewMsgTx() emptybytes = new([]byte) sigStr, _ = txscript.RawTxInSignature(tx, 0, *emptybytes, txscript.SigHashAll, privKey) commitSig, _ = btcec.ParseSignature(sigStr, btcec.S256())
func openChannel(ctx *cli.Context) error { // TODO(roasbeef): add deadline to context ctxb := context.Background() client := getClient(ctx) if ctx.Int("peer_id") != 0 && ctx.String("node_key") != "" { return fmt.Errorf("both peer_id and lightning_id cannot be set " + "at the same time, only one can be specified") } req := &lnrpc.OpenChannelRequest{ LocalFundingAmount: int64(ctx.Int("local_amt")), RemoteFundingAmount: int64(ctx.Int("remote_amt")), NumConfs: uint32(ctx.Int("num_confs")), } if ctx.Int("peer_id") != 0 { req.TargetPeerId = int32(ctx.Int("peer_id")) } else { nodePubHex, err := hex.DecodeString(ctx.String("node_key")) if err != nil { return fmt.Errorf("unable to decode lightning id: %v", err) } req.NodePubkey = nodePubHex } stream, err := client.OpenChannel(ctxb, req) if err != nil { return err } if !ctx.Bool("block") { return nil } for { resp, err := stream.Recv() if err == io.EOF { return nil } else if err != nil { return err } switch update := resp.Update.(type) { case *lnrpc.OpenStatusUpdate_ChanOpen: channelPoint := update.ChanOpen.ChannelPoint txid, err := wire.NewShaHash(channelPoint.FundingTxid) if err != nil { return err } index := channelPoint.OutputIndex printRespJson(struct { ChannelPoint string `json:"channel_point"` }{ ChannelPoint: fmt.Sprintf("%v:%v", txid, index), }, ) } } return nil }
// BlockRootOK checks for block self-consistency. // If the block has no wintess txs, and no coinbase witness commitment, // it only checks the tx merkle root. If either a witness commitment or // any witnesses are detected, it also checks that as well. // Returns false if anything goes wrong, true if everything is fine. func BlockOK(blk wire.MsgBlock) bool { var txids, wtxids []*wire.ShaHash // txids and wtxids // witMode true if any tx has a wintess OR coinbase has wit commit var witMode bool for _, tx := range blk.Transactions { // make slice of (w)/txids txid := tx.TxSha() wtxid := tx.WitnessHash() if !witMode && !txid.IsEqual(&wtxid) { witMode = true } txids = append(txids, &txid) wtxids = append(wtxids, &wtxid) } var commitBytes []byte // try to extract coinbase witness commitment (even if !witMode) cb := blk.Transactions[0] // get coinbase tx for i := len(cb.TxOut) - 1; i >= 0; i-- { // start at the last txout if bytes.HasPrefix(cb.TxOut[i].PkScript, WitMagicBytes) && len(cb.TxOut[i].PkScript) > 37 { // 38 bytes or more, and starts with WitMagicBytes is a hit commitBytes = cb.TxOut[i].PkScript[6:38] witMode = true // it there is a wit commit it must be valid } } if witMode { // witmode, so check witness tree // first find ways witMode can be disqualified if len(commitBytes) != 32 { // witness in block but didn't find a wintess commitment; fail log.Printf("block %s has witness but no witcommit", blk.BlockSha().String()) return false } if len(cb.TxIn) != 1 { log.Printf("block %s coinbase tx has %d txins (must be 1)", blk.BlockSha().String(), len(cb.TxIn)) return false } if len(cb.TxIn[0].Witness) != 1 { log.Printf("block %s coinbase has %d witnesses (must be 1)", blk.BlockSha().String(), len(cb.TxIn[0].Witness)) return false } if len(cb.TxIn[0].Witness[0]) != 32 { log.Printf("block %s coinbase has %d byte witness nonce (not 32)", blk.BlockSha().String(), len(cb.TxIn[0].Witness[0])) return false } // witness nonce is the cb's witness, subject to above constraints witNonce, err := wire.NewShaHash(cb.TxIn[0].Witness[0]) if err != nil { log.Printf("Witness nonce error: %s", err.Error()) return false // not sure why that'd happen but fail } var empty [32]byte wtxids[0].SetBytes(empty[:]) // coinbase wtxid is 0x00...00 // witness root calculated from wtixds witRoot := calcRoot(wtxids) calcWitCommit := wire.DoubleSha256SH( append(witRoot.Bytes(), witNonce.Bytes()...)) // witness root given in coinbase op_return givenWitCommit, err := wire.NewShaHash(commitBytes) if err != nil { log.Printf("Witness root error: %s", err.Error()) return false // not sure why that'd happen but fail } // they should be the same. If not, fail. if !calcWitCommit.IsEqual(givenWitCommit) { log.Printf("Block %s witRoot error: calc %s given %s", blk.BlockSha().String(), calcWitCommit.String(), givenWitCommit.String()) return false } } // got through witMode check so that should be OK; // check regular txid merkleroot. Which is, like, trivial. return blk.Header.MerkleRoot.IsEqual(calcRoot(txids)) }
// readElement is a one-stop utility function to deserialize any datastructure // encoded using the serialization format of lnwire. func readElement(r io.Reader, element interface{}) error { var err error switch e := element.(type) { case *uint8: var b [1]uint8 if _, err := r.Read(b[:]); err != nil { return err } *e = b[0] case *uint16: var b [2]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = binary.BigEndian.Uint16(b[:]) case *ErrorCode: var b [2]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = ErrorCode(binary.BigEndian.Uint16(b[:])) case *CreditsAmount: var b [8]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = CreditsAmount(int64(binary.BigEndian.Uint64(b[:]))) case *uint32: var b [4]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = binary.BigEndian.Uint32(b[:]) case *uint64: var b [8]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = binary.BigEndian.Uint64(b[:]) case *HTLCKey: var b [8]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = HTLCKey(int64(binary.BigEndian.Uint64(b[:]))) case *btcutil.Amount: var b [8]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = btcutil.Amount(int64(binary.BigEndian.Uint64(b[:]))) case **wire.ShaHash: var b wire.ShaHash if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = &b case **btcec.PublicKey: var b [33]byte if _, err = io.ReadFull(r, b[:]); err != nil { return err } pubKey, err := btcec.ParsePubKey(b[:], btcec.S256()) if err != nil { return err } *e = pubKey case *[]uint64: var numItems uint16 if err := readElement(r, &numItems); err != nil { return err } // if numItems > 65535 { // return fmt.Errorf("Too many items in []uint64") // } // Read the number of items var items []uint64 for i := uint16(0); i < numItems; i++ { var item uint64 err = readElement(r, &item) if err != nil { return err } items = append(items, item) } *e = items case *[]*btcec.Signature: var numSigs uint8 err = readElement(r, &numSigs) if err != nil { return err } if numSigs > 127 { return fmt.Errorf("Too many signatures!") } // Read that number of signatures var sigs []*btcec.Signature for i := uint8(0); i < numSigs; i++ { sig := new(btcec.Signature) err = readElement(r, &sig) if err != nil { return err } sigs = append(sigs, sig) } *e = sigs return nil case **btcec.Signature: sigBytes, err := wire.ReadVarBytes(r, 0, 73, "signature") if err != nil { return err } sig, err := btcec.ParseSignature(sigBytes, btcec.S256()) if err != nil { return err } *e = sig case *[][32]byte: // How many to read var sliceSize uint16 err = readElement(r, &sliceSize) if err != nil { return err } data := make([][32]byte, 0, sliceSize) // Append the actual for i := uint16(0); i < sliceSize; i++ { var element [32]byte err = readElement(r, &element) if err != nil { return err } data = append(data, element) } *e = data case *[32]byte: if _, err = io.ReadFull(r, e[:]); err != nil { return err } case *wire.BitcoinNet: var b [4]byte if _, err := io.ReadFull(r, b[:]); err != nil { return err } *e = wire.BitcoinNet(binary.BigEndian.Uint32(b[:])) return nil case *[]byte: bytes, err := wire.ReadVarBytes(r, 0, MaxSliceLength, "byte slice") if err != nil { return err } *e = bytes case *PkScript: pkScript, err := wire.ReadVarBytes(r, 0, 25, "pkscript") if err != nil { return err } *e = pkScript case *string: str, err := wire.ReadVarString(r, 0) if err != nil { return err } *e = str case *[]*wire.TxIn: // Read the size (1-byte number of txins) var numScripts uint8 if err := readElement(r, &numScripts); err != nil { return err } if numScripts > 127 { return fmt.Errorf("Too many txins") } // Append the actual TxIns txins := make([]*wire.TxIn, 0, numScripts) for i := uint8(0); i < numScripts; i++ { outpoint := new(wire.OutPoint) txin := wire.NewTxIn(outpoint, nil, nil) if err := readElement(r, &txin); err != nil { return err } txins = append(txins, txin) } *e = txins case **wire.TxIn: // Hash var h [32]byte if _, err = io.ReadFull(r, h[:]); err != nil { return err } hash, err := wire.NewShaHash(h[:]) if err != nil { return err } (*e).PreviousOutPoint.Hash = *hash // Index var idxBytes [4]byte _, err = io.ReadFull(r, idxBytes[:]) if err != nil { return err } (*e).PreviousOutPoint.Index = binary.BigEndian.Uint32(idxBytes[:]) return nil case **wire.OutPoint: // TODO(roasbeef): consolidate with above var h [32]byte if _, err = io.ReadFull(r, h[:]); err != nil { return err } hash, err := wire.NewShaHash(h[:]) if err != nil { return err } // Index var idxBytes [4]byte _, err = io.ReadFull(r, idxBytes[:]) if err != nil { return err } index := binary.BigEndian.Uint32(idxBytes[:]) *e = wire.NewOutPoint(hash, index) default: return fmt.Errorf("Unknown type in readElement: %T", e) } return nil }
// 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, t *harnessTest) { 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 { t.Fatalf("unable to create new nodes: %v", err) } ctx, _ = context.WithTimeout(context.Background(), timeout) if err := net.ConnectNodes(ctx, net.Alice, carol); err != nil { t.Fatalf("unable to connect carol to alice: %v", err) } ctx, _ = context.WithTimeout(context.Background(), timeout) carolBalance := btcutil.Amount(maxPendingChannels) * amount if err := net.SendCoins(ctx, carolBalance, carol); err != nil { t.Fatalf("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++ { ctx, _ = context.WithTimeout(context.Background(), timeout) stream, err := net.OpenChannel(ctx, net.Alice, carol, amount, 1) if err != nil { t.Fatalf("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. ctx, _ = context.WithTimeout(context.Background(), timeout) _, err = net.OpenChannel(ctx, net.Alice, carol, amount, 1) if err == nil { t.Fatalf("error wasn't received") } else if grpc.Code(err) != OpenChannelFundingError { t.Fatalf("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(t, net, 1)[0] chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels) for i, stream := range openStreams { ctx, _ = context.WithTimeout(context.Background(), timeout) fundingChanPoint, err := net.WaitForChannelOpen(ctx, stream) if err != nil { t.Fatalf("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) } assertTxInBlock(t, 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 { t.Fatalf("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 { ctx, _ = context.WithTimeout(context.Background(), timeout) closeChannelAndAssert(t, net, ctx, net.Alice, chanPoint) } }
func testMultiHopPayments(net *networkHarness, t *harnessTest) { const chanAmt = btcutil.Amount(100000) ctxb := context.Background() timeout := time.Duration(time.Second * 5) // 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) aliceChanTXID, err := wire.NewShaHash(chanPointAlice.FundingTxid) if err != nil { t.Fatalf("unable to create sha hash: %v", err) } aliceFundPoint := wire.OutPoint{ Hash: *aliceChanTXID, Index: chanPointAlice.OutputIndex, } // Create a new node (Carol), load her with some funds, then establish // a connection between Carol and Alice with a channel that has // identical capacity to the one created above. // // 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) } if err := net.ConnectNodes(ctxb, carol, net.Alice); err != nil { t.Fatalf("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) } ctxt, _ = context.WithTimeout(ctxb, timeout) chanPointCarol := openChannelAndAssert(t, net, ctxt, carol, net.Alice, chanAmt) carolChanTXID, err := wire.NewShaHash(chanPointCarol.FundingTxid) if err != nil { t.Fatalf("unable to create sha hash: %v", err) } carolFundPoint := wire.OutPoint{ Hash: *carolChanTXID, Index: chanPointCarol.OutputIndex, } // Create 5 invoices for Bob, which expect a payment from Carol for 1k // satoshis with a different preimage each time. const numPayments = 5 const paymentAmt = 1000 rHashes := make([][]byte, numPayments) for i := 0; i < numPayments; i++ { preimage := bytes.Repeat([]byte{byte(i)}, 32) invoice := &lnrpc.Invoice{ Memo: "testing", RPreimage: preimage, Value: paymentAmt, } resp, err := net.Bob.AddInvoice(ctxb, invoice) if err != nil { t.Fatalf("unable to add invoice: %v", err) } rHashes[i] = resp.RHash } // Carol's routing table should show a path from Carol -> Alice -> Bob, // with the two channels above recognized as the only links within the // network. time.Sleep(time.Second) req := &lnrpc.ShowRoutingTableRequest{} routingResp, err := carol.ShowRoutingTable(ctxb, req) if err != nil { t.Fatalf("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 "+ "network, instead %v are", len(routingResp.Channels)) } for _, link := range routingResp.Channels { switch { case link.Outpoint == aliceFundPoint.String(): switch { case link.Id1 == net.Alice.PubKeyStr && link.Id2 == net.Bob.PubKeyStr: continue case link.Id1 == net.Bob.PubKeyStr && link.Id2 == net.Alice.PubKeyStr: continue default: t.Fatalf("unkown link within routing "+ "table: %v", spew.Sdump(link)) } case link.Outpoint == carolFundPoint.String(): switch { case link.Id1 == net.Alice.PubKeyStr && link.Id2 == carol.PubKeyStr: continue case link.Id1 == carol.PubKeyStr && link.Id2 == net.Alice.PubKeyStr: continue default: t.Fatalf("unkown link within routing "+ "table: %v", spew.Sdump(link)) } default: t.Fatalf("unkown channel %v found in routing table, "+ "only %v and %v should exist", link.Outpoint, aliceFundPoint, carolFundPoint) } } // 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) } // Concurrently pay off all 5 of Bob's invoices. Each of the goroutines // will unblock on the recv once the HTLC it sent has been fully // settled. var wg sync.WaitGroup for _, rHash := range rHashes { sendReq := &lnrpc.SendRequest{ PaymentHash: rHash, Dest: net.Bob.PubKey[:], Amt: paymentAmt, } wg.Add(1) go func() { if err := carolPayStream.Send(sendReq); err != nil { t.Fatalf("unable to send payment: %v", err) } if _, err := carolPayStream.Recv(); err != nil { t.Fatalf("unable to recv pay resp: %v", err) } wg.Done() }() } finClear := make(chan struct{}) go func() { wg.Wait() close(finClear) }() select { case <-time.After(time.Second * 10): t.Fatalf("HTLC's not cleared after 10 seconds") case <-finClear: } assertAsymmetricBalance := func(node *lightningNode, chanPoint wire.OutPoint, localBalance, remoteBalance int64) { channelName := "" switch chanPoint { case carolFundPoint: channelName = "Carol(local) => Alice(remote)" case aliceFundPoint: channelName = "Alice(local) => Bob(remote)" } checkBalance := func() error { listReq := &lnrpc.ListChannelsRequest{} resp, err := node.ListChannels(ctxb, listReq) if err != nil { return fmt.Errorf("unable to for node's "+ "channels: %v", err) } for _, channel := range resp.Channels { if channel.ChannelPoint != chanPoint.String() { continue } if channel.LocalBalance != localBalance { return fmt.Errorf("%v: incorrect local "+ "balances: %v != %v", channelName, channel.LocalBalance, localBalance) } if channel.RemoteBalance != remoteBalance { return fmt.Errorf("%v: incorrect remote "+ "balances: %v != %v", channelName, channel.RemoteBalance, remoteBalance) } return nil } return fmt.Errorf("channel not found") } // As far as HTLC inclusion in commitment transaction might be // postponed we will try to check the balance couple of // times, and then if after some period of time we receive wrong // balance return the error. // TODO(roasbeef): remove sleep after invoice notification hooks // are in place var timeover uint32 go func() { <-time.After(time.Second * 20) atomic.StoreUint32(&timeover, 1) }() for { isTimeover := atomic.LoadUint32(&timeover) == 1 if err := checkBalance(); err != nil { if isTimeover { t.Fatalf("Check balance failed: %v", err) } } else { break } } } // At this point all the channels within our proto network should be // shifted by 5k satoshis in the direction of Bob, the sink within the // payment flow generated above. The order of asserts corresponds to // increasing of time is needed to embed the HTLC in commitment // transaction, in channel Carol->Alice->Bob, order is Bob,Alice,Carol. const sourceBal = int64(95000) const sinkBal = int64(5000) assertAsymmetricBalance(net.Bob, aliceFundPoint, sinkBal, sourceBal) assertAsymmetricBalance(net.Alice, aliceFundPoint, sourceBal, sinkBal) assertAsymmetricBalance(net.Alice, carolFundPoint, sinkBal, sourceBal) assertAsymmetricBalance(carol, carolFundPoint, sourceBal, sinkBal) ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(t, net, ctxt, net.Alice, chanPointAlice) ctxt, _ = context.WithTimeout(ctxb, timeout) closeChannelAndAssert(t, net, ctxt, carol, chanPointCarol) }
"time" "github.com/btcsuite/fastsha256" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) var ( // debugPre is the default debug preimage which is inserted into the // invoice registry if the --debughtlc flag is activated on start up. // All nodes initialize with the flag active will immediately settle // any incoming HTLC whose rHash is corresponds with the debug // preimage. debugPre, _ = wire.NewShaHash(bytes.Repeat([]byte{1}, 32)) debugHash = wire.ShaHash(fastsha256.Sum256(debugPre[:])) ) // invoiceRegistry is a central registry of all the outstanding invoices // created by the daemon. The registry is a thin wrapper around a map in order // to ensure that all updates/reads are thread safe. type invoiceRegistry struct { sync.RWMutex cdb *channeldb.DB clientMtx sync.Mutex nextClientID uint32 notificationClients map[uint32]*invoiceSubscription