Merge pull request #4236 from guggero/grpc-docs

docs: update instructions for JavaScript and Python on how to use gRPC
This commit is contained in:
Olaoluwa Osuntokun 2020-05-08 16:13:45 -07:00 committed by GitHub
commit 2190bc7af9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 135 additions and 104 deletions

@ -1,15 +1,16 @@
# How to write a simple `lnd` client in Javascript using `node.js` # How to write a simple `lnd` client in Javascript using `node.js`
### Setup and Installation ## Setup and Installation
First, you'll need to initialize a simple nodejs project: First, you'll need to initialize a simple nodejs project:
``` ```
npm init (or npm init -f if you want to use the default values without prompt) npm init (or npm init -f if you want to use the default values without prompt)
``` ```
Then you need to install the Javascript grpc library dependency: Then you need to install the Javascript grpc and proto loader library
dependencies:
``` ```
npm install grpc --save npm install grpc @grpc/proto-loader --save
``` ```
You also need to copy the `lnd` `rpc.proto` file in your project directory (or You also need to copy the `lnd` `rpc.proto` file in your project directory (or
@ -18,49 +19,57 @@ at least somewhere reachable by your Javascript code).
The `rpc.proto` file is [located in the `lnrpc` directory of the `lnd` The `rpc.proto` file is [located in the `lnrpc` directory of the `lnd`
sources](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto). sources](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto).
In order for the auto-generated code to compile successfully, you'll need to ### Imports and Client
comment out the following line:
```
//import "google/api/annotations.proto";
```
#### Imports and Client
Every time you work with Javascript gRPC, you will have to import `grpc`, load Every time you work with Javascript gRPC, you will have to import `grpc`, load
`rpc.proto`, and create a connection to your client like so: `rpc.proto`, and create a connection to your client like so:
```js ```js
var grpc = require('grpc'); const grpc = require('grpc');
var fs = require("fs"); const protoLoader = require('@grpc/proto-loader');
const fs = require("fs");
// Due to updated ECDSA generated tls.cert we need to let gprc know that // Due to updated ECDSA generated tls.cert we need to let gprc know that
// we need to use that cipher suite otherwise there will be a handhsake // we need to use that cipher suite otherwise there will be a handhsake
// error when we communicate with the lnd rpc server. // error when we communicate with the lnd rpc server.
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// We need to give the proto loader some extra options, otherwise the code won't
// fully work with lnd.
const loaderOptions = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
};
const packageDefinition = protoLoader.loadSync('rpc.proto', loaderOptions);
// Lnd cert is at ~/.lnd/tls.cert on Linux and // Lnd cert is at ~/.lnd/tls.cert on Linux and
// ~/Library/Application Support/Lnd/tls.cert on Mac // ~/Library/Application Support/Lnd/tls.cert on Mac
var lndCert = fs.readFileSync("~/.lnd/tls.cert"); let lndCert = fs.readFileSync("~/.lnd/tls.cert");
var credentials = grpc.credentials.createSsl(lndCert); let credentials = grpc.credentials.createSsl(lndCert);
var lnrpcDescriptor = grpc.load("rpc.proto"); let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
var lnrpc = lnrpcDescriptor.lnrpc; let lnrpc = lnrpcDescriptor.lnrpc;
var lightning = new lnrpc.Lightning('localhost:10009', credentials); let lightning = new lnrpc.Lightning('localhost:10009', credentials);
``` ```
### Examples ## Examples
Let's walk through some examples of Javascript gRPC clients. These examples Let's walk through some examples of Javascript gRPC clients. These examples
assume that you have at least two `lnd` nodes running, the RPC location of one assume that you have at least two `lnd` nodes running, the RPC location of one
of which is at the default `localhost:10009`, with an open channel between the of which is at the default `localhost:10009`, with an open channel between the
two nodes. two nodes.
#### Simple RPC ### Simple RPC
```js ```js
> lightning.getInfo({}, function(err, response) { lightning.getInfo({}, function(err, response) {
console.log('GetInfo:', response); if (err) {
}); console.log('Error: ' + err);
}
console.log('GetInfo:', response);
});
``` ```
You should get something like this in your console: You should get something like this in your console:
@ -79,10 +88,10 @@ GetInfo: { identity_pubkey: '03c892e3f3f077ea1e381c081abb36491a2502bc43ed37ffb82
chains: [ 'bitcoin' ] } chains: [ 'bitcoin' ] }
``` ```
#### Response-streaming RPC ### Response-streaming RPC
```js ```js
var call = lightning.subscribeInvoices({}); let call = lightning.subscribeInvoices({});
call.on('data', function(invoice) { call.on('data', function(invoice) {
console.log(invoice); console.log(invoice);
}) })
@ -108,7 +117,7 @@ $ lncli sendpayment --pay_req=<PAYMENT_REQUEST>
Your Javascript console should now display the details of the recently satisfied Your Javascript console should now display the details of the recently satisfied
invoice. invoice.
#### Bidirectional-streaming RPC ### Bidirectional-streaming RPC
This example has a few dependencies: This example has a few dependencies:
```shell ```shell
@ -120,15 +129,15 @@ You can run the following in your shell or put it in a program and run it like
```js ```js
// Load some libraries specific to this example // Load some libraries specific to this example
var async = require('async'); const async = require('async');
var _ = require('lodash'); const _ = require('lodash');
var ByteBuffer = require('bytebuffer'); const ByteBuffer = require('bytebuffer');
var dest_pubkey = <RECEIVER_ID_PUBKEY>; let dest_pubkey = <RECEIVER_ID_PUBKEY>;
var dest_pubkey_bytes = ByteBuffer.fromHex(dest_pubkey); let dest_pubkey_bytes = ByteBuffer.fromHex(dest_pubkey);
// Set a listener on the bidirectional stream // Set a listener on the bidirectional stream
var call = lightning.sendPayment(); let call = lightning.sendPayment();
call.on('data', function(payment) { call.on('data', function(payment) {
console.log("Payment sent:"); console.log("Payment sent:");
console.log(payment); console.log(payment);
@ -153,8 +162,8 @@ function paymentSender(destination, amount) {
_.delay(callback, 2000); _.delay(callback, 2000);
}; };
} }
var payment_senders = []; let payment_senders = [];
for (var i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
payment_senders[i] = paymentSender(dest_pubkey_bytes, 100); payment_senders[i] = paymentSender(dest_pubkey_bytes, 100);
} }
async.series(payment_senders, function() { async.series(payment_senders, function() {
@ -165,67 +174,62 @@ async.series(payment_senders, function() {
This example will send a payment of 100 satoshis every 2 seconds. This example will send a payment of 100 satoshis every 2 seconds.
#### Using Macaroons ### Using Macaroons
To authenticate using macaroons you need to include the macaroon in the metadata of the request. To authenticate using macaroons you need to include the macaroon in the metadata
of each request.
The following snippet will add the macaroon to every request automatically:
```js ```js
var fs = require('fs'); const fs = require('fs');
var grpc = require('grpc'); const grpc = require('grpc');
const protoLoader = require('@grpc/proto-loader');
const loaderOptions = {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
};
const packageDefinition = protoLoader.loadSync('rpc.proto', loaderOptions);
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and // Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and
// ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac // ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac
var m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon'); let m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
var macaroon = m.toString('hex'); let macaroon = m.toString('hex');
var meta = new grpc.Metadata().add('macaroon', macaroon);
var lnrpcDescriptor = grpc.load("rpc.proto");
var lnrpc = lnrpcDescriptor.lnrpc;
var client = new lnrpc.Lightning('some.address:10009', grpc.credentials.createInsecure());
client.getInfo({}, meta);
```
However, this can get tiresome to do for each request, so to avoid explicitly including the macaroon we can update the credentials to include it automatically.
```js
var fs = require('fs');
var grpc = require('grpc');
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA'
// Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and
// ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac
var m = fs.readFileSync('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon');
var macaroon = m.toString('hex');
// build meta data credentials // build meta data credentials
var metadata = new grpc.Metadata() let metadata = new grpc.Metadata()
metadata.add('macaroon', macaroon) metadata.add('macaroon', macaroon)
var macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => { let macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => {
callback(null, metadata); callback(null, metadata);
}); });
// build ssl credentials using the cert the same as before // build ssl credentials using the cert the same as before
var lndCert = fs.readFileSync("~/.lnd/tls.cert"); let lndCert = fs.readFileSync("~/.lnd/tls.cert");
var sslCreds = grpc.credentials.createSsl(lndCert); let sslCreds = grpc.credentials.createSsl(lndCert);
// combine the cert credentials and the macaroon auth credentials // combine the cert credentials and the macaroon auth credentials
// such that every call is properly encrypted and authenticated // such that every call is properly encrypted and authenticated
var credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds); let credentials = grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds);
// Pass the crendentials when creating a channel // Pass the crendentials when creating a channel
var lnrpcDescriptor = grpc.load("rpc.proto"); let lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
var lnrpc = lnrpcDescriptor.lnrpc; let lnrpc = lnrpcDescriptor.lnrpc;
var client = new lnrpc.Lightning('some.address:10009', credentials); let client = new lnrpc.Lightning('some.address:10009', credentials);
client.getInfo({}, (err, res) => { ... }); client.getInfo({}, (err, response) => {
if (err) {
console.log('Error: ' + err);
}
console.log('GetInfo:', response);
});
``` ```
## Conclusion
### Conclusion
With the above, you should have all the `lnd` related `gRPC` dependencies With the above, you should have all the `lnd` related `gRPC` dependencies
installed locally in your project. In order to get up to speed with `protofbuf` installed locally in your project. In order to get up to speed with `protofbuf`
@ -234,3 +238,9 @@ Javascript](https://developers.google.com/protocol-buffers/docs/reference/javasc
Additionally, [this official gRPC Additionally, [this official gRPC
resource](http://www.grpc.io/docs/tutorials/basic/node.html) provides more resource](http://www.grpc.io/docs/tutorials/basic/node.html) provides more
details around how to drive `gRPC` from `node.js`. details around how to drive `gRPC` from `node.js`.
## API documentation
There is an [online API documentation](https://api.lightning.community?javascript)
available that shows all currently existing RPC methods, including code snippets
on how to use them.

@ -3,46 +3,61 @@
This section enumerates what you need to do to write a client that communicates This section enumerates what you need to do to write a client that communicates
with `lnd` in Python. with `lnd` in Python.
### Setup and Installation ## Setup and Installation
Lnd uses the gRPC protocol for communication with clients like lncli. gRPC is Lnd uses the gRPC protocol for communication with clients like lncli. gRPC is
based on protocol buffers and as such, you will need to compile the lnd proto based on protocol buffers and as such, you will need to compile the lnd proto
file in Python before you can use it to communicate with lnd. file in Python before you can use it to communicate with lnd.
* Create a virtual environment for your project 1. Create a virtual environment for your project
``` ```
$ virtualenv lnd $ virtualenv lnd
``` ```
* Activate the virtual environment 2. Activate the virtual environment
``` ```
$ source lnd/bin/activate $ source lnd/bin/activate
``` ```
* Install dependencies (googleapis-common-protos is required due to the use of 3. Install dependencies (googleapis-common-protos is required due to the use of
google/api/annotations.proto) google/api/annotations.proto)
``` ```
(lnd)$ pip install grpcio grpcio-tools googleapis-common-protos (lnd)$ pip install grpcio grpcio-tools googleapis-common-protos
``` ```
* Clone the google api's repository (required due to the use of 4. Clone the google api's repository (required due to the use of
google/api/annotations.proto) google/api/annotations.proto)
``` ```
(lnd)$ git clone https://github.com/googleapis/googleapis.git (lnd)$ git clone https://github.com/googleapis/googleapis.git
``` ```
* Copy the lnd rpc.proto file (you'll find this at 5. Copy the lnd rpc.proto file (you'll find this at
[lnrpc/rpc.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto)) [lnrpc/rpc.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/rpc.proto))
or just download it or just download it
``` ```
(lnd)$ curl -o rpc.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/rpc.proto (lnd)$ curl -o rpc.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/rpc.proto
``` ```
* Compile the proto file 6. Compile the proto file
``` ```
(lnd)$ python -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. rpc.proto (lnd)$ python -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. rpc.proto
``` ```
After following these steps, two files `rpc_pb2.py` and `rpc_pb2_grpc.py` will After following these steps, two files `rpc_pb2.py` and `rpc_pb2_grpc.py` will
be generated. These files will be imported in your project anytime you use be generated. These files will be imported in your project anytime you use
Python gRPC. Python gRPC.
#### Imports and Client ### Generating RPC modules for subservers
If you want to use any of the subservers' functionality, you also need to
generate the python modules for them.
For example, if you want to generate the RPC modules for the `Router` subserver
(located/defined in `routerrpc/router.proto`), you need to run the following two
extra steps (after completing all 6 step described above) to get the
`router_pb2.py` and `router_pb2_grpc.py`:
```
(lnd)$ curl -o router.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto
(lnd)$ python -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. router.proto
```
### Imports and Client
Every time you use Python gRPC, you will have to import the generated rpc modules Every time you use Python gRPC, you will have to import the generated rpc modules
and set up a channel and stub to your connect to your `lnd` node: and set up a channel and stub to your connect to your `lnd` node:
@ -66,13 +81,13 @@ channel = grpc.secure_channel('localhost:10009', creds)
stub = lnrpc.LightningStub(channel) stub = lnrpc.LightningStub(channel)
``` ```
### Examples ## Examples
Let's walk through some examples of Python gRPC clients. These examples assume Let's walk through some examples of Python gRPC clients. These examples assume
that you have at least two `lnd` nodes running, the RPC location of one of which that you have at least two `lnd` nodes running, the RPC location of one of which
is at the default `localhost:10009`, with an open channel between the two nodes. is at the default `localhost:10009`, with an open channel between the two nodes.
#### Simple RPC ### Simple RPC
```python ```python
# Retrieve and display the wallet balance # Retrieve and display the wallet balance
@ -80,7 +95,7 @@ response = stub.WalletBalance(ln.WalletBalanceRequest())
print(response.total_balance) print(response.total_balance)
``` ```
#### Response-streaming RPC ### Response-streaming RPC
```python ```python
request = ln.InvoiceSubscription() request = ln.InvoiceSubscription()
@ -102,7 +117,7 @@ $ lncli sendpayment --pay_req=<PAY_REQ>
Your Python console should now display the details of the recently satisfied Your Python console should now display the details of the recently satisfied
invoice. invoice.
#### Bidirectional-streaming RPC ### Bidirectional-streaming RPC
```python ```python
from time import sleep from time import sleep
@ -133,7 +148,7 @@ for payment in stub.SendPayment(request_iterable):
``` ```
This example will send a payment of 100 satoshis every 2 seconds. This example will send a payment of 100 satoshis every 2 seconds.
#### Using Macaroons ### Using Macaroons
To authenticate using macaroons you need to include the macaroon in the metadata of the request. To authenticate using macaroons you need to include the macaroon in the metadata of the request.
@ -180,7 +195,7 @@ stub.GetInfo(ln.GetInfoRequest())
``` ```
### Conclusion ## Conclusion
With the above, you should have all the `lnd` related `gRPC` dependencies With the above, you should have all the `lnd` related `gRPC` dependencies
installed locally into your virtual environment. In order to get up to speed installed locally into your virtual environment. In order to get up to speed
@ -189,3 +204,9 @@ Python](https://developers.google.com/protocol-buffers/docs/pythontutorial).
Additionally, [this official gRPC Additionally, [this official gRPC
resource](http://www.grpc.io/docs/tutorials/basic/python.html) provides more resource](http://www.grpc.io/docs/tutorials/basic/python.html) provides more
details around how to drive `gRPC` from Python. details around how to drive `gRPC` from Python.
## API documentation
There is an [online API documentation](https://api.lightning.community?python)
available that shows all currently existing RPC methods, including code snippets
on how to use them.