From f3a6f8ffe63b513e24e09f610b7ffe5fba9b6cd7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 29 Jun 2016 11:31:29 -0700 Subject: [PATCH] lnd: implement the sendcoins RPC request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements the “send coins” RPC request which was introduced at both the lnrpc and command line level in a prior commit. A small refactoring has taken place w.r.t to sendmany+sendcoins in order to eliminate some code duplication. --- lnwallet/script_utils_test.go | 20 ++++++++- rpcserver.go | 76 ++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/lnwallet/script_utils_test.go b/lnwallet/script_utils_test.go index 03d37bd3..8580901f 100644 --- a/lnwallet/script_utils_test.go +++ b/lnwallet/script_utils_test.go @@ -164,6 +164,10 @@ func TestRevocationKeyDerivation(t *testing.T) { } } +// makeWitnessTestCase is a helper function used within test cases involving +// the validity of a crafted witness. This function is a wrapper function which +// allows constructing table-driven tests. In the case of an error while +// constructing the witness, the test fails fataly. func makeWitnessTestCase(t *testing.T, f func() (wire.TxWitness, error)) func() wire.TxWitness { return func() wire.TxWitness { witness, err := f() @@ -190,6 +194,8 @@ func makeWitnessTestCase(t *testing.T, f func() (wire.TxWitness, error)) func() // * invalid sequence for CSV // * valid lock-time+sequence, valid sig func TestHTLCSenderSpendValidation(t *testing.T) { + // TODO(roasbeef): eliminate duplication with other HTLC tests. + // We generate a fake output, and the coresponding txin. This output // doesn't need to exist, as we'll only be validating spending from the // transaction that references this. @@ -199,13 +205,15 @@ func TestHTLCSenderSpendValidation(t *testing.T) { } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) + // Generate a payment and revocation pre-image to be used below. revokePreimage := testHdSeed[:] revokeHash := fastsha256.Sum256(revokePreimage) - paymentPreimage := revokeHash paymentPreimage[0] ^= 1 paymentHash := fastsha256.Sum256(paymentPreimage[:]) + // We'll also need some tests keys for alice and bob, and meta-data of + // the HTLC output. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), @@ -214,6 +222,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { cltvTimeout := uint32(8) csvTimeout := uint32(5) + // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. htlcScript, err := senderHTLCScript(cltvTimeout, csvTimeout, aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) if err != nil { @@ -318,6 +327,8 @@ func TestHTLCSenderSpendValidation(t *testing.T) { t.Fatalf("unable to create engine: %v", err) } + // This buffer will trace execution of the Script, only dumping + // out to stdout in the case that a test fails. var debugBuf bytes.Buffer done := false @@ -366,13 +377,15 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { } fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) + // Generate a payment and revocation pre-image to be used below. revokePreimage := testHdSeed[:] revokeHash := fastsha256.Sum256(revokePreimage) - paymentPreimage := revokeHash paymentPreimage[0] ^= 1 paymentHash := fastsha256.Sum256(paymentPreimage[:]) + // We'll also need some tests keys for alice and bob, and meta-data of + // the HTLC output. aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), testWalletPrivKey) bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), @@ -381,6 +394,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { cltvTimeout := uint32(8) csvTimeout := uint32(5) + // Generate the raw HTLC redemption scripts, and its p2wsh counterpart. htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout, aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) if err != nil { @@ -486,6 +500,8 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { t.Fatalf("unable to create engine: %v", err) } + // This buffer will trace execution of the Script, only dumping + // out to stdout in the case that a test fails. var debugBuf bytes.Buffer done := false diff --git a/rpcserver.go b/rpcserver.go index 50ae2d50..5a675f63 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -62,13 +62,13 @@ func (r *rpcServer) Stop() error { return nil } -// SendMany handles a request for a transaction create multiple specified -// outputs in parallel. -func (r *rpcServer) SendMany(ctx context.Context, - in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { - - outputs := make([]*wire.TxOut, 0, len(in.AddrToAmount)) - for addr, amt := range in.AddrToAmount { +// addrPairsToOutputs converts a map describing a set of outputs to be created, +// the outputs themselves. The passed map pairs up an address, to a desired +// output value amount. Each address is converted to its corresponding pkScript +// to be used within the constructed output(s). +func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) { + outputs := make([]*wire.TxOut, 0, len(addrPairs)) + for addr, amt := range addrPairs { addr, err := btcutil.DecodeAddress(addr, activeNetParams) if err != nil { return nil, err @@ -82,14 +82,50 @@ func (r *rpcServer) SendMany(ctx context.Context, outputs = append(outputs, wire.NewTxOut(amt, pkscript)) } - // Instruct the wallet to create an transaction paying to the specified - // outputs, selecting any coins with at least one confirmation. - txid, err := r.server.lnwallet.SendOutputs(outputs, defaultAccount, 1) + return outputs, nil +} + +// sendCoinsOnChain makes an on-chain transaction in or to send coins to one or +// more addresses specified in the passed payment map. The payment map maps an +// address to a specified output value to be sent to that address. +func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64) (*wire.ShaHash, error) { + outputs, err := addrPairsToOutputs(paymentMap) if err != nil { return nil, err } - rpcsLog.Infof("Generated txid: %v", txid.String()) + return r.server.lnwallet.SendOutputs(outputs, defaultAccount, 1) +} + +// SendCoins executes a request to send coins to a particular address. Unlike +// SendMany, this RPC call only allows creating a single output at a time. +func (r *rpcServer) SendCoins(ctx context.Context, + in *lnrpc.SendCoinsRequest) (*lnrpc.SendCoinsResponse, error) { + + rpcsLog.Infof("[sendcoins] addr=%v, amt=%v", in.Addr, btcutil.Amount(in.Amount)) + + paymentMap := map[string]int64{in.Addr: in.Amount} + txid, err := r.sendCoinsOnChain(paymentMap) + if err != nil { + return nil, err + } + + rpcsLog.Infof("[sendcoins] spend generated txid: %v", txid.String()) + + return &lnrpc.SendCoinsResponse{Txid: txid.String()}, nil +} + +// SendMany handles a request for a transaction create multiple specified +// outputs in parallel. +func (r *rpcServer) SendMany(ctx context.Context, + in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { + + txid, err := r.sendCoinsOnChain(in.AddrToAmount) + if err != nil { + return nil, err + } + + rpcsLog.Infof("[sendmany] spend generated txid: %v", txid.String()) return &lnrpc.SendManyResponse{Txid: txid.String()}, nil } @@ -119,7 +155,7 @@ func (r *rpcServer) NewAddress(ctx context.Context, return nil, err } - rpcsLog.Infof("Generated new address: %v", addr.String()) + rpcsLog.Infof("[newaddress] addr=%v", addr.String()) return &lnrpc.NewAddressResponse{Address: addr.String()}, nil } @@ -132,7 +168,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, } idAtHost := fmt.Sprintf("%v@%v", in.Addr.PubKeyHash, in.Addr.Host) - rpcsLog.Debugf("Attempting to connect to peer %v", idAtHost) + rpcsLog.Debugf("[connectpeer] peer=%v", idAtHost) peerAddr, err := lndc.LnAddrFromString(idAtHost) if err != nil { @@ -155,7 +191,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, func (r *rpcServer) OpenChannel(ctx context.Context, in *lnrpc.OpenChannelRequest) (*lnrpc.OpenChannelResponse, error) { - rpcsLog.Tracef("Recieved request to openchannel to peerid(%v) "+ + rpcsLog.Tracef("[openchannel] request to peerid(%v) "+ "allocation(us=%v, them=%v) numconfs=%v", in.TargetPeerId, in.LocalFundingAmount, in.RemoteFundingAmount, in.NumConfs) @@ -171,7 +207,7 @@ func (r *rpcServer) OpenChannel(ctx context.Context, return nil, err } - rpcsLog.Tracef("Opened channel with peerid(%v), ChannelPoint(%v)", + rpcsLog.Tracef("[openchannel] success peerid(%v), ChannelPoint(%v)", in.TargetPeerId, resp) return &lnrpc.OpenChannelResponse{ @@ -191,12 +227,12 @@ func (r *rpcServer) CloseChannel(ctx context.Context, index := in.ChannelPoint.OutputIndex txid, err := wire.NewShaHash(in.ChannelPoint.FundingTxid) if err != nil { - rpcsLog.Errorf("(closechannel) invalid txid: %v", err) + rpcsLog.Errorf("[closechannel] invalid txid: %v", err) return nil, err } targetChannelPoint := wire.NewOutPoint(txid, index) - rpcsLog.Tracef("Recieved closechannel request for ChannelPoint(%v)", + rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v)", targetChannelPoint) resp, err := r.server.CloseChannel(targetChannelPoint) @@ -213,7 +249,7 @@ func (r *rpcServer) CloseChannel(ctx context.Context, func (r *rpcServer) ListPeers(ctx context.Context, in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) { - rpcsLog.Tracef("recieved listpeers request") + rpcsLog.Tracef("[listpeers] request") serverPeers := r.server.Peers() resp := &lnrpc.ListPeersResponse{ @@ -250,7 +286,7 @@ func (r *rpcServer) ListPeers(ctx context.Context, resp.Peers = append(resp.Peers, peer) } - rpcsLog.Tracef("listpeers yielded %v peers", serverPeers) + rpcsLog.Debugf("[listpeers] yielded %v peers", serverPeers) return resp, nil } @@ -288,7 +324,7 @@ func (r *rpcServer) WalletBalance(ctx context.Context, balance = outputSum.ToBTC() } - rpcsLog.Debugf("walletbalance query response: %v", balance) + rpcsLog.Debugf("[walletbalance] balance=%v", balance) return &lnrpc.WalletBalanceResponse{balance}, nil }