github.com/decred/dcrlnd@v0.7.6/docs/grpc/python.md (about) 1 # How to write a Python gRPC client for the Lightning Network Daemon 2 3 This section enumerates what you need to do to write a client that communicates 4 with `lnd` in Python. 5 6 ## Setup and Installation 7 8 Lnd uses the gRPC protocol for communication with clients like lncli. gRPC is 9 based on protocol buffers and as such, you will need to compile the lnd proto 10 file in Python before you can use it to communicate with lnd. 11 12 1. Create a virtual environment for your project 13 ``` 14 $ virtualenv lnd 15 ``` 16 2. Activate the virtual environment 17 ``` 18 $ source lnd/bin/activate 19 ``` 20 3. Install dependencies (googleapis-common-protos is required due to the use of 21 google/api/annotations.proto) 22 ``` 23 (lnd)$ pip install grpcio grpcio-tools googleapis-common-protos 24 ``` 25 4. Clone the google api's repository (required due to the use of 26 google/api/annotations.proto) 27 ``` 28 (lnd)$ git clone https://github.com/googleapis/googleapis.git 29 ``` 30 5. Copy the lnd lightning.proto file (you'll find this at 31 [lnrpc/lightning.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto)) 32 or just download it 33 ```shell 34 lnd ⛰ curl -o lightning.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto 35 ``` 36 6. Compile the proto file 37 ```shell 38 lnd ⛰ python -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. lightning.proto 39 ``` 40 41 After following these steps, two files `lightning_pb2.py` and 42 `lightning_pb2_grpc.py` will be generated. These files will be imported in your 43 project anytime you use Python gRPC. 44 45 ### Generating RPC modules for subservers 46 47 If you want to use any of the subservers' functionality, you also need to 48 generate the python modules for them. 49 50 For example, if you want to generate the RPC modules for the `Router` subserver 51 (located/defined in `routerrpc/router.proto`), you need to run the following two 52 extra steps (after completing all 6 step described above) to get the 53 `router_pb2.py` and `router_pb2_grpc.py`: 54 55 ``` 56 (lnd)$ curl -o router.proto -s https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto 57 (lnd)$ python -m grpc_tools.protoc --proto_path=googleapis:. --python_out=. --grpc_python_out=. router.proto 58 ``` 59 60 ### Imports and Client 61 62 Every time you use Python gRPC, you will have to import the generated rpc modules 63 and set up a channel and stub to your connect to your `lnd` node. 64 65 Note that when an IP address is used to connect to the node (e.g. 192.168.1.21 instead of localhost) you need to add `--tlsextraip=192.168.1.21` to your `lnd` configuration and re-generate the certificate (delete tls.cert and tls.key and restart lnd). 66 67 ```python 68 import lightning_pb2 as ln 69 import lightning_pb2_grpc as lnrpc 70 import grpc 71 import os 72 73 # Due to updated ECDSA generated tls.cert we need to let gprc know that 74 # we need to use that cipher suite otherwise there will be a handhsake 75 # error when we communicate with the lnd rpc server. 76 os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA' 77 78 # Lnd cert is at ~/.lnd/tls.cert on Linux and 79 # ~/Library/Application Support/Lnd/tls.cert on Mac 80 cert = open(os.path.expanduser('~/.lnd/tls.cert'), 'rb').read() 81 creds = grpc.ssl_channel_credentials(cert) 82 channel = grpc.secure_channel('localhost:10009', creds) 83 stub = lnrpc.LightningStub(channel) 84 ``` 85 86 ## Examples 87 88 Let's walk through some examples of Python gRPC clients. These examples assume 89 that you have at least two `lnd` nodes running, the RPC location of one of which 90 is at the default `localhost:10009`, with an open channel between the two nodes. 91 92 ### Simple RPC 93 94 ```python 95 # Retrieve and display the wallet balance 96 response = stub.WalletBalance(ln.WalletBalanceRequest()) 97 print(response.total_balance) 98 ``` 99 100 ### Response-streaming RPC 101 102 ```python 103 request = ln.InvoiceSubscription() 104 for invoice in stub.SubscribeInvoices(request): 105 print(invoice) 106 ``` 107 108 Now, create an invoice for your node at `localhost:10009`and send a payment to 109 it from another node. 110 ```bash 111 $ lncli addinvoice --amt=100 112 { 113 "r_hash": <R_HASH>, 114 "pay_req": <PAY_REQ> 115 } 116 $ lncli sendpayment --pay_req=<PAY_REQ> 117 ``` 118 119 Your Python console should now display the details of the recently satisfied 120 invoice. 121 122 ### Bidirectional-streaming RPC 123 124 ```python 125 from time import sleep 126 import codecs 127 128 def request_generator(dest, amt): 129 # Initialization code here 130 counter = 0 131 print("Starting up") 132 while True: 133 request = ln.SendRequest( 134 dest=dest, 135 amt=amt, 136 ) 137 yield request 138 # Alter parameters here 139 counter += 1 140 sleep(2) 141 142 # Outputs from lncli are hex-encoded 143 dest_hex = <RECEIVER_ID_PUBKEY> 144 dest_bytes = codecs.decode(dest_hex, 'hex') 145 146 request_iterable = request_generator(dest=dest_bytes, amt=100) 147 148 for payment in stub.SendPayment(request_iterable): 149 print(payment) 150 ``` 151 This example will send a payment of 100 atoms every 2 seconds. 152 153 ### Using Macaroons 154 155 To authenticate using macaroons you need to include the macaroon in the metadata of the request. 156 157 ```python 158 import codecs 159 160 # Lnd admin macaroon is at ~/.lnd/data/chain/bitcoin/simnet/admin.macaroon on Linux and 161 # ~/Library/Application Support/Lnd/data/chain/bitcoin/simnet/admin.macaroon on Mac 162 with open(os.path.expanduser('~/.lnd/data/chain/bitcoin/simnet/admin.macaroon'), 'rb') as f: 163 macaroon_bytes = f.read() 164 macaroon = codecs.encode(macaroon_bytes, 'hex') 165 ``` 166 167 The simplest approach to use the macaroon is to include the metadata in each request as shown below. 168 169 ```python 170 stub.GetInfo(ln.GetInfoRequest(), metadata=[('macaroon', macaroon)]) 171 ``` 172 173 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. 174 175 ```python 176 def metadata_callback(context, callback): 177 # for more info see grpc docs 178 callback([('macaroon', macaroon)], None) 179 180 181 # build ssl credentials using the cert the same as before 182 cert_creds = grpc.ssl_channel_credentials(cert) 183 184 # now build meta data credentials 185 auth_creds = grpc.metadata_call_credentials(metadata_callback) 186 187 # combine the cert credentials and the macaroon auth credentials 188 # such that every call is properly encrypted and authenticated 189 combined_creds = grpc.composite_channel_credentials(cert_creds, auth_creds) 190 191 # finally pass in the combined credentials when creating a channel 192 channel = grpc.secure_channel('localhost:10009', combined_creds) 193 stub = lnrpc.LightningStub(channel) 194 195 # now every call will be made with the macaroon already included 196 stub.GetInfo(ln.GetInfoRequest()) 197 ``` 198 199 200 ## Conclusion 201 202 With the above, you should have all the `lnd` related `gRPC` dependencies 203 installed locally into your virtual environment. In order to get up to speed 204 with `protofbuf` usage from Python, see [this official `protobuf` tutorial for 205 Python](https://developers.google.com/protocol-buffers/docs/pythontutorial). 206 Additionally, [this official gRPC 207 resource](http://www.grpc.io/docs/tutorials/basic/python.html) provides more 208 details around how to drive `gRPC` from Python. 209 210 ## API documentation 211 212 There is an [online API documentation](https://api.lightning.community?python) 213 available that shows all currently existing RPC methods, including code snippets 214 on how to use them. 215 216 ## Special Scenarios 217 218 Due to a conflict between lnd's `UpdateChannelPolicy` gRPC endpoint and the python reserved word list, the follow syntax is required in order to use `PolicyUpdateRequest` with the `global` variable. 219 Here is an example of a working format that allows for use of a reserved word `global` in this scenario. 220 221 ``` 222 args = {'global': True, 'base_fee_msat': 1000, 'fee_rate': 0.000001, 'time_lock_delta': 40} 223 stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(**args)) 224 ```