lnd: implement the sendcoins RPC request

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.
This commit is contained in:
Olaoluwa Osuntokun 2016-06-29 11:31:29 -07:00
parent e391cf088e
commit f3a6f8ffe6
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2
2 changed files with 74 additions and 22 deletions

@ -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 { func makeWitnessTestCase(t *testing.T, f func() (wire.TxWitness, error)) func() wire.TxWitness {
return func() wire.TxWitness { return func() wire.TxWitness {
witness, err := f() witness, err := f()
@ -190,6 +194,8 @@ func makeWitnessTestCase(t *testing.T, f func() (wire.TxWitness, error)) func()
// * invalid sequence for CSV // * invalid sequence for CSV
// * valid lock-time+sequence, valid sig // * valid lock-time+sequence, valid sig
func TestHTLCSenderSpendValidation(t *testing.T) { func TestHTLCSenderSpendValidation(t *testing.T) {
// TODO(roasbeef): eliminate duplication with other HTLC tests.
// We generate a fake output, and the coresponding txin. This output // 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 // doesn't need to exist, as we'll only be validating spending from the
// transaction that references this. // transaction that references this.
@ -199,13 +205,15 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
} }
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
// Generate a payment and revocation pre-image to be used below.
revokePreimage := testHdSeed[:] revokePreimage := testHdSeed[:]
revokeHash := fastsha256.Sum256(revokePreimage) revokeHash := fastsha256.Sum256(revokePreimage)
paymentPreimage := revokeHash paymentPreimage := revokeHash
paymentPreimage[0] ^= 1 paymentPreimage[0] ^= 1
paymentHash := fastsha256.Sum256(paymentPreimage[:]) 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(), aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey) testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
@ -214,6 +222,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
cltvTimeout := uint32(8) cltvTimeout := uint32(8)
csvTimeout := uint32(5) csvTimeout := uint32(5)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcScript, err := senderHTLCScript(cltvTimeout, csvTimeout, htlcScript, err := senderHTLCScript(cltvTimeout, csvTimeout,
aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:])
if err != nil { if err != nil {
@ -318,6 +327,8 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
t.Fatalf("unable to create engine: %v", err) 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 var debugBuf bytes.Buffer
done := false done := false
@ -366,13 +377,15 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
} }
fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil) fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)
// Generate a payment and revocation pre-image to be used below.
revokePreimage := testHdSeed[:] revokePreimage := testHdSeed[:]
revokeHash := fastsha256.Sum256(revokePreimage) revokeHash := fastsha256.Sum256(revokePreimage)
paymentPreimage := revokeHash paymentPreimage := revokeHash
paymentPreimage[0] ^= 1 paymentPreimage[0] ^= 1
paymentHash := fastsha256.Sum256(paymentPreimage[:]) 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(), aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey) testWalletPrivKey)
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(), bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
@ -381,6 +394,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
cltvTimeout := uint32(8) cltvTimeout := uint32(8)
csvTimeout := uint32(5) csvTimeout := uint32(5)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout, htlcScript, err := receiverHTLCScript(cltvTimeout, csvTimeout,
aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:]) aliceKeyPub, bobKeyPub, revokeHash[:], paymentHash[:])
if err != nil { if err != nil {
@ -486,6 +500,8 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
t.Fatalf("unable to create engine: %v", err) 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 var debugBuf bytes.Buffer
done := false done := false

@ -62,13 +62,13 @@ func (r *rpcServer) Stop() error {
return nil return nil
} }
// SendMany handles a request for a transaction create multiple specified // addrPairsToOutputs converts a map describing a set of outputs to be created,
// outputs in parallel. // the outputs themselves. The passed map pairs up an address, to a desired
func (r *rpcServer) SendMany(ctx context.Context, // output value amount. Each address is converted to its corresponding pkScript
in *lnrpc.SendManyRequest) (*lnrpc.SendManyResponse, error) { // to be used within the constructed output(s).
func addrPairsToOutputs(addrPairs map[string]int64) ([]*wire.TxOut, error) {
outputs := make([]*wire.TxOut, 0, len(in.AddrToAmount)) outputs := make([]*wire.TxOut, 0, len(addrPairs))
for addr, amt := range in.AddrToAmount { for addr, amt := range addrPairs {
addr, err := btcutil.DecodeAddress(addr, activeNetParams) addr, err := btcutil.DecodeAddress(addr, activeNetParams)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,14 +82,50 @@ func (r *rpcServer) SendMany(ctx context.Context,
outputs = append(outputs, wire.NewTxOut(amt, pkscript)) outputs = append(outputs, wire.NewTxOut(amt, pkscript))
} }
// Instruct the wallet to create an transaction paying to the specified return outputs, nil
// outputs, selecting any coins with at least one confirmation. }
txid, err := r.server.lnwallet.SendOutputs(outputs, defaultAccount, 1)
// 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 { if err != nil {
return nil, err 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 return &lnrpc.SendManyResponse{Txid: txid.String()}, nil
} }
@ -119,7 +155,7 @@ func (r *rpcServer) NewAddress(ctx context.Context,
return nil, err 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 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) 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) peerAddr, err := lndc.LnAddrFromString(idAtHost)
if err != nil { if err != nil {
@ -155,7 +191,7 @@ func (r *rpcServer) ConnectPeer(ctx context.Context,
func (r *rpcServer) OpenChannel(ctx context.Context, func (r *rpcServer) OpenChannel(ctx context.Context,
in *lnrpc.OpenChannelRequest) (*lnrpc.OpenChannelResponse, error) { 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, "allocation(us=%v, them=%v) numconfs=%v", in.TargetPeerId,
in.LocalFundingAmount, in.RemoteFundingAmount, in.NumConfs) in.LocalFundingAmount, in.RemoteFundingAmount, in.NumConfs)
@ -171,7 +207,7 @@ func (r *rpcServer) OpenChannel(ctx context.Context,
return nil, err return nil, err
} }
rpcsLog.Tracef("Opened channel with peerid(%v), ChannelPoint(%v)", rpcsLog.Tracef("[openchannel] success peerid(%v), ChannelPoint(%v)",
in.TargetPeerId, resp) in.TargetPeerId, resp)
return &lnrpc.OpenChannelResponse{ return &lnrpc.OpenChannelResponse{
@ -191,12 +227,12 @@ func (r *rpcServer) CloseChannel(ctx context.Context,
index := in.ChannelPoint.OutputIndex index := in.ChannelPoint.OutputIndex
txid, err := wire.NewShaHash(in.ChannelPoint.FundingTxid) txid, err := wire.NewShaHash(in.ChannelPoint.FundingTxid)
if err != nil { if err != nil {
rpcsLog.Errorf("(closechannel) invalid txid: %v", err) rpcsLog.Errorf("[closechannel] invalid txid: %v", err)
return nil, err return nil, err
} }
targetChannelPoint := wire.NewOutPoint(txid, index) targetChannelPoint := wire.NewOutPoint(txid, index)
rpcsLog.Tracef("Recieved closechannel request for ChannelPoint(%v)", rpcsLog.Tracef("[closechannel] request for ChannelPoint(%v)",
targetChannelPoint) targetChannelPoint)
resp, err := r.server.CloseChannel(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, func (r *rpcServer) ListPeers(ctx context.Context,
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) { in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {
rpcsLog.Tracef("recieved listpeers request") rpcsLog.Tracef("[listpeers] request")
serverPeers := r.server.Peers() serverPeers := r.server.Peers()
resp := &lnrpc.ListPeersResponse{ resp := &lnrpc.ListPeersResponse{
@ -250,7 +286,7 @@ func (r *rpcServer) ListPeers(ctx context.Context,
resp.Peers = append(resp.Peers, peer) resp.Peers = append(resp.Peers, peer)
} }
rpcsLog.Tracef("listpeers yielded %v peers", serverPeers) rpcsLog.Debugf("[listpeers] yielded %v peers", serverPeers)
return resp, nil return resp, nil
} }
@ -288,7 +324,7 @@ func (r *rpcServer) WalletBalance(ctx context.Context,
balance = outputSum.ToBTC() balance = outputSum.ToBTC()
} }
rpcsLog.Debugf("walletbalance query response: %v", balance) rpcsLog.Debugf("[walletbalance] balance=%v", balance)
return &lnrpc.WalletBalanceResponse{balance}, nil return &lnrpc.WalletBalanceResponse{balance}, nil
} }