├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── checkchannels.py ├── checklndconf.py ├── checkmail.py ├── improvecentrality.py ├── lib ├── CandidateFilter.py ├── GraphFilter.py ├── bc_utils.py ├── fastcentrality.py ├── lnGraph.py └── nodeinterface.py ├── lnrpc_generated ├── lightning_pb2.py ├── lightning_pb2_grpc.py ├── routerrpc │ ├── router_pb2.py │ └── router_pb2_grpc.py ├── signrpc │ ├── signer_pb2.py │ └── signer_pb2_grpc.py └── walletrpc │ ├── walletkit_pb2.py │ └── walletkit_pb2_grpc.py ├── nodeview.py ├── plotforwards.py ├── relativefees.py ├── requirements.txt ├── setfeepolicy.py └── watchhtlcstream.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.cert 3 | *.macaroon 4 | *.json 5 | *.ods 6 | *.proto 7 | *.pdf 8 | *.conf 9 | *.csv 10 | *.backup 11 | googleapis 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gridflare 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lndpytools 2 | This repository is a collection of handy Python scripts for lightning node management. More scripts will be added over time. 3 | 4 | ## Available scripts 5 | ``` 6 | checkchannels.py LX Identify your least effective channels. WIP. 7 | checklndconf.py Check an lnd.conf file for some simple recommendations. 8 | checkmail.py L Check for recent keysends with metadata. 9 | improvecentrality.py X Find nodes that maximize centrality to improve routing. 10 | nodeview.py Some box&whisker plots of node data. 11 | plotforwards.py L Heatmap of recent forwarding activity. 12 | relativefees.py Summarize a node's fee policy vs neighbours. 13 | setfeepolicy.py L A basic script for setting fees and HTLC size. 14 | watchhtlcstream.py L Human-readable log and CSV of HTLC events in real time. 15 | 16 | L - Requires a connection to LND 17 | X - CPU intensive, do not run on node hardware. 18 | ``` 19 | 20 | Individual documentation for each script is contained in the top section of each file. 21 | 22 | ## Usage 23 | ### Setup 24 | 25 | Download the repository 26 | 27 | `$ git clone https://github.com/Gridflare/lndpytools.git` 28 | 29 | Change into the new directory 30 | 31 | `$ cd lndpytools` 32 | 33 | Install requirements 34 | 35 | `$ pip3 install -r requirements.txt --user` 36 | 37 | (The `--user` flag avoids conflicts with python libs installed by your system) 38 | 39 | ### With LND gRPC 40 | Most scripts use gRPC to connect to lnd directly after some setup, create the config file with 41 | 42 | `$ python3 nodeinterface.py` 43 | 44 | Double check `node.conf` and rerun nodeinterface, it will say `Connected to node ` if everything is correct 45 | 46 | ### With describegraph.json 47 | Instead of connecting to lnd, some scripts can use a fresh copy of `describegraph.json` in the lndpytools directory. 48 | The json file will be preferred over connecting to LND where possible. 49 | Create this file from lnd with 50 | 51 | `$ lncli describegraph > describegraph.json` 52 | 53 | ### Running 54 | You are now ready to run the scripts like so 55 | 56 | `$ python3 checkmail.py` 57 | 58 | ### Updates 59 | You can download updates to the repo with 60 | 61 | `$ git pull` 62 | 63 | ### Tor proxy for 1ml.com lookups 64 | 65 | The `improvecentrality.py` script uses 1ml.com for information on other nodes. To use use Tor to access 1ml.com information use the `ALL_PROXY` environment variable. For example: 66 | 67 | `$ ALL_PROXY="socks5://localhost:9050" improvecentrality.py`. 68 | 69 | ## Advanced 70 | All scripts are based on `nodeinterface.py` or `lnGraph.py`. 71 | 72 | `NodeInterface` provides an introspective thin wrapper around the LND gRPC API. It tries to be faithful to the official docs while being much less verbose. Many calls are still unsupported, work in progress. 73 | 74 | `lnGraph` provides an interface for loading LN graph data from a JSON file or LND into NetworkX or iGraph. For computationally intense centrality measures, the `fastcentrality` module can translate a NetworkX graph into iGraph for improved performance. 75 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gridflare/lndpytools/51a8f0f13ba01740f9cc568169eee4c8b4742f53/__init__.py -------------------------------------------------------------------------------- /checkchannels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Connects to LND and checks channels for issues 4 | 5 | """ 6 | from concurrent.futures import ProcessPoolExecutor 7 | from itertools import repeat 8 | import time 9 | from math import ceil 10 | 11 | from lib.nodeinterface import NodeInterface 12 | from lib.lnGraph import lnGraph 13 | from lib import fastcentrality 14 | 15 | centralitycheckcount = 23 16 | youththresholdweeks = 6 17 | 18 | 19 | def printchannelflags(mynode): 20 | print('Chanid kCap RBal Alias Flags') 21 | myneighbours = {} 22 | currentblockheight = mynode.GetInfo().block_height 23 | 24 | for chan in mynode.ListChannels().channels: 25 | 26 | flags = [] 27 | chanid = chan.chan_id 28 | alias = mynode.getAlias(chan.remote_pubkey) 29 | kcap = chan.capacity / 1000 30 | remote_ratio = chan.remote_balance / chan.capacity 31 | 32 | if chan.private: 33 | flags.append('private') 34 | 35 | if not chan.active: 36 | flags.append('inactive') 37 | 38 | totalsatsmoved = chan.total_satoshis_sent + chan.total_satoshis_received 39 | if totalsatsmoved == 0: 40 | flags.append('unused') 41 | elif chan.total_satoshis_sent == 0: 42 | flags.append('never_sent') 43 | elif chan.total_satoshis_received == 0: 44 | flags.append('never_rcvd') 45 | 46 | chanblock = chanid >> 40 # Block when the channel was created 47 | chanage = currentblockheight - chanblock 48 | 49 | if chanage < 1008 * youththresholdweeks: 50 | weeks = ceil(chanage / 1008) 51 | flags.append(f'young<{weeks}w') 52 | 53 | if chan.initiator: 54 | if remote_ratio > 0.95: flags.append('depleted') 55 | else: 56 | if remote_ratio < 0.05: flags.append('depleted') 57 | 58 | print(chanid, f'{kcap:5.0f}{remote_ratio:6.1%} {alias[:20]:20}', *flags) 59 | myneighbours[chan.remote_pubkey] = {'flags': flags, 60 | 'usage': totalsatsmoved, 61 | 'age': chanage} 62 | 63 | return myneighbours 64 | 65 | 66 | def newcentralityremoval(graphcopy, mynodekey, peer2remove): 67 | graphcopy.remove_edge(peer2remove, mynodekey) 68 | 69 | bc = fastcentrality.betweenness(graphcopy) 70 | 71 | ourcent = bc[mynodekey] 72 | theircent = bc[peer2remove] 73 | 74 | # undo for the next check in the batch 75 | graphcopy.add_edge(peer2remove, mynodekey) 76 | 77 | return ourcent, theircent 78 | 79 | 80 | def printcentralitydiffs(mynode, myneighbours): 81 | peersbyusage = [i[0] for i in 82 | sorted(myneighbours.items(), key=lambda n: n[1]['usage']) 83 | if ('private' not in i[1]['flags'] 84 | and i[1]['age'] >= 1008 * youththresholdweeks) 85 | ] 86 | underusedpeers = peersbyusage[:centralitycheckcount] 87 | 88 | if len(underusedpeers) == 0: 89 | print('All channels are young, skipping removal analysis') 90 | return 91 | 92 | print('\nFetching graph for further analysis') 93 | # Get the graph in networkx format, for reusing tools 94 | graph = lnGraph.fromlnd(lndnode=mynode, include_unannounced=True) 95 | mynodekey = mynode.GetInfo().identity_pubkey 96 | 97 | print(f'Checking up to {centralitycheckcount} least used public channels for removal centrality impact') 98 | peersbyremovalcentralityimpact = [] 99 | with ProcessPoolExecutor() as executor: 100 | t = time.time() 101 | 102 | base_centrality_future = executor.submit(fastcentrality.betweenness, graph) 103 | 104 | results = executor.map(newcentralityremoval, 105 | repeat(graph.copy()), 106 | repeat(mynodekey), 107 | underusedpeers) 108 | 109 | print('Waiting for base centrality computation to finish') 110 | print('An SSL warning may appear, please contact if you know the cause') 111 | base_centralities = base_centrality_future.result() 112 | mycurrentcentrality = base_centralities[mynodekey] 113 | counter = 0 114 | njobs = len(underusedpeers) 115 | print(f'Progress: {counter}/{njobs} {counter / njobs:.1%}', 116 | f'Elapsed time {time.time() - t:.1f}s', 117 | end='\r') 118 | 119 | for node_key, newcent in zip(underusedpeers, results): 120 | ournewcent, theirnewcent = newcent 121 | ourcentdelta = ournewcent - mycurrentcentrality 122 | theircentdelta = theirnewcent - base_centralities[node_key] 123 | 124 | peersbyremovalcentralityimpact.append((ourcentdelta, theircentdelta, node_key)) 125 | 126 | counter += 1 127 | print(f'Progress: {counter}/{njobs} {counter / njobs:.1%}', 128 | f'Elapsed time {time.time() - t:.1f}s', 129 | end='\r') 130 | 131 | print(f'Completed centrality difference calculations in {time.time() - t:.1f}s') 132 | 133 | peersbyremovalcentralityimpact.sort(reverse=True) 134 | print('Centrality change if channel closed:') 135 | print(' Us Them', 'Alias', 'Flags') 136 | for ourcentdelta, theircentdelta, pub_key in peersbyremovalcentralityimpact: 137 | 138 | if abs(base_centralities[pub_key]) > 0: 139 | theirpdelta = f'{theircentdelta / base_centralities[pub_key]:+6.1%}' 140 | else: 141 | theirpdelta = f'{"N/A":>6}' 142 | 143 | print(f'{ourcentdelta / mycurrentcentrality:+6.1%}', 144 | theirpdelta, 145 | f'{graph.nodes[pub_key]["alias"][:20]:20}', 146 | *myneighbours[pub_key]['flags'] 147 | ) 148 | 149 | 150 | if __name__ == '__main__': 151 | mynode = NodeInterface.fromconfig() 152 | myneighbours = printchannelflags(mynode) 153 | 154 | printcentralitydiffs(mynode, myneighbours) 155 | -------------------------------------------------------------------------------- /checklndconf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Use this script to check if your config had defaults that need changing. 4 | If your config is in a non-default location, pass it as an argument. 5 | """ 6 | import configparser 7 | import sys 8 | import os 9 | 10 | """ 11 | TODO 12 | check lnd version, scrape from https://github.com/lightningnetwork/lnd/releases/latest 13 | lncli -v or lncli --version or lnd --version if lnd is not running 14 | """ 15 | 16 | if len(sys.argv) > 1: 17 | confpath = sys.argv[1] 18 | if not os.path.isfile(confpath): 19 | raise FileNotFoundError(f'Please check your spelling, your config was not found at {confpath}') 20 | else: 21 | if os.path.isfile('lnd.conf'): 22 | confpath = 'lnd.conf' 23 | else: 24 | confpath = os.path.expanduser('~/.lnd/lnd.conf') 25 | if not os.path.isfile(confpath): 26 | raise FileNotFoundError(f'You config does not exist at the default location of {confpath}') 27 | 28 | lndconf = configparser.ConfigParser(strict=False) 29 | lndconf.read(confpath) 30 | 31 | 32 | AppOpts = lndconf['Application Options'] 33 | 34 | issuecount = 0 35 | if not AppOpts.get('alias'): 36 | issuecount += 1 37 | print('No alias set, other operators may have a hard time finding you') 38 | if not AppOpts.get('color'): 39 | issuecount += 1 40 | print('No color set, a color helps you stand out in explorers') 41 | if not AppOpts.get('minchansize'): 42 | issuecount += 1 43 | print('No minimum channel size, other nodes could open uneconomical channels to you') 44 | if not AppOpts.getboolean('accept-keysend'): 45 | issuecount += 1 46 | print('Node is not configured to accept keysend payments') 47 | if not AppOpts.getboolean('accept-amp'): 48 | issuecount += 1 49 | print('Node is not configured to accept AMP payments') 50 | 51 | hastor = False 52 | if 'Tor' in lndconf: 53 | hastor = lndconf['Tor'].getboolean('tor.active', False) 54 | 55 | if not any((AppOpts.get('listen'), AppOpts.getboolean('nat'), hastor)): 56 | issuecount += 1 57 | print('Your node cannot accept channels, consider setting up tor:') 58 | print('https://wiki.ion.radar.tech/tutorials/nodes/tor') 59 | 60 | wtcactive = False 61 | if 'wtclient' in lndconf: 62 | wtcactive = lndconf['wtclient'].getboolean('wtclient.active', False) 63 | 64 | if not wtcactive: 65 | issuecount += 1 66 | print('Watchtower client service (wtclient) is not activated') 67 | print('Learn more about watchtowers: https://openoms.gitbook.io/lightning-node-management/advanced-tools/watchtower') 68 | 69 | BtcOpts = lndconf['Bitcoin'] 70 | if BtcOpts.getint('bitcoin.feerate', 1) == 1: 71 | issuecount += 1 72 | print('Found default fee settings, the default rate fee is low and could cause liquidity to get stuck') 73 | 74 | autocompact = False 75 | if 'bolt' in lndconf: 76 | autocompact = lndconf['bolt'].getboolean('db.bolt.auto-compact', False) 77 | 78 | if not autocompact: 79 | issuecount += 1 80 | print('db.bolt.auto-compact is not enabled, this can help use less disk space') 81 | print('If you are on a 32-bit OS such as a Pi, your DB must never exceed 1GB') 82 | 83 | print(issuecount, 'potential improvements were found') 84 | print('See here for an explanation of all options in the config file') 85 | print('https://github.com/lightningnetwork/lnd/blob/master/sample-lnd.conf') 86 | -------------------------------------------------------------------------------- /checkmail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script checks your node's 60 most recent invoices for keysend 4 | payments with custom_records and attempts to decode the contents. 5 | Fields that cannot be decoded are printed as is. 6 | """ 7 | 8 | import time 9 | 10 | from lib.nodeinterface import NodeInterface 11 | 12 | 13 | mynode = NodeInterface.fromconfig() 14 | 15 | invoices = mynode.ListInvoices( 16 | num_max_invoices=60, 17 | reversed=True, 18 | ).invoices 19 | 20 | for invoice in invoices: 21 | if not invoice.settled: 22 | continue 23 | 24 | htlc_records = [] 25 | for htlc in invoice.htlcs: 26 | custom_records = htlc.custom_records 27 | if custom_records: 28 | htlc_records.append(custom_records) 29 | 30 | if htlc_records: 31 | print('\nReceived',invoice.value_msat/1000,'sats on',time.ctime(invoice.settle_date)) 32 | print('-'*40) 33 | 34 | for custom_records in htlc_records: 35 | for k, v in custom_records.items(): 36 | print('Key:', k) 37 | try: 38 | print(v.decode(),'\n') 39 | except UnicodeDecodeError: 40 | # This is a hack, but it works 41 | print(str(v).strip("b'"),'\n') 42 | 43 | -------------------------------------------------------------------------------- /improvecentrality.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script requires describegraph.json and will generate its own config file. 4 | Running on node hardware is not recommended, copy describegraph.json to a 5 | desktop. 6 | 7 | This script attempts to find peers that will substantially improve a node's 8 | betweenness centrality. This does not guarantee that suggestions are good 9 | routing nodes, do not blindly connect to the script's suggestions. 10 | 11 | Initial candidate selection is done by information within the graph data, 12 | further refinement is done using the 1ML availability metric and a score. 13 | 14 | The score used for the preliminary ranking of candidates is a modified farness 15 | metric. This score has no physical meaning, but appears to correlate well with 16 | betweenness centrality, while being much easier to compute. 17 | 18 | Since the end goal is improvement of centrality, and the score isn't perfect, 19 | the script will compute how each potential peer will affect centrality. 20 | 21 | The easiest speedup is to limit the number of nodes that pass final selection 22 | to a number your CPU can reasonably process, 23 | finalcandidatecount = (n*num_cpu_cores)-1 is my suggestion. 24 | 25 | If you have an opinion on what the minimum size for a channel to be relevant to 26 | the routing network is, you can dial that in, higher values will simplify the 27 | graph more and improve performance. 28 | 29 | """ 30 | 31 | from concurrent.futures import ProcessPoolExecutor 32 | from itertools import repeat 33 | import pandas as pd 34 | import numpy as np 35 | from scipy import stats 36 | 37 | from lib.GraphFilter import GraphFilter 38 | from lib.CandidateFilter import CandidateFilter 39 | from lib.lnGraph import lnGraph 40 | from lib.fastcentrality import * 41 | from lib.bc_utils import * 42 | import time 43 | 44 | 45 | def get_farness_score(peer2add, myfarness, graphcopy, idx, mynodekey): 46 | # Modify the graph with a simulated channel 47 | graphcopy.add_edge(idx[peer2add], idx[mynodekey]) 48 | 49 | mynewfarness = 1 / closeness(graphcopy, idx[mynodekey]) 50 | myfarnessdelta = mynewfarness - myfarness 51 | 52 | # Since this function is batched, and making a fresh copy is slow, 53 | # Make sure all changes are undone 54 | graphcopy.delete_edges([(idx[peer2add], idx[mynodekey])]) 55 | 56 | # Want this data from the unmodified graph 57 | # Otherwise their score will be lowered if the channel 58 | # is too beneficial to them 59 | theirfarness = 1 / closeness(graphcopy, idx[peer2add]) 60 | 61 | # This is where the magic happens 62 | # Nodes that reduce our farness, 63 | # as well as nodes with a high farness, 64 | # are prioritized. But especially nodes that have both. 65 | farnessscore = np.sqrt(abs(myfarnessdelta)) + theirfarness / 1000 66 | 67 | return farnessscore 68 | 69 | 70 | def calculate_farness_scores(candidatekeys, graph, idx, mynodekey, nthreads=None): 71 | 72 | print('Running modified farness score calculations') 73 | 74 | t = time.time() 75 | farnesscores = {} 76 | myfarness = 1 / closeness(graph, idx[mynodekey]) 77 | 78 | with ProcessPoolExecutor(max_workers=nthreads) as executor: 79 | scoreresults = executor.map(get_farness_score, 80 | candidatekeys, 81 | repeat(myfarness), 82 | repeat(graph.copy()), 83 | repeat(idx), 84 | repeat(mynodekey), 85 | chunksize=128) 86 | 87 | for nkey, score in zip(candidatekeys, scoreresults): 88 | farnesscores[nkey] = score 89 | 90 | print(f'Completed modified farness score calculations in {time.time() - t:.1f}s') 91 | 92 | return farnesscores 93 | 94 | 95 | def get_new_centrality(peer2add, graphcopy, idx, mynodekey): 96 | 97 | graphcopy.add_edge(idx[peer2add], idx[mynodekey]) 98 | 99 | newbc = betweenness(graphcopy, idx[mynodekey]) 100 | 101 | # Remove in case the same instance is reused due to batching 102 | graphcopy.delete_edges([(idx[peer2add], idx[mynodekey])]) 103 | 104 | return newbc 105 | 106 | 107 | def calculate_centrality_deltas(candidatekeys, graph, idx, mynodekey, nthreads=None): 108 | 109 | t = time.time() 110 | centralitydeltas = {} 111 | 112 | with ProcessPoolExecutor(max_workers=nthreads) as executor: 113 | print('Starting baseline centrality computation') 114 | mycentralityfuture = executor.submit(betweenness, graph, idx[mynodekey]) 115 | 116 | print('Queuing computations for new centralities') 117 | 118 | newcentralities = executor.map(get_new_centrality, 119 | candidatekeys, 120 | repeat(graph.copy()), 121 | repeat(idx), 122 | repeat(mynodekey), 123 | chunksize=4) 124 | 125 | print('Waiting for baseline centrality calculation to complete') 126 | myoldcentrality = mycentralityfuture.result() 127 | 128 | print('Our current centrality is approximately', int(myoldcentrality)) 129 | 130 | print('Collecting centrality results, this may take a while') 131 | 132 | counter = 0 133 | njobs = len(candidatekeys) 134 | print(f'Progress: {counter}/{njobs} {counter / njobs:.1%}', 135 | f'Elapsed time {time.time() - t:.1f}s', 136 | end='\r') 137 | 138 | for nkey, newcentrality in zip(candidatekeys, newcentralities): 139 | centralitydelta = newcentrality - myoldcentrality 140 | centralitydeltas[nkey] = centralitydelta 141 | 142 | counter += 1 143 | print(f'Progress: {counter}/{njobs} {counter / njobs:.1%}', 144 | f'Elapsed time {time.time() - t:.1f}s', 145 | end='\r') 146 | 147 | print(f'Completed centrality difference calculations in {time.time() - t:.1f}s') 148 | return centralitydeltas, myoldcentrality 149 | 150 | def safe_div(x,y): 151 | if y==0: return 0 152 | return x/y 153 | 154 | def print_results(centralitydeltas, mycurrentcentrality, filtered_graph, farness_scores, validate): 155 | cols = 'delta', 'MFscor', 'Avail', 'Relbty', 'Alias', 'Pubkey' 156 | print(*cols) 157 | export_dict = {k: [] for k in cols} 158 | cdeltascores = [] 159 | mfscores = [] 160 | 161 | for nkey, cdelta in sorted(centralitydeltas.items(), key=lambda i: -i[1]): 162 | nodedata = filtered_graph.nodes[nkey] 163 | alias = nodedata['alias'] 164 | mfscore = farness_scores[nkey] 165 | arank = get_1ml_stats(nkey)['noderank']['availability'] 166 | reliability = 1 - nodedata['disabledcount']['receiving'] / filtered_graph.degree(nkey) 167 | 168 | cdeltascores.append(cdelta) 169 | mfscores.append(mfscore) 170 | 171 | cdeltastr = f'{safe_div(cdelta, mycurrentcentrality):6.1%}' 172 | relbtystr = f'{reliability:6.1%}' 173 | export_dict['delta'].append(cdeltastr) 174 | export_dict['MFscor'].append(mfscore) 175 | export_dict['Avail'].append(arank) 176 | export_dict['Relbty'].append(relbtystr) 177 | export_dict['Alias'].append(alias) 178 | export_dict['Pubkey'].append(nkey) 179 | 180 | # print(f'{cdelta / mycurrentcentrality:+6.1%} {mfscore:6.2f} {arank:5}, {relbtystr}, {alias:>50}, {nkey:>20}') 181 | 182 | if validate: 183 | reg = stats.linregress(cdeltascores, mfscores) 184 | r = round(reg.rvalue, 3) 185 | print('Heuristic validation found an r value of', r) 186 | if r < 0.7: 187 | print('r is low, you will need a higher finalcandidatecount to compensate') 188 | elif r < 0.8: 189 | print('r is on the low end of the expected range for this heuristic') 190 | print('consider a higher finalcandidatecount to compensate') 191 | elif r < 0.9: 192 | print('r is on the high end of the expected range for this heuristic') 193 | else: 194 | print('r is better than expected for this heuristic') 195 | export_pd = pd.DataFrame(export_dict) 196 | print(export_pd.to_markdown()) 197 | return export_pd 198 | 199 | 200 | def save_recommendations(export_dict, config): 201 | csv_export_name = config['Other'].get('csvexportname') 202 | if csv_export_name: 203 | df = pd.DataFrame(export_dict) 204 | df.set_index('Pubkey') 205 | df.to_csv(csv_export_name) 206 | 207 | 208 | def main(): 209 | parser = make_parser() 210 | args = parser.parse_args() 211 | config = load_config(args.conffile) 212 | pub_key = config['Node']['pub_key'] 213 | 214 | nthreads = config['Other'].getint('threads', -1) 215 | nthreads = None if nthreads <= 0 else nthreads 216 | 217 | graph_filters = config['GraphFilters'] 218 | graph = lnGraph.autoload(expirehours=False, 219 | include_unannounced=graph_filters.getboolean( 220 | 'includeunannounced', False)) 221 | 222 | filtered_graph = GraphFilter(graph, pub_key, graph_filters).filtered_g 223 | fast_graph, idx = nx2ig(filtered_graph) 224 | 225 | if pub_key not in filtered_graph.nodes: 226 | print(f'Failed to find a match for pub_key={pub_key} in the graph') 227 | print('Please double check improvecentrality.conf') 228 | exit() 229 | if filtered_graph.degree(pub_key) < 2: 230 | print('This script requires your node to have a minimum of 2 stable, public channels') 231 | print('Your node does not meet this requirement at this time.') 232 | exit() 233 | 234 | print('Performing analysis for', filtered_graph.nodes[pub_key]['alias']) 235 | candidate_filters = config['CandidateFilters'] 236 | candidate_filters["pub_key"] = pub_key 237 | if args.validate: 238 | candidate_filters['finalcandidatecount'] = '400' 239 | channel_candidates = CandidateFilter(filtered_graph, candidate_filters).filtered_candidates 240 | print('First filtering pass found', len(channel_candidates), 'candidates for new channels') 241 | 242 | 243 | farness_scores = calculate_farness_scores( 244 | channel_candidates, fast_graph, idx, pub_key, nthreads=nthreads) 245 | candidates_by_farness = sorted(channel_candidates, key=lambda k: -farness_scores[k]) 246 | 247 | max_availability = candidate_filters.getint('max1mlavailability') 248 | final_candidate_count = candidate_filters.getint('finalcandidatecount') 249 | final_candidates = select_by_1ml(candidates_by_farness, max_availability, 250 | final_candidate_count) 251 | 252 | if len(final_candidates) == 0: 253 | print('No candidates found, is your graph stale?') 254 | print('If issue persists, delete describegraph.json and improvecentrality.conf') 255 | raise ValueError('No valid candidates') 256 | 257 | centrality_deltas, current_centrality = calculate_centrality_deltas( 258 | final_candidates, fast_graph, idx, pub_key, nthreads=nthreads) 259 | export_dict = print_results(centrality_deltas, current_centrality, filtered_graph, farness_scores, args.validate) 260 | save_recommendations(export_dict, config) 261 | 262 | 263 | if __name__ == '__main__': 264 | main() 265 | -------------------------------------------------------------------------------- /lib/CandidateFilter.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import numpy as np 3 | 4 | 5 | class CandidateFilter: 6 | def __init__(self, graph, candidate_filters): 7 | # Conditions a node must meet to be considered for further connection analysis 8 | self.min_channels = candidate_filters.getint('minchancount') 9 | self.min_capacity = int(candidate_filters.getfloat('mincapacitybtc') * 1e8) 10 | self.max_channels = candidate_filters.getint('maxchancount') 11 | self.max_capacity = int(candidate_filters.getfloat('maxcapacitybtc') * 1e8) 12 | self.min_avg_chan = candidate_filters.getint('minavgchan') 13 | self.minmedchan = candidate_filters.getint('minmedchan') 14 | self.minavgchanageblks = candidate_filters.getint('minavgchanageblks') 15 | self.minreliability = candidate_filters.getfloat('minreliability') 16 | self.minchannelsstr = candidate_filters['minchannels'] 17 | self.pub_key = candidate_filters['pub_key'] 18 | self.minchanstiers = self.minchannelsstr.split() 19 | 20 | self.graph = graph 21 | 22 | assert self.minavgchanageblks is not None, 'Config is missing values, try recreating it' 23 | 24 | # Can't guarantee lnd available to get this 25 | self.currblockheight = requests.get( 26 | "https://mempool.space/api/blocks/tip/height" 27 | ).json() 28 | 29 | self.filtered_candidates = [n['pub_key'] for n in 30 | filter(lambda n: self.filtercandidatenodes(n), 31 | graph.nodes.values())] 32 | 33 | def validate_capacities(self, chancaps): 34 | """ 35 | This function allows more granular selection of candidates than total or 36 | average capacity. 37 | """ 38 | for tierfilter in self.minchanstiers: 39 | if 'k' in tierfilter: 40 | ksize, mincount = tierfilter.split('k') 41 | size = int(ksize) * 1e3 42 | elif 'M' in tierfilter: 43 | Msize, mincount = tierfilter.split('M') 44 | size = int(Msize) * 1e6 45 | else: 46 | raise RuntimeError('No recognized seperator in minchannel filter') 47 | 48 | if sum((c >= size for c in chancaps)) < int(mincount): 49 | return False 50 | 51 | return True 52 | 53 | def get_avg_change(self, candidatekey, currblockheight): 54 | ages = [] 55 | for chankeypair in self.graph.edges(candidatekey): 56 | chan = self.graph.edges[chankeypair] 57 | chanblk = int(chan['channel_id']) >> 40 58 | ageblks = currblockheight - chanblk 59 | ages.append(ageblks) 60 | 61 | if len(ages) == 0: 62 | return -1 63 | 64 | return np.mean(ages) 65 | 66 | def get_reliability(self, candidatekey): 67 | # Record reliability-related data into the node record 68 | 69 | node = self.graph.nodes[candidatekey] 70 | 71 | # This should never happen, but there is evidence that it does 72 | if self.graph.degree(candidatekey) == 0: 73 | print(node) 74 | raise RuntimeError(f"{node['alias']} has no valid channels") 75 | 76 | chankeypairs = self.graph.edges(candidatekey) 77 | node['disabledcount'] = {'sending': 0, 'receiving': 0} 78 | 79 | for chankeypair in chankeypairs: 80 | chan = self.graph.edges[chankeypair] 81 | 82 | if None in [chan['node1_policy'], chan['node2_policy']]: 83 | continue # TODO: Might want to add a penalty to this 84 | 85 | if candidatekey == chan['node1_pub']: 86 | if chan['node1_policy']['disabled']: 87 | node['disabledcount']['sending'] += 1 88 | if chan['node2_policy']['disabled']: 89 | node['disabledcount']['receiving'] += 1 90 | elif candidatekey == chan['node2_pub']: 91 | if chan['node2_policy']['disabled']: 92 | node['disabledcount']['sending'] += 1 93 | if chan['node1_policy']['disabled']: 94 | node['disabledcount']['receiving'] += 1 95 | else: 96 | assert False 97 | 98 | return 1 - node['disabledcount']['receiving'] / self.graph.degree(candidatekey) 99 | 100 | def filtercandidatenodes(self, n): 101 | # This is the inital filtering pass, it does not include 1ML or centrality 102 | 103 | # If already connected or is ourself, abort 104 | nkey = n['pub_key'] 105 | if self.graph.has_edge(self.pub_key, nkey) or self.pub_key == nkey: 106 | return False 107 | 108 | cond = (len(n['addresses']) > 0, # Node must be connectable 109 | self.graph.degree(nkey) > 0, # Must have unfiltered channels 110 | self.min_channels <= n['num_channels'] <= self.max_channels, 111 | self.min_capacity <= n['capacity'] <= self.max_capacity, 112 | n['capacity'] / n['num_channels'] >= self.min_avg_chan, # avg chan size 113 | np.median(n['capacities']) >= self.minmedchan, 114 | ) 115 | 116 | # Filters that require more computation or are invalid for tiny 117 | # nodes are excluded from cond in order to get safety and a 118 | # slight performance boost from short circuiting 119 | 120 | return (all(cond) 121 | and self.validate_capacities(n['capacities']) 122 | and self.get_avg_change(nkey, self.currblockheight) >= self.minavgchanageblks 123 | and self.get_reliability(nkey) >= self.minreliability 124 | ) 125 | -------------------------------------------------------------------------------- /lib/GraphFilter.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | import time 3 | 4 | 5 | class GraphFilter: 6 | # Assumed size of the new channels, should not currently have an effect 7 | channel_size = 2e6 8 | 9 | def __init__(self, graph, mynodekey, graph_filters, channels_to_add=None, channels_to_remove=None): 10 | self.graph = graph 11 | self.graph_filters = graph_filters 12 | self.pub_key = mynodekey 13 | # Configuration ends here 14 | # self.full_g = self.autoload(expirehours=False) 15 | if channels_to_add: 16 | for peer in channels_to_add: 17 | self.graph.add_edge(mynodekey, peer, capacity=self.channel_size, last_update=time.time()) 18 | if channels_to_remove: 19 | for peer in channels_to_remove: 20 | self.graph.remove_edge(mynodekey, peer) 21 | nx.freeze(self.graph) 22 | 23 | print('Loaded graph. Number of nodes:', self.graph.number_of_nodes(), 'Edges:', self.graph.number_of_edges()) 24 | self.filtered_g = self.filter_relevant_nodes() 25 | 26 | print('Simplified graph. Number of nodes:', self.filtered_g.number_of_nodes(), 'Edges:', 27 | self.filtered_g.number_of_edges()) 28 | if self.filtered_g.number_of_edges() == 0: 29 | raise RuntimeError('No recently updated channels were found, is describgraph.json recent?') 30 | 31 | def filter_relevant_nodes(self): 32 | t = time.time() 33 | gfilt = nx.subgraph_view(self.graph, filter_node=self.filter_node, filter_edge=self.filter_edge) 34 | 35 | most_recent_update = 0 36 | for edge in self.graph.edges.values(): 37 | if most_recent_update < edge['last_update'] < t: 38 | most_recent_update = edge['last_update'] 39 | 40 | print(f'Latest update in graph: {time.ctime(most_recent_update)} ({most_recent_update})') 41 | if t - most_recent_update > 6 * 60 * 60: 42 | raise RuntimeError('Graph is more than 6 hours old, results will be innaccurate') 43 | 44 | return gfilt 45 | 46 | def filter_node(self, nk): 47 | n = self.graph.nodes[nk] 48 | t = time.time() 49 | 50 | cond = ( 51 | # A node that hasn't updated in this time might be dead 52 | t - n['last_update'] < 4 * 30 * 24 * 60 * 60, 53 | self.graph.degree(nk) > 0, # doesn't seem to fix the issue 54 | ) 55 | 56 | return all(cond) 57 | 58 | def filter_edge(self, n1, n2): 59 | minimum_capacity = self.graph_filters.getint('minrelevantchan') 60 | t = time.time() 61 | 62 | edge = self.graph.edges[n1, n2] 63 | is_adj = self.pub_key in [n1, n2] 64 | cond = ( 65 | # A channel that hasn't updated in this time might be dead 66 | t - edge['last_update'] <= 1.5 * 24 * 60 * 60, 67 | 68 | # Remove economically irrelevant channels 69 | edge['capacity'] >= minimum_capacity or is_adj, 70 | ) 71 | return all(cond) 72 | -------------------------------------------------------------------------------- /lib/bc_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import json 4 | import time 5 | import requests 6 | import configparser 7 | 8 | 9 | def load_config(config_file='improvecentrality.conf'): 10 | if os.path.isfile(config_file): 11 | config = configparser.ConfigParser() 12 | config.read(config_file) 13 | 14 | return config 15 | 16 | print('Config not found, creating', config_file) 17 | config = configparser.ConfigParser() 18 | config['Node'] = {'pub_key': 'yournodepubkeyhere'} 19 | config['GraphFilters'] = { 20 | # Ignore channels smaller than this during analysis, 21 | # unless they connect to us. Higher values improve 22 | # script performance and odds of good routes. 23 | # However lower values give numbers closer to reality 24 | 'minrelevantchan': 500_000, 25 | 26 | # Whether to include private channels 27 | # Workaround for having used chantools dropchannelgraph 28 | # Only has an effect when loading from lnd, not json 29 | 'includeunannounced': False, 30 | } 31 | config['CandidateFilters'] = { 32 | 'minchancount': 8, 33 | 'maxchancount': 10000, 34 | 'mincapacitybtc': 0.5, 35 | 'maxcapacitybtc': 1000, 36 | 'minavgchan': 1_000_000, 37 | 'minmedchan': 1_000_000, 38 | 'minavgchanageblks': 4_000, 39 | # Default 4+ >1.5M channels, 3+ >5M, 1+ >8M channels 40 | 'minchannels': '1500k4 5M3 8M1', 41 | 'minreliability': 0.97, 42 | # Node must be ranked better than this for availability on 1ml 43 | 'max1mlavailability': 2000, 44 | # Limit the number of nodes passed to the final (slow!) centrality computation 45 | 'finalcandidatecount': 11, 46 | } 47 | config['Other'] = { 48 | # Export results to this CSV file. Set to None or '' to disable 49 | 'csvexportname': 'newchannels.csv', 50 | 51 | # How many threads to use in parallel processing. 52 | # Set this if having out of memory issues. 53 | 'threads': -1, 54 | } 55 | 56 | with open(config_file, 'w') as cf: 57 | config.write(cf) 58 | 59 | print('Please complete the config and rerun') 60 | exit() 61 | 62 | 63 | def make_parser(): 64 | parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, 65 | description='Suggests peers based on centrality impact', 66 | epilog='''Tor proxy for 1ml.com lookups: 67 | Use ALL_PROXY environment variable for Tor proxy support 68 | Example: ALL_PROXY="socks5://localhost:9050" python improvecentrality.py''') 69 | parser.add_argument('--conffile', type=str, default='improvecentrality.conf', 70 | help='Specify an alternate config location') 71 | parser.add_argument('--validate', action="store_true", 72 | help='Runs a much longer analysis and reports on the heuristic') 73 | return parser 74 | 75 | 76 | def get_1ml_cache(cached1ml=None): 77 | if cached1ml is None: 78 | cached1ml = dict() 79 | if cached1ml: 80 | pass 81 | 82 | elif os.path.isfile('_cache/1mlcache.json'): 83 | # print('Found cache file for 1ML statistics') 84 | with open('_cache/1mlcache.json') as f: 85 | cached1ml.update(json.load(f)) 86 | 87 | elif not os.path.exists('_cache'): 88 | os.mkdir('_cache') 89 | 90 | return cached1ml 91 | 92 | 93 | def get_1ml_stats(node_key): 94 | cachetimeout = 3 * 24 * 60 * 60 95 | cached1ml = get_1ml_cache() 96 | 97 | if node_key in cached1ml.keys(): 98 | node1ml = cached1ml[node_key] 99 | cutofftime = time.time() - cachetimeout 100 | if node1ml.get('_fetch_time', 0) > cutofftime: 101 | return node1ml 102 | # else, fetch a new copy 103 | 104 | r = requests.get(f"https://1ml.com/node/{node_key}/json") 105 | if r.status_code != 200: 106 | print(f'Bad response {r.status_code}: {r.body}') 107 | raise RuntimeError(f'Bad response {r.status_code}: {r.body}') 108 | cached1ml[node_key] = r.json() 109 | cached1ml[node_key]['_fetch_time'] = time.time() 110 | 111 | with open('_cache/1mlcache.json', 'w') as f: 112 | json.dump(cached1ml, f, indent=2) 113 | 114 | return cached1ml[node_key] 115 | 116 | 117 | def filter_by_availability(candidatekeys, max1mlavailability): 118 | def checkavailablityscore(nodekey): 119 | nodestats1ml = get_1ml_stats(nodekey) 120 | try: 121 | availabilityrank = nodestats1ml['noderank']['availability'] 122 | except KeyError: 123 | print('Failed to fetch availability for', nodekey) 124 | return False 125 | 126 | return availabilityrank < max1mlavailability 127 | 128 | results = filter(checkavailablityscore, candidatekeys) 129 | 130 | return results 131 | 132 | 133 | def select_by_1ml(sortedcandidates, max1mlavailability, finalcandidatecount): 134 | print('Checking 1ML availability statistics') 135 | t = time.time() 136 | availablecandidates = [] 137 | for i, canditate in enumerate( 138 | filter_by_availability(sortedcandidates, max1mlavailability)): 139 | if i >= finalcandidatecount: break 140 | availablecandidates.append(canditate) 141 | 142 | print('1ML availability filter selected', 143 | len(availablecandidates), 144 | 'candidates for new channels', 145 | f'in {time.time() - t:.1f}s') 146 | return availablecandidates 147 | -------------------------------------------------------------------------------- /lib/fastcentrality.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Fucntions for using igraph tools on networkx graphs 4 | 5 | """ 6 | import networkx as nx 7 | import igraph 8 | 9 | 10 | def nx2ig(nxgraph): 11 | """ 12 | Convert networkx graph to igraph 13 | For centrality we only care about keys and channels 14 | igraph uses integer indexing, so a map will also be created. 15 | """ 16 | 17 | nxnodekeys = nxgraph.nodes 18 | nxnodemap = {nk: i for i, nk in enumerate(nxnodekeys)} 19 | 20 | ig = igraph.Graph() 21 | ig.add_vertices(len(nxnodekeys)) 22 | 23 | igedges = [(nxnodemap[nk1], nxnodemap[nk2]) 24 | for nk1, nk2 in nxgraph.edges] 25 | 26 | ig.add_edges(igedges) 27 | 28 | return ig, nxnodemap 29 | 30 | 31 | def betweenness(graph, nodeid=None): 32 | remap2nx = False 33 | if isinstance(graph, nx.Graph): 34 | # Convert nx graph to igraph format 35 | graph, nxnodemap = nx2ig(graph) 36 | # Convert nx node id to igraph integer 37 | if nodeid is not None: 38 | nodeid = nxnodemap[nodeid] 39 | else: 40 | remap2nx = True 41 | 42 | bc = graph.betweenness(nodeid) 43 | if remap2nx: 44 | # Assumes nxnodemap dict has keys in order 45 | bc = {nk: c for nk, c in zip(nxnodemap.keys(), bc)} 46 | 47 | return bc 48 | 49 | 50 | def closeness(graph, nodeid=None): 51 | remap2nx = False 52 | if isinstance(graph, nx.Graph): 53 | # Convert nx graph to igraph format 54 | graph, nxnodemap = nx2ig(graph) 55 | # Convert nx node id to igraph integer 56 | if nodeid is not None: 57 | nodeid = nxnodemap[nodeid] 58 | else: 59 | remap2nx = True 60 | 61 | cc = graph.closeness(nodeid, normalized=False) 62 | if remap2nx: 63 | # Assumes nxnodemap dict has keys in order 64 | cc = {nk: c for nk, c in zip(nxnodemap.keys(), cc)} 65 | 66 | return cc 67 | 68 | 69 | if __name__ == '__main__': 70 | # just a quick test 71 | nxg = nx.Graph() 72 | nxg.add_nodes_from(['A', 'B', 'C', 'D', 'E']) 73 | nxg.add_edges_from([('A', 'B'), ('A', 'C'), ('A', 'E'), 74 | ('C', 'D'), ('D', 'E')]) 75 | 76 | ig, nxnodemap = nx2ig(nxg) 77 | 78 | print(nxnodemap) 79 | print(ig) 80 | 81 | import time 82 | 83 | t = time.time() 84 | igbc = ig.betweenness() 85 | print('IG BCentrality in (ms)', (time.time() - t) * 1000) 86 | print(igbc) 87 | 88 | t = time.time() 89 | igcc = ig.closeness() 90 | print('IG CCentrality in (ms)', (time.time() - t) * 1000) 91 | print(igcc) 92 | 93 | t = time.time() 94 | nxbc = nx.algorithms.centrality.betweenness_centrality(nxg, normalized=False) 95 | print('NX BCentrality in (ms)', (time.time() - t) * 1000) 96 | print(nxbc) 97 | -------------------------------------------------------------------------------- /lib/lnGraph.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import time 4 | 5 | from networkx import Graph as nxGraph 6 | from igraph import Graph as igGraph 7 | 8 | from lib.nodeinterface import NodeInterface 9 | 10 | 11 | class lnGraphBase: 12 | """Holds common methods for the below classes""" 13 | 14 | @classmethod 15 | def autoload(cls, expirehours=8, include_unannounced=False): 16 | """Intelligently load from a json file or node""" 17 | 18 | # Check for json, check age 19 | graphfilename = 'describegraph.json' 20 | if os.path.isfile(graphfilename): 21 | mtime = os.path.getmtime(graphfilename) 22 | 23 | # if expired, warn and exit 24 | if expirehours: 25 | if time.time() - mtime > expirehours * 60 * 60: 26 | print(graphfilename, 'was found but is more than 8 hours old') 27 | print('Please update it or delete to attempt fetching from lnd') 28 | exit() 29 | 30 | return cls.fromjson(graphfile=graphfilename) 31 | 32 | else: 33 | # fromconfig will create and exit if the config is missing 34 | ni = NodeInterface.fromconfig() 35 | 36 | # else load from lnd 37 | print('Fetching graph data from lnd') 38 | return cls.fromlnd(lndnode=ni, include_unannounced=include_unannounced) 39 | 40 | @classmethod 41 | def fromjson(cls, graphfile='describegraph.json'): 42 | raise NotImplementedError 43 | 44 | @classmethod 45 | def fromlnd(cls, lndnode: NodeInterface=None, include_unannounced=False): 46 | raise NotImplementedError 47 | 48 | class gRPCadapters: 49 | """Collection of useful gRPC to JSON conversions""" 50 | 51 | @staticmethod 52 | def addrs2dict(addrs): 53 | # explicit convert to Python types 54 | addrlist = [] 55 | for addr in addrs: 56 | addrdict = {'network': addr.network, 'addr': addr.addr} 57 | addrlist.append(addrdict) 58 | return addrlist 59 | 60 | @staticmethod 61 | def nodepolicy2dict(np): 62 | return dict( 63 | time_lock_delta=np.time_lock_delta, 64 | min_htlc=np.min_htlc, 65 | fee_base_msat=np.fee_base_msat, 66 | fee_rate_milli_msat=np.fee_rate_milli_msat, 67 | disabled=np.disabled, 68 | max_htlc_msat=np.max_htlc_msat, 69 | last_update=np.last_update, 70 | ) 71 | 72 | 73 | class lnGraph(lnGraphBase, nxGraph): 74 | """Methods for loading network data into networkx""" 75 | 76 | @classmethod 77 | def fromjson(cls, graphfile='describegraph.json'): 78 | with open(graphfile, encoding='utf8') as f: 79 | graphdata = json.load(f) 80 | 81 | g = cls() 82 | # Leave weight undefined for now, could be capacity or fee depending on algorithm 83 | for node in graphdata['nodes']: 84 | g.add_node(node['pub_key'], 85 | pub_key=node['pub_key'], 86 | last_update=node['last_update'], 87 | alias=node['alias'], 88 | color=node['color'], 89 | addresses=node['addresses'], 90 | capacity=0, # Increment while iterating channels 91 | num_channels=0, # Increment while iterating channels 92 | capacities=[], # For creating histograms 93 | ) 94 | 95 | for edge in graphdata['edges']: 96 | n1 = edge['node1_pub'] 97 | n2 = edge['node2_pub'] 98 | cap = int(edge['capacity']) 99 | 100 | # track node capacity 101 | # Note that this will not account for channels filtered later 102 | for n in [n1, n2]: 103 | g.nodes[n]['capacity'] += cap 104 | g.nodes[n]['capacities'].append(cap) 105 | g.nodes[n]['num_channels'] += 1 106 | 107 | edgeparams = dict( 108 | channel_id=edge['channel_id'], 109 | chan_point=edge['chan_point'], 110 | last_update=edge['last_update'], 111 | capacity=cap, 112 | node1_pub=n1, 113 | node2_pub=n2, 114 | node1_policy=edge['node1_policy'], 115 | node2_policy=edge['node2_policy'], 116 | ) 117 | 118 | redundant_edges = [] 119 | if g.has_edge(n1, n2): 120 | 121 | # Track the total value between nodes 122 | link_capacity = cap + g.edges[n1, n2]['link_capacity'] 123 | 124 | # Need to decide to overwrite or hide 125 | if g.edges[n1, n2]['capacity'] < cap: 126 | # Old edge is smaller, replace 127 | lesser_edge = g.edges[n1, n2].copy() 128 | redundant_edges = lesser_edge['redundant_edges'] 129 | del lesser_edge['redundant_edges'] 130 | del lesser_edge['link_capacity'] 131 | redundant_edges.append(lesser_edge) 132 | else: 133 | # Old edge is bigger, keep it 134 | g.edges[n1, n2]['redundant_edges'].append(edgeparams) 135 | continue 136 | else: 137 | link_capacity = cap 138 | 139 | 140 | edgeparams['redundant_edges'] = redundant_edges 141 | edgeparams['link_capacity'] = link_capacity 142 | 143 | g.add_edge(n1, n2, **edgeparams) 144 | 145 | if redundant_edges: 146 | assert g.edges[n1, n2]['channel_id'] != redundant_edges[0]['channel_id'] 147 | 148 | return g 149 | 150 | @classmethod 151 | def fromlnd(cls, lndnode: NodeInterface = None, include_unannounced=False): 152 | if lndnode is None: 153 | lndnode = NodeInterface() # use defaults 154 | 155 | graphdata = lndnode.DescribeGraph(include_unannounced=include_unannounced) 156 | 157 | g = cls() 158 | 159 | # Leave weight undefined for now, could be capacity or fee depending on algorithm 160 | for node in graphdata.nodes: 161 | g.add_node(node.pub_key, 162 | pub_key=node.pub_key, 163 | last_update=node.last_update, 164 | alias=node.alias, 165 | color=node.color, 166 | addresses=gRPCadapters.addrs2dict(node.addresses), 167 | capacity=0, # Increment while iterating channels 168 | num_channels=0, # Increment while iterating channels 169 | capacities=[], # For easily creating histograms of channels 170 | ) 171 | 172 | for edge in graphdata.edges: 173 | n1 = edge.node1_pub 174 | n2 = edge.node2_pub 175 | cap = edge.capacity 176 | 177 | # track node capacity 178 | # Note that this will not account for channels filtered later 179 | for n in [n1, n2]: 180 | g.nodes[n]['capacity'] += cap 181 | g.nodes[n]['capacities'].append(cap) 182 | g.nodes[n]['num_channels'] += 1 183 | 184 | 185 | edgeparams = dict( 186 | channel_id=edge.channel_id, 187 | chan_point=edge.chan_point, 188 | last_update=edge.last_update, 189 | capacity=cap, 190 | node1_pub=n1, 191 | node2_pub=n2, 192 | node1_policy=gRPCadapters.nodepolicy2dict(edge.node1_policy), 193 | node2_policy=gRPCadapters.nodepolicy2dict(edge.node2_policy), 194 | ) 195 | 196 | redundant_edges = [] 197 | if g.has_edge(n1, n2): 198 | 199 | # Track the total value between nodes 200 | link_capacity = cap + g.edges[n1, n2]['link_capacity'] 201 | 202 | # Need to decide to overwrite or hide 203 | if g.edges[n1, n2]['capacity'] < cap: 204 | # Old edge is smaller, replace 205 | lesser_edge = g.edges[n1, n2].copy() 206 | redundant_edges = lesser_edge['redundant_edges'] 207 | del lesser_edge['redundant_edges'] 208 | del lesser_edge['link_capacity'] 209 | redundant_edges.append(lesser_edge) 210 | else: 211 | # Old edge is bigger, keep it 212 | g.edges[n1, n2]['redundant_edges'].append(edgeparams) 213 | continue 214 | else: 215 | link_capacity = cap 216 | 217 | edgeparams['redundant_edges'] = redundant_edges 218 | edgeparams['link_capacity'] = cap 219 | 220 | g.add_edge(n1, n2, **edgeparams) 221 | 222 | if redundant_edges: # sanity check for reference leaks 223 | assert g.edges[n1, n2]['channel_id'] != redundant_edges[0]['channel_id'] 224 | 225 | return g 226 | 227 | def channels(self, nodeid): 228 | """Return channels for a node, including redundants""" 229 | 230 | channels = [] 231 | for peerkey in self.adj[mynodekey]: 232 | mainchan = self.edges[peerkey, mynodekey].copy() 233 | redundants = mainchan['redundant_edges'] 234 | del mainchan['redundant_edges'] 235 | channels.append(mainchan) 236 | channels.extend(redundants) 237 | 238 | return channels 239 | 240 | class lnGraphBaseIGraph(lnGraphBase, igGraph): 241 | @property 242 | def nodes(self): 243 | return self.vs 244 | 245 | @property 246 | def channels(self): 247 | return self.es 248 | 249 | @property 250 | def num_nodes(self): 251 | return len(self.nodes) 252 | 253 | @property 254 | def num_channels(self): 255 | # Divide by 2, each channel is represented by 2 directed edges 256 | return len(self.channels)//2 257 | 258 | 259 | class lnGraphV2(lnGraphBaseIGraph): 260 | @staticmethod 261 | def _init_temp_structure(): 262 | # temporary values used during initialization 263 | v = dict( 264 | nodeattrs = {a:[] for a in ['pub_key', 'last_update', 'alias', 'color', 'addresses']}, 265 | edgeattrs = {a:[] for a in ['channel_id', 'chan_point', 'capacity', 'last_update']}, 266 | policies1 = [], 267 | policies2 = [], 268 | edgeids1 = [], 269 | edgeids2 = [], 270 | nodepubs1 = [], 271 | nodepubs2 = [], 272 | ) 273 | 274 | return v 275 | 276 | @staticmethod 277 | def _init_record_channel(init_vars, nodeindexmap, n1pub, n2pub, 278 | chan_cap, policy1, policy2): 279 | # Record the channel to records 280 | init_vars['edgeids1'].append((n1pub,n2pub)) 281 | init_vars['edgeids2'].append((n2pub,n1pub)) 282 | init_vars['nodepubs1'].append(n1pub) 283 | init_vars['nodepubs2'].append(n2pub) 284 | 285 | n1i = nodeindexmap[n1pub] 286 | n2i = nodeindexmap[n2pub] 287 | 288 | init_vars['nodeattrs']['capacity'][n1i] += chan_cap 289 | init_vars['nodeattrs']['capacity'][n2i] += chan_cap 290 | init_vars['nodeattrs']['num_channels'][n1i] += 1 291 | init_vars['nodeattrs']['num_channels'][n2i] += 1 292 | 293 | init_vars['policies1'].append(policy1) 294 | init_vars['policies2'].append(policy2) 295 | 296 | @staticmethod 297 | def _init_setflattenedpolicies(policies:list,direction:str,dest:dict): 298 | policy_keys = ['time_lock_delta', 'min_htlc', 'max_htlc_msat', 299 | 'fee_base_msat', 'fee_rate_milli_msat', 300 | 'disabled', 'last_update'] 301 | 302 | for k in policy_keys: 303 | dest[f'{k}_{direction}'] = [ 304 | None if p is None else p[k] for p in policies 305 | ] 306 | 307 | @classmethod 308 | def _init_setpolicies1(cls, init_vars): 309 | # Set policies from the perspective of node 1 310 | init_vars['edgeattrs']['local_pubkey'] = init_vars['nodepubs1'] 311 | init_vars['edgeattrs']['remote_pubkey'] = init_vars['nodepubs2'] 312 | 313 | cls._init_setflattenedpolicies( 314 | init_vars['policies1'],'out',init_vars['edgeattrs']) 315 | cls._init_setflattenedpolicies( 316 | init_vars['policies2'],'in',init_vars['edgeattrs']) 317 | 318 | @classmethod 319 | def _init_setpolicies2(cls, init_vars): 320 | # Set policies from the perspective of node 2 321 | init_vars['edgeattrs']['local_pubkey'] = init_vars['nodepubs2'] 322 | init_vars['edgeattrs']['remote_pubkey'] = init_vars['nodepubs1'] 323 | 324 | cls._init_setflattenedpolicies( 325 | init_vars['policies2'],'out',init_vars['edgeattrs']) 326 | cls._init_setflattenedpolicies( 327 | init_vars['policies1'],'in',init_vars['edgeattrs']) 328 | 329 | @classmethod 330 | def fromjson(cls, graphfile='describegraph.json'): 331 | with open(graphfile, encoding='utf8') as f: 332 | graphdata = json.load(f) 333 | 334 | # Directed graph will be superior when fee metrics are computed 335 | g = cls(directed=True) 336 | 337 | intattrs = ['capacity', 'last_update', 338 | 'fee_base_msat', 'fee_rate_milli_msat', 339 | 'min_htlc', 'max_htlc_msat'] 340 | 341 | def fixstr2int(data: dict): 342 | if data is None: return None 343 | 344 | data_keys = data.keys() 345 | for k in intattrs: 346 | if k in data_keys: 347 | data[k] = int(data[k]) 348 | 349 | return data 350 | 351 | graph_elements = cls._init_temp_structure() 352 | 353 | for node in graphdata['nodes']: 354 | node = fixstr2int(node) 355 | for k in graph_elements['nodeattrs'].keys(): 356 | graph_elements['nodeattrs'][k].append(node[k]) 357 | 358 | n = len(graph_elements['nodeattrs']['pub_key']) 359 | graph_elements['nodeattrs']['capacity'] = [0]*n # Increment while iterating channels 360 | graph_elements['nodeattrs']['num_channels'] = [0]*n # Increment while iterating channels 361 | 362 | nodeindexmap = {k:i for i, k in 363 | enumerate(graph_elements['nodeattrs']['pub_key'])} 364 | 365 | for edge in graphdata['edges']: 366 | edge = fixstr2int(edge) 367 | n1pub = edge['node1_pub'] 368 | n2pub = edge['node2_pub'] 369 | cap = edge['capacity'] 370 | 371 | for k in graph_elements['edgeattrs'].keys(): 372 | graph_elements['edgeattrs'][k].append(edge[k]) 373 | 374 | cls._init_record_channel(graph_elements, nodeindexmap, 375 | n1pub, n2pub, cap, 376 | fixstr2int(edge['node1_policy']), 377 | fixstr2int(edge['node2_policy']), 378 | ) 379 | 380 | g.add_vertices(graph_elements['nodeattrs']['pub_key'], 381 | graph_elements['nodeattrs']) 382 | 383 | # Using a directed graph, have to add edges twice because channels are bidirectionsl 384 | cls._init_setpolicies1(graph_elements) 385 | 386 | g.add_edges(graph_elements['edgeids1'], graph_elements['edgeattrs']) 387 | 388 | cls._init_setpolicies2(graph_elements) 389 | 390 | g.add_edges(graph_elements['edgeids2'], graph_elements['edgeattrs']) 391 | return g 392 | 393 | @classmethod 394 | def fromlnd(cls, lndnode: NodeInterface = None, include_unannounced=False): 395 | if lndnode is None: 396 | lndnode = NodeInterface() # use defaults 397 | 398 | graphdata = lndnode.DescribeGraph(include_unannounced=include_unannounced) 399 | 400 | g = cls(directed=True) 401 | 402 | graph_elements = cls._init_temp_structure() 403 | 404 | for node in graphdata.nodes: 405 | for k in graph_elements['nodeattrs'].keys(): 406 | a = getattr(node, k) 407 | if k == 'addresses': 408 | a = gRPCadapters.addrs2dict(a) 409 | graph_elements['nodeattrs'][k].append(a) 410 | 411 | n = len(graph_elements['nodeattrs']['pub_key']) # Number of nodes in graph 412 | graph_elements['nodeattrs']['capacity'] = [0]*n # Increment while iterating channels 413 | graph_elements['nodeattrs']['num_channels'] = [0]*n # Increment while iterating channels 414 | 415 | nodeindexmap = {k:i for i, k in 416 | enumerate(graph_elements['nodeattrs']['pub_key'])} 417 | 418 | for edge in graphdata.edges: 419 | n1pub = edge.node1_pub 420 | n2pub = edge.node2_pub 421 | cap = int(edge.capacity) 422 | 423 | for k in graph_elements['edgeattrs'].keys(): 424 | graph_elements['edgeattrs'][k].append(getattr(edge, k)) 425 | 426 | cls._init_record_channel(graph_elements, nodeindexmap, 427 | n1pub,n2pub, cap, 428 | gRPCadapters.nodepolicy2dict(edge.node1_policy), 429 | gRPCadapters.nodepolicy2dict(edge.node2_policy), 430 | ) 431 | 432 | g.add_vertices(graph_elements['nodeattrs']['pub_key'], 433 | graph_elements['nodeattrs']) 434 | 435 | # Using a directed graph, have to add edges twice to keep track of policies 436 | cls._init_setpolicies1(graph_elements) 437 | 438 | g.add_edges(graph_elements['edgeids1'], graph_elements['edgeattrs']) 439 | 440 | cls._init_setpolicies2(graph_elements) 441 | 442 | g.add_edges(graph_elements['edgeids2'], graph_elements['edgeattrs']) 443 | return g 444 | 445 | def simple(self): 446 | """ 447 | Returns a copy without parallel edges, 448 | intelligently consolidates policy information 449 | """ 450 | c = self.copy() 451 | 452 | unknown_in = c.es.select(disabled_in_eq=None) 453 | unknown_out = c.es.select(disabled_out_eq=None) 454 | 455 | pkeys = ['time_lock_delta', 'min_htlc', 'max_htlc_msat', 456 | 'fee_base_msat', 'fee_rate_milli_msat', 457 | 'disabled', 'last_update'] 458 | 459 | combine_edges_method = { 460 | 'capacity':'sum', 461 | 'last_update': 'max', 462 | 'local_pubkey': 'first', 463 | 'remote_pubkey': 'first', 464 | 'channel_id': 'first', 465 | } 466 | for d in ['in','out']: 467 | policies = { 468 | f'time_lock_delta_{d}': 'max', 469 | f'min_htlc_{d}': 'min', 470 | f'max_htlc_msat_{d}': 'max', 471 | f'fee_base_msat_{d}': 'max', 472 | f'fee_rate_milli_msat_{d}': 'max', 473 | f'disabled_{d}': all, 474 | f'last_update_{d}': 'max', 475 | } 476 | combine_edges_method.update(policies) 477 | 478 | # Can't use comparisons when a field is None 479 | for pk in pkeys: 480 | # -1 should be a safe placeholder 481 | unknown_in.set_attribute_values(f'{pk}_in', -1) 482 | unknown_out.set_attribute_values(f'{pk}_out', -1) 483 | 484 | s = c.simplify(combine_edges=combine_edges_method) 485 | 486 | # Reset unknown fields to None 487 | unknown_in = s.es.select(time_lock_delta_in_eq=-1) 488 | unknown_out = s.es.select(time_lock_delta_out_eq=-1) 489 | for pk in pkeys: 490 | unknown_in.set_attribute_values(f'{pk}_in', None) 491 | unknown_out.set_attribute_values(f'{pk}_out', None) 492 | 493 | return s 494 | 495 | def simple_nopolicy(self): 496 | """Faster than .simple(), discards policy info""" 497 | combine_edges_method = { 498 | 'capacity': 'sum', 499 | 'last_update': 'max', 500 | 'local_pubkey': 'first', 501 | 'remote_pubkey': 'first', 502 | 'channel_id': 'first', # Keep, because useful as an index 503 | } 504 | return self.copy().simplify(combine_edges=combine_edges_method) 505 | 506 | def simple_nopolicy_undirected(self): 507 | """Faster than .simple(), discards policy info""" 508 | combine_edges_method = { 509 | 'capacity': 'sum', 510 | 'last_update': 'max', 511 | 'channel_id': 'first', 512 | } 513 | g = self.copy() 514 | # TODO: This will double channel capacity!! 515 | g.to_undirected(mode='collapse', combine_edges=combine_edges_method) 516 | return g 517 | 518 | if __name__ == '__main__': 519 | print('Loading graph') 520 | g = lnGraphV2.autoload() 521 | # ~ g = lnGraphV2.fromlnd(lndnode=NodeInterface.fromconfig()) 522 | print(g.summary()) 523 | # find() grabs the first match, for testing 524 | n = g.nodes.find(alias='Gridflare') 525 | print(n) 526 | print(g.channels.find(_from=n)) 527 | -------------------------------------------------------------------------------- /lib/nodeinterface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is a wrapper around LND's gRPC interface 4 | 5 | It is incomplete and largely untested, read-only usage is highly recommended 6 | 7 | """ 8 | 9 | import os 10 | import configparser 11 | from functools import lru_cache 12 | import codecs 13 | 14 | import grpc 15 | 16 | from lnrpc_generated import lightning_pb2 as ln, lightning_pb2_grpc as lnrpc 17 | from lnrpc_generated.walletrpc import walletkit_pb2 as walletrpc, walletkit_pb2_grpc as walletkitstub 18 | from lnrpc_generated.routerrpc import router_pb2 as routerrpc,router_pb2_grpc as routerstub 19 | 20 | 21 | MSGMAXMB = 50 * 1024 * 1024 22 | LNDDIR = os.path.expanduser('~/.lnd') 23 | 24 | class BaseInterface: 25 | """A class that tries to intelligently call functions from an LND service 26 | This introspection does not work in many cases. 27 | """ 28 | 29 | def __getattr__(self, cmd): 30 | """ 31 | Some magic for undefined functions, QOL hack 32 | """ 33 | 34 | if hasattr(self._rpc, cmd+'Request'): 35 | lnfunc = getattr(self._rpc, cmd+'Request') 36 | elif hasattr(self._rpc, f'Get{cmd}Request'): 37 | lnfunc = getattr(self._rpc, f'Get{cmd}Request') 38 | else: 39 | raise NotImplementedError('Unhandled method self._rpc.(Get)' + cmd + 'Request') 40 | 41 | if hasattr(self._stub, cmd): 42 | stubfunc = getattr(self._stub, cmd) 43 | 44 | def rpcCommand(*args,**kwargs): 45 | return stubfunc(lnfunc(*args, **kwargs)) 46 | return rpcCommand 47 | 48 | elif hasattr(self._stub, 'Get'+cmd): 49 | stubfunc = getattr(self._stub, 'Get'+cmd) 50 | def rpcCommand(*args,**kwargs): 51 | if args: 52 | raise TypeError('Cannot use positional arguments with this command') 53 | return stubfunc(lnfunc(**kwargs)) 54 | return rpcCommand 55 | 56 | else: 57 | raise NotImplementedError('Unhandled method stub.(Get)' + cmd) 58 | 59 | 60 | class SubserverRPC(BaseInterface): 61 | """ 62 | Generic class for subservers, may need to be extended in the future 63 | """ 64 | def __init__(self, subRPC, substub): 65 | self._rpc = subRPC 66 | self._stub = substub 67 | 68 | class MinimalNodeInterface(BaseInterface): 69 | """A class implementing the bare minimum to communicate with LND over RPC""" 70 | 71 | def __init__(self, server=None, tlspath=None, macpath=None, cachedir='_cache'): 72 | 73 | if server is None: server = 'localhost:10009' 74 | if tlspath is None: tlspath = LNDDIR + '/tls.cert' 75 | if macpath is None: macpath = LNDDIR + '/data/chain/bitcoin/mainnet/admin.macaroon' 76 | 77 | assert os.path.isfile(tlspath), tlspath + ' does not exist!' 78 | assert os.path.isfile(macpath), macpath + ' does not exist!' 79 | assert tlspath.endswith(('.cert','.crt')) 80 | assert macpath.endswith('.macaroon') 81 | 82 | tlsCert = open(tlspath, 'rb').read() 83 | sslCred = grpc.ssl_channel_credentials(tlsCert) 84 | macaroon = codecs.encode(open(macpath, 'rb').read(), 'hex') 85 | authCred = grpc.metadata_call_credentials( 86 | lambda _, callback: callback( 87 | [('macaroon', macaroon)], None)) 88 | masterCred = grpc.composite_channel_credentials(sslCred, authCred) 89 | 90 | os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA' 91 | 92 | options = [ 93 | ('grpc.max_message_length', MSGMAXMB), 94 | ('grpc.max_receive_message_length', MSGMAXMB) 95 | ] 96 | 97 | grpc_channel = grpc.secure_channel(server, masterCred, options) 98 | 99 | self._stub = lnrpc.LightningStub(grpc_channel) 100 | self._rpc = ln 101 | 102 | self.wallet = SubserverRPC(walletrpc, walletkitstub.WalletKitStub(grpc_channel)) 103 | self.router = SubserverRPC(routerrpc, routerstub.RouterStub(grpc_channel)) 104 | 105 | if not os.path.exists(cachedir): 106 | os.mkdir(cachedir) 107 | 108 | self.cachedir = cachedir 109 | 110 | @classmethod 111 | def fromconfig(cls, conffile='node.conf', nodename='Node1'): 112 | if not os.path.isfile(conffile): 113 | print('Config for lnd not found, will create', conffile) 114 | config = configparser.ConfigParser() 115 | config[nodename] = {'server': 'localhost:10009', 116 | 'macpath': LNDDIR + '/data/chain/bitcoin/mainnet/readonly.macaroon', 117 | 'tlspath': LNDDIR + '/tls.cert', 118 | } 119 | 120 | with open(conffile, 'w') as cf: 121 | config.write(cf) 122 | 123 | print('Please check/complete the config and rerun') 124 | print('If running remotely you will need a local copy of your macaroon and tls.cert') 125 | print('Using readonly.macaroon is recommended unless you know what you are doing.') 126 | exit() 127 | 128 | else: 129 | config = configparser.ConfigParser() 130 | config.read(conffile) 131 | return cls(**config[nodename]) 132 | 133 | 134 | class BasicNodeInterface(MinimalNodeInterface): 135 | """A subclass of MinimalNodeInterface implementing missing methods""" 136 | 137 | def UpdateChannelPolicy(self, **kwargs): 138 | if 'chan_point' in kwargs and isinstance(kwargs['chan_point'], str): 139 | cp = kwargs['chan_point'] 140 | kwargs['chan_point'] = ln.ChannelPoint( 141 | funding_txid_str=cp.split(':')[0], 142 | output_index=int(cp.split(':')[1]) 143 | ) 144 | 145 | return self._stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(**kwargs)) 146 | 147 | def DescribeGraph(self, include_unannounced=True): 148 | return self._stub.DescribeGraph( 149 | ln.ChannelGraphRequest(include_unannounced=include_unannounced)) 150 | 151 | def getForwardingHistory(self, starttime): 152 | """Same as the bare metal method, but this one pages automatically""" 153 | fwdhist = [] 154 | offset = 0 155 | 156 | def getfwdbatch(starttime, pageOffset): 157 | fwdbatch = self.ForwardingHistory( 158 | start_time=starttime, 159 | index_offset=pageOffset, 160 | ).forwarding_events 161 | return fwdbatch 162 | 163 | # requires Python 3.8+ 164 | # ~ while (fwdbatch := getfwdbatch(starttime, offset)): 165 | # ~ offset += len(fwdbatch) 166 | # ~ fwdhist.extend(fwdbatch) 167 | 168 | fwdbatch = getfwdbatch(starttime, offset) 169 | while fwdbatch: 170 | offset += len(fwdbatch) 171 | fwdhist.extend(fwdbatch) 172 | fwdbatch = getfwdbatch(starttime, offset) 173 | 174 | return fwdhist 175 | 176 | def ListInvoices(self, **kwargs): 177 | """Wrapper required due to inconsistent naming""" 178 | return self._stub.ListInvoices(ln.ListInvoiceRequest(**kwargs)) 179 | 180 | 181 | class AdvancedNodeInterface(BasicNodeInterface): 182 | """Class implementing recombinant methods not directly available from LND""" 183 | 184 | def __init__(self, *args, **kwargs): 185 | super().__init__(*args,**kwargs) 186 | 187 | @lru_cache(maxsize=256) 188 | def getAlias(self, pubkey=None): 189 | if pubkey is None: 190 | return self.GetInfo().alias 191 | else: 192 | return self._stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=pubkey)).node.alias 193 | 194 | def getNeighboursInfo(self,pubkey=None): 195 | """Return more useful info about our, or another node's, neighbours""" 196 | return list(map(self.GetNodeInfo, self.getNeighboursPubkeys(pubkey))) 197 | 198 | 199 | class NodeInterface(AdvancedNodeInterface): 200 | """ 201 | A class streamlining the LND RPC interface 202 | Alias for AdvancedNodeInterface 203 | Methods that are forwarded directly to LND are capitalised e.g. GetInfo() 204 | Methods that process the data in any way use lowerCamelCase e.g. getAlias() 205 | """ 206 | 207 | if __name__ == '__main__': 208 | # Testing connectivity 209 | ni = NodeInterface.fromconfig() 210 | print('Connected to node', ni.getAlias()) 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /lnrpc_generated/routerrpc/router_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: routerrpc/router.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import enum_type_wrapper 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | from lnrpc_generated import lightning_pb2 as lightning__pb2 17 | 18 | 19 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16routerrpc/router.proto\x12\trouterrpc\x1a\x0flightning.proto\"\xa4\x05\n\x12SendPaymentRequest\x12\x0c\n\x04\x64\x65st\x18\x01 \x01(\x0c\x12\x0b\n\x03\x61mt\x18\x02 \x01(\x03\x12\x10\n\x08\x61mt_msat\x18\x0c \x01(\x03\x12\x14\n\x0cpayment_hash\x18\x03 \x01(\x0c\x12\x18\n\x10\x66inal_cltv_delta\x18\x04 \x01(\x05\x12\x14\n\x0cpayment_addr\x18\x14 \x01(\x0c\x12\x17\n\x0fpayment_request\x18\x05 \x01(\t\x12\x17\n\x0ftimeout_seconds\x18\x06 \x01(\x05\x12\x15\n\rfee_limit_sat\x18\x07 \x01(\x03\x12\x16\n\x0e\x66\x65\x65_limit_msat\x18\r \x01(\x03\x12\x1e\n\x10outgoing_chan_id\x18\x08 \x01(\x04\x42\x04\x18\x01\x30\x01\x12\x19\n\x11outgoing_chan_ids\x18\x13 \x03(\x04\x12\x17\n\x0flast_hop_pubkey\x18\x0e \x01(\x0c\x12\x12\n\ncltv_limit\x18\t \x01(\x05\x12%\n\x0broute_hints\x18\n \x03(\x0b\x32\x10.lnrpc.RouteHint\x12Q\n\x13\x64\x65st_custom_records\x18\x0b \x03(\x0b\x32\x34.routerrpc.SendPaymentRequest.DestCustomRecordsEntry\x12\x1a\n\x12\x61llow_self_payment\x18\x0f \x01(\x08\x12(\n\rdest_features\x18\x10 \x03(\x0e\x32\x11.lnrpc.FeatureBit\x12\x11\n\tmax_parts\x18\x11 \x01(\r\x12\x1b\n\x13no_inflight_updates\x18\x12 \x01(\x08\x12\x1b\n\x13max_shard_size_msat\x18\x15 \x01(\x04\x12\x0b\n\x03\x61mp\x18\x16 \x01(\x08\x1a\x38\n\x16\x44\x65stCustomRecordsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"H\n\x13TrackPaymentRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x1b\n\x13no_inflight_updates\x18\x02 \x01(\x08\"0\n\x0fRouteFeeRequest\x12\x0c\n\x04\x64\x65st\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61mt_sat\x18\x02 \x01(\x03\"E\n\x10RouteFeeResponse\x12\x18\n\x10routing_fee_msat\x18\x01 \x01(\x03\x12\x17\n\x0ftime_lock_delay\x18\x02 \x01(\x03\"G\n\x12SendToRouteRequest\x12\x14\n\x0cpayment_hash\x18\x01 \x01(\x0c\x12\x1b\n\x05route\x18\x02 \x01(\x0b\x32\x0c.lnrpc.Route\"H\n\x13SendToRouteResponse\x12\x10\n\x08preimage\x18\x01 \x01(\x0c\x12\x1f\n\x07\x66\x61ilure\x18\x02 \x01(\x0b\x32\x0e.lnrpc.Failure\"\x1c\n\x1aResetMissionControlRequest\"\x1d\n\x1bResetMissionControlResponse\"\x1c\n\x1aQueryMissionControlRequest\"J\n\x1bQueryMissionControlResponse\x12%\n\x05pairs\x18\x02 \x03(\x0b\x32\x16.routerrpc.PairHistoryJ\x04\x08\x01\x10\x02\"T\n\x1cXImportMissionControlRequest\x12%\n\x05pairs\x18\x01 \x03(\x0b\x32\x16.routerrpc.PairHistory\x12\r\n\x05\x66orce\x18\x02 \x01(\x08\"\x1f\n\x1dXImportMissionControlResponse\"o\n\x0bPairHistory\x12\x11\n\tnode_from\x18\x01 \x01(\x0c\x12\x0f\n\x07node_to\x18\x02 \x01(\x0c\x12$\n\x07history\x18\x07 \x01(\x0b\x32\x13.routerrpc.PairDataJ\x04\x08\x03\x10\x04J\x04\x08\x04\x10\x05J\x04\x08\x05\x10\x06J\x04\x08\x06\x10\x07\"\x99\x01\n\x08PairData\x12\x11\n\tfail_time\x18\x01 \x01(\x03\x12\x14\n\x0c\x66\x61il_amt_sat\x18\x02 \x01(\x03\x12\x15\n\rfail_amt_msat\x18\x04 \x01(\x03\x12\x14\n\x0csuccess_time\x18\x05 \x01(\x03\x12\x17\n\x0fsuccess_amt_sat\x18\x06 \x01(\x03\x12\x18\n\x10success_amt_msat\x18\x07 \x01(\x03J\x04\x08\x03\x10\x04\" \n\x1eGetMissionControlConfigRequest\"R\n\x1fGetMissionControlConfigResponse\x12/\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1f.routerrpc.MissionControlConfig\"Q\n\x1eSetMissionControlConfigRequest\x12/\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1f.routerrpc.MissionControlConfig\"!\n\x1fSetMissionControlConfigResponse\"\xa3\x01\n\x14MissionControlConfig\x12\x19\n\x11half_life_seconds\x18\x01 \x01(\x04\x12\x17\n\x0fhop_probability\x18\x02 \x01(\x02\x12\x0e\n\x06weight\x18\x03 \x01(\x02\x12\x1f\n\x17maximum_payment_results\x18\x04 \x01(\r\x12&\n\x1eminimum_failure_relax_interval\x18\x05 \x01(\x04\"O\n\x17QueryProbabilityRequest\x12\x11\n\tfrom_node\x18\x01 \x01(\x0c\x12\x0f\n\x07to_node\x18\x02 \x01(\x0c\x12\x10\n\x08\x61mt_msat\x18\x03 \x01(\x03\"U\n\x18QueryProbabilityResponse\x12\x13\n\x0bprobability\x18\x01 \x01(\x01\x12$\n\x07history\x18\x02 \x01(\x0b\x32\x13.routerrpc.PairData\"\x88\x01\n\x11\x42uildRouteRequest\x12\x10\n\x08\x61mt_msat\x18\x01 \x01(\x03\x12\x18\n\x10\x66inal_cltv_delta\x18\x02 \x01(\x05\x12\x1c\n\x10outgoing_chan_id\x18\x03 \x01(\x04\x42\x02\x30\x01\x12\x13\n\x0bhop_pubkeys\x18\x04 \x03(\x0c\x12\x14\n\x0cpayment_addr\x18\x05 \x01(\x0c\"1\n\x12\x42uildRouteResponse\x12\x1b\n\x05route\x18\x01 \x01(\x0b\x32\x0c.lnrpc.Route\"\x1c\n\x1aSubscribeHtlcEventsRequest\"\xdc\x03\n\tHtlcEvent\x12\x1b\n\x13incoming_channel_id\x18\x01 \x01(\x04\x12\x1b\n\x13outgoing_channel_id\x18\x02 \x01(\x04\x12\x18\n\x10incoming_htlc_id\x18\x03 \x01(\x04\x12\x18\n\x10outgoing_htlc_id\x18\x04 \x01(\x04\x12\x14\n\x0ctimestamp_ns\x18\x05 \x01(\x04\x12\x32\n\nevent_type\x18\x06 \x01(\x0e\x32\x1e.routerrpc.HtlcEvent.EventType\x12\x30\n\rforward_event\x18\x07 \x01(\x0b\x32\x17.routerrpc.ForwardEventH\x00\x12\x39\n\x12\x66orward_fail_event\x18\x08 \x01(\x0b\x32\x1b.routerrpc.ForwardFailEventH\x00\x12.\n\x0csettle_event\x18\t \x01(\x0b\x32\x16.routerrpc.SettleEventH\x00\x12\x33\n\x0flink_fail_event\x18\n \x01(\x0b\x32\x18.routerrpc.LinkFailEventH\x00\"<\n\tEventType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x08\n\x04SEND\x10\x01\x12\x0b\n\x07RECEIVE\x10\x02\x12\x0b\n\x07\x46ORWARD\x10\x03\x42\x07\n\x05\x65vent\"v\n\x08HtlcInfo\x12\x19\n\x11incoming_timelock\x18\x01 \x01(\r\x12\x19\n\x11outgoing_timelock\x18\x02 \x01(\r\x12\x19\n\x11incoming_amt_msat\x18\x03 \x01(\x04\x12\x19\n\x11outgoing_amt_msat\x18\x04 \x01(\x04\"1\n\x0c\x46orwardEvent\x12!\n\x04info\x18\x01 \x01(\x0b\x32\x13.routerrpc.HtlcInfo\"\x12\n\x10\x46orwardFailEvent\"\x1f\n\x0bSettleEvent\x12\x10\n\x08preimage\x18\x01 \x01(\x0c\"\xae\x01\n\rLinkFailEvent\x12!\n\x04info\x18\x01 \x01(\x0b\x32\x13.routerrpc.HtlcInfo\x12\x30\n\x0cwire_failure\x18\x02 \x01(\x0e\x32\x1a.lnrpc.Failure.FailureCode\x12\x30\n\x0e\x66\x61ilure_detail\x18\x03 \x01(\x0e\x32\x18.routerrpc.FailureDetail\x12\x16\n\x0e\x66\x61ilure_string\x18\x04 \x01(\t\"r\n\rPaymentStatus\x12&\n\x05state\x18\x01 \x01(\x0e\x32\x17.routerrpc.PaymentState\x12\x10\n\x08preimage\x18\x02 \x01(\x0c\x12!\n\x05htlcs\x18\x04 \x03(\x0b\x32\x12.lnrpc.HTLCAttemptJ\x04\x08\x03\x10\x04\".\n\nCircuitKey\x12\x0f\n\x07\x63han_id\x18\x01 \x01(\x04\x12\x0f\n\x07htlc_id\x18\x02 \x01(\x04\"\x97\x03\n\x1b\x46orwardHtlcInterceptRequest\x12\x33\n\x14incoming_circuit_key\x18\x01 \x01(\x0b\x32\x15.routerrpc.CircuitKey\x12\x1c\n\x14incoming_amount_msat\x18\x05 \x01(\x04\x12\x17\n\x0fincoming_expiry\x18\x06 \x01(\r\x12\x14\n\x0cpayment_hash\x18\x02 \x01(\x0c\x12\"\n\x1aoutgoing_requested_chan_id\x18\x07 \x01(\x04\x12\x1c\n\x14outgoing_amount_msat\x18\x03 \x01(\x04\x12\x17\n\x0foutgoing_expiry\x18\x04 \x01(\r\x12Q\n\x0e\x63ustom_records\x18\x08 \x03(\x0b\x32\x39.routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry\x12\x12\n\nonion_blob\x18\t \x01(\x0c\x1a\x34\n\x12\x43ustomRecordsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12\r\n\x05value\x18\x02 \x01(\x0c:\x02\x38\x01\"\xe5\x01\n\x1c\x46orwardHtlcInterceptResponse\x12\x33\n\x14incoming_circuit_key\x18\x01 \x01(\x0b\x32\x15.routerrpc.CircuitKey\x12\x33\n\x06\x61\x63tion\x18\x02 \x01(\x0e\x32#.routerrpc.ResolveHoldForwardAction\x12\x10\n\x08preimage\x18\x03 \x01(\x0c\x12\x17\n\x0f\x66\x61ilure_message\x18\x04 \x01(\x0c\x12\x30\n\x0c\x66\x61ilure_code\x18\x05 \x01(\x0e\x32\x1a.lnrpc.Failure.FailureCode\"o\n\x17UpdateChanStatusRequest\x12\'\n\nchan_point\x18\x01 \x01(\x0b\x32\x13.lnrpc.ChannelPoint\x12+\n\x06\x61\x63tion\x18\x02 \x01(\x0e\x32\x1b.routerrpc.ChanStatusAction\"\x1a\n\x18UpdateChanStatusResponse*\x81\x04\n\rFailureDetail\x12\x0b\n\x07UNKNOWN\x10\x00\x12\r\n\tNO_DETAIL\x10\x01\x12\x10\n\x0cONION_DECODE\x10\x02\x12\x15\n\x11LINK_NOT_ELIGIBLE\x10\x03\x12\x14\n\x10ON_CHAIN_TIMEOUT\x10\x04\x12\x14\n\x10HTLC_EXCEEDS_MAX\x10\x05\x12\x18\n\x14INSUFFICIENT_BALANCE\x10\x06\x12\x16\n\x12INCOMPLETE_FORWARD\x10\x07\x12\x13\n\x0fHTLC_ADD_FAILED\x10\x08\x12\x15\n\x11\x46ORWARDS_DISABLED\x10\t\x12\x14\n\x10INVOICE_CANCELED\x10\n\x12\x15\n\x11INVOICE_UNDERPAID\x10\x0b\x12\x1b\n\x17INVOICE_EXPIRY_TOO_SOON\x10\x0c\x12\x14\n\x10INVOICE_NOT_OPEN\x10\r\x12\x17\n\x13MPP_INVOICE_TIMEOUT\x10\x0e\x12\x14\n\x10\x41\x44\x44RESS_MISMATCH\x10\x0f\x12\x16\n\x12SET_TOTAL_MISMATCH\x10\x10\x12\x15\n\x11SET_TOTAL_TOO_LOW\x10\x11\x12\x10\n\x0cSET_OVERPAID\x10\x12\x12\x13\n\x0fUNKNOWN_INVOICE\x10\x13\x12\x13\n\x0fINVALID_KEYSEND\x10\x14\x12\x13\n\x0fMPP_IN_PROGRESS\x10\x15\x12\x12\n\x0e\x43IRCULAR_ROUTE\x10\x16*\xae\x01\n\x0cPaymentState\x12\r\n\tIN_FLIGHT\x10\x00\x12\r\n\tSUCCEEDED\x10\x01\x12\x12\n\x0e\x46\x41ILED_TIMEOUT\x10\x02\x12\x13\n\x0f\x46\x41ILED_NO_ROUTE\x10\x03\x12\x10\n\x0c\x46\x41ILED_ERROR\x10\x04\x12$\n FAILED_INCORRECT_PAYMENT_DETAILS\x10\x05\x12\x1f\n\x1b\x46\x41ILED_INSUFFICIENT_BALANCE\x10\x06*<\n\x18ResolveHoldForwardAction\x12\n\n\x06SETTLE\x10\x00\x12\x08\n\x04\x46\x41IL\x10\x01\x12\n\n\x06RESUME\x10\x02*5\n\x10\x43hanStatusAction\x12\n\n\x06\x45NABLE\x10\x00\x12\x0b\n\x07\x44ISABLE\x10\x01\x12\x08\n\x04\x41UTO\x10\x02\x32\xf1\x0b\n\x06Router\x12@\n\rSendPaymentV2\x12\x1d.routerrpc.SendPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12\x42\n\x0eTrackPaymentV2\x12\x1e.routerrpc.TrackPaymentRequest\x1a\x0e.lnrpc.Payment0\x01\x12K\n\x10\x45stimateRouteFee\x12\x1a.routerrpc.RouteFeeRequest\x1a\x1b.routerrpc.RouteFeeResponse\x12Q\n\x0bSendToRoute\x12\x1d.routerrpc.SendToRouteRequest\x1a\x1e.routerrpc.SendToRouteResponse\"\x03\x88\x02\x01\x12\x42\n\rSendToRouteV2\x12\x1d.routerrpc.SendToRouteRequest\x1a\x12.lnrpc.HTLCAttempt\x12\x64\n\x13ResetMissionControl\x12%.routerrpc.ResetMissionControlRequest\x1a&.routerrpc.ResetMissionControlResponse\x12\x64\n\x13QueryMissionControl\x12%.routerrpc.QueryMissionControlRequest\x1a&.routerrpc.QueryMissionControlResponse\x12j\n\x15XImportMissionControl\x12\'.routerrpc.XImportMissionControlRequest\x1a(.routerrpc.XImportMissionControlResponse\x12p\n\x17GetMissionControlConfig\x12).routerrpc.GetMissionControlConfigRequest\x1a*.routerrpc.GetMissionControlConfigResponse\x12p\n\x17SetMissionControlConfig\x12).routerrpc.SetMissionControlConfigRequest\x1a*.routerrpc.SetMissionControlConfigResponse\x12[\n\x10QueryProbability\x12\".routerrpc.QueryProbabilityRequest\x1a#.routerrpc.QueryProbabilityResponse\x12I\n\nBuildRoute\x12\x1c.routerrpc.BuildRouteRequest\x1a\x1d.routerrpc.BuildRouteResponse\x12T\n\x13SubscribeHtlcEvents\x12%.routerrpc.SubscribeHtlcEventsRequest\x1a\x14.routerrpc.HtlcEvent0\x01\x12M\n\x0bSendPayment\x12\x1d.routerrpc.SendPaymentRequest\x1a\x18.routerrpc.PaymentStatus\"\x03\x88\x02\x01\x30\x01\x12O\n\x0cTrackPayment\x12\x1e.routerrpc.TrackPaymentRequest\x1a\x18.routerrpc.PaymentStatus\"\x03\x88\x02\x01\x30\x01\x12\x66\n\x0fHtlcInterceptor\x12\'.routerrpc.ForwardHtlcInterceptResponse\x1a&.routerrpc.ForwardHtlcInterceptRequest(\x01\x30\x01\x12[\n\x10UpdateChanStatus\x12\".routerrpc.UpdateChanStatusRequest\x1a#.routerrpc.UpdateChanStatusResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/routerrpcb\x06proto3') 20 | 21 | _FAILUREDETAIL = DESCRIPTOR.enum_types_by_name['FailureDetail'] 22 | FailureDetail = enum_type_wrapper.EnumTypeWrapper(_FAILUREDETAIL) 23 | _PAYMENTSTATE = DESCRIPTOR.enum_types_by_name['PaymentState'] 24 | PaymentState = enum_type_wrapper.EnumTypeWrapper(_PAYMENTSTATE) 25 | _RESOLVEHOLDFORWARDACTION = DESCRIPTOR.enum_types_by_name['ResolveHoldForwardAction'] 26 | ResolveHoldForwardAction = enum_type_wrapper.EnumTypeWrapper(_RESOLVEHOLDFORWARDACTION) 27 | _CHANSTATUSACTION = DESCRIPTOR.enum_types_by_name['ChanStatusAction'] 28 | ChanStatusAction = enum_type_wrapper.EnumTypeWrapper(_CHANSTATUSACTION) 29 | UNKNOWN = 0 30 | NO_DETAIL = 1 31 | ONION_DECODE = 2 32 | LINK_NOT_ELIGIBLE = 3 33 | ON_CHAIN_TIMEOUT = 4 34 | HTLC_EXCEEDS_MAX = 5 35 | INSUFFICIENT_BALANCE = 6 36 | INCOMPLETE_FORWARD = 7 37 | HTLC_ADD_FAILED = 8 38 | FORWARDS_DISABLED = 9 39 | INVOICE_CANCELED = 10 40 | INVOICE_UNDERPAID = 11 41 | INVOICE_EXPIRY_TOO_SOON = 12 42 | INVOICE_NOT_OPEN = 13 43 | MPP_INVOICE_TIMEOUT = 14 44 | ADDRESS_MISMATCH = 15 45 | SET_TOTAL_MISMATCH = 16 46 | SET_TOTAL_TOO_LOW = 17 47 | SET_OVERPAID = 18 48 | UNKNOWN_INVOICE = 19 49 | INVALID_KEYSEND = 20 50 | MPP_IN_PROGRESS = 21 51 | CIRCULAR_ROUTE = 22 52 | IN_FLIGHT = 0 53 | SUCCEEDED = 1 54 | FAILED_TIMEOUT = 2 55 | FAILED_NO_ROUTE = 3 56 | FAILED_ERROR = 4 57 | FAILED_INCORRECT_PAYMENT_DETAILS = 5 58 | FAILED_INSUFFICIENT_BALANCE = 6 59 | SETTLE = 0 60 | FAIL = 1 61 | RESUME = 2 62 | ENABLE = 0 63 | DISABLE = 1 64 | AUTO = 2 65 | 66 | 67 | _SENDPAYMENTREQUEST = DESCRIPTOR.message_types_by_name['SendPaymentRequest'] 68 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY = _SENDPAYMENTREQUEST.nested_types_by_name['DestCustomRecordsEntry'] 69 | _TRACKPAYMENTREQUEST = DESCRIPTOR.message_types_by_name['TrackPaymentRequest'] 70 | _ROUTEFEEREQUEST = DESCRIPTOR.message_types_by_name['RouteFeeRequest'] 71 | _ROUTEFEERESPONSE = DESCRIPTOR.message_types_by_name['RouteFeeResponse'] 72 | _SENDTOROUTEREQUEST = DESCRIPTOR.message_types_by_name['SendToRouteRequest'] 73 | _SENDTOROUTERESPONSE = DESCRIPTOR.message_types_by_name['SendToRouteResponse'] 74 | _RESETMISSIONCONTROLREQUEST = DESCRIPTOR.message_types_by_name['ResetMissionControlRequest'] 75 | _RESETMISSIONCONTROLRESPONSE = DESCRIPTOR.message_types_by_name['ResetMissionControlResponse'] 76 | _QUERYMISSIONCONTROLREQUEST = DESCRIPTOR.message_types_by_name['QueryMissionControlRequest'] 77 | _QUERYMISSIONCONTROLRESPONSE = DESCRIPTOR.message_types_by_name['QueryMissionControlResponse'] 78 | _XIMPORTMISSIONCONTROLREQUEST = DESCRIPTOR.message_types_by_name['XImportMissionControlRequest'] 79 | _XIMPORTMISSIONCONTROLRESPONSE = DESCRIPTOR.message_types_by_name['XImportMissionControlResponse'] 80 | _PAIRHISTORY = DESCRIPTOR.message_types_by_name['PairHistory'] 81 | _PAIRDATA = DESCRIPTOR.message_types_by_name['PairData'] 82 | _GETMISSIONCONTROLCONFIGREQUEST = DESCRIPTOR.message_types_by_name['GetMissionControlConfigRequest'] 83 | _GETMISSIONCONTROLCONFIGRESPONSE = DESCRIPTOR.message_types_by_name['GetMissionControlConfigResponse'] 84 | _SETMISSIONCONTROLCONFIGREQUEST = DESCRIPTOR.message_types_by_name['SetMissionControlConfigRequest'] 85 | _SETMISSIONCONTROLCONFIGRESPONSE = DESCRIPTOR.message_types_by_name['SetMissionControlConfigResponse'] 86 | _MISSIONCONTROLCONFIG = DESCRIPTOR.message_types_by_name['MissionControlConfig'] 87 | _QUERYPROBABILITYREQUEST = DESCRIPTOR.message_types_by_name['QueryProbabilityRequest'] 88 | _QUERYPROBABILITYRESPONSE = DESCRIPTOR.message_types_by_name['QueryProbabilityResponse'] 89 | _BUILDROUTEREQUEST = DESCRIPTOR.message_types_by_name['BuildRouteRequest'] 90 | _BUILDROUTERESPONSE = DESCRIPTOR.message_types_by_name['BuildRouteResponse'] 91 | _SUBSCRIBEHTLCEVENTSREQUEST = DESCRIPTOR.message_types_by_name['SubscribeHtlcEventsRequest'] 92 | _HTLCEVENT = DESCRIPTOR.message_types_by_name['HtlcEvent'] 93 | _HTLCINFO = DESCRIPTOR.message_types_by_name['HtlcInfo'] 94 | _FORWARDEVENT = DESCRIPTOR.message_types_by_name['ForwardEvent'] 95 | _FORWARDFAILEVENT = DESCRIPTOR.message_types_by_name['ForwardFailEvent'] 96 | _SETTLEEVENT = DESCRIPTOR.message_types_by_name['SettleEvent'] 97 | _LINKFAILEVENT = DESCRIPTOR.message_types_by_name['LinkFailEvent'] 98 | _PAYMENTSTATUS = DESCRIPTOR.message_types_by_name['PaymentStatus'] 99 | _CIRCUITKEY = DESCRIPTOR.message_types_by_name['CircuitKey'] 100 | _FORWARDHTLCINTERCEPTREQUEST = DESCRIPTOR.message_types_by_name['ForwardHtlcInterceptRequest'] 101 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY = _FORWARDHTLCINTERCEPTREQUEST.nested_types_by_name['CustomRecordsEntry'] 102 | _FORWARDHTLCINTERCEPTRESPONSE = DESCRIPTOR.message_types_by_name['ForwardHtlcInterceptResponse'] 103 | _UPDATECHANSTATUSREQUEST = DESCRIPTOR.message_types_by_name['UpdateChanStatusRequest'] 104 | _UPDATECHANSTATUSRESPONSE = DESCRIPTOR.message_types_by_name['UpdateChanStatusResponse'] 105 | _HTLCEVENT_EVENTTYPE = _HTLCEVENT.enum_types_by_name['EventType'] 106 | SendPaymentRequest = _reflection.GeneratedProtocolMessageType('SendPaymentRequest', (_message.Message,), { 107 | 108 | 'DestCustomRecordsEntry' : _reflection.GeneratedProtocolMessageType('DestCustomRecordsEntry', (_message.Message,), { 109 | 'DESCRIPTOR' : _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY, 110 | '__module__' : 'routerrpc.router_pb2' 111 | # @@protoc_insertion_point(class_scope:routerrpc.SendPaymentRequest.DestCustomRecordsEntry) 112 | }) 113 | , 114 | 'DESCRIPTOR' : _SENDPAYMENTREQUEST, 115 | '__module__' : 'routerrpc.router_pb2' 116 | # @@protoc_insertion_point(class_scope:routerrpc.SendPaymentRequest) 117 | }) 118 | _sym_db.RegisterMessage(SendPaymentRequest) 119 | _sym_db.RegisterMessage(SendPaymentRequest.DestCustomRecordsEntry) 120 | 121 | TrackPaymentRequest = _reflection.GeneratedProtocolMessageType('TrackPaymentRequest', (_message.Message,), { 122 | 'DESCRIPTOR' : _TRACKPAYMENTREQUEST, 123 | '__module__' : 'routerrpc.router_pb2' 124 | # @@protoc_insertion_point(class_scope:routerrpc.TrackPaymentRequest) 125 | }) 126 | _sym_db.RegisterMessage(TrackPaymentRequest) 127 | 128 | RouteFeeRequest = _reflection.GeneratedProtocolMessageType('RouteFeeRequest', (_message.Message,), { 129 | 'DESCRIPTOR' : _ROUTEFEEREQUEST, 130 | '__module__' : 'routerrpc.router_pb2' 131 | # @@protoc_insertion_point(class_scope:routerrpc.RouteFeeRequest) 132 | }) 133 | _sym_db.RegisterMessage(RouteFeeRequest) 134 | 135 | RouteFeeResponse = _reflection.GeneratedProtocolMessageType('RouteFeeResponse', (_message.Message,), { 136 | 'DESCRIPTOR' : _ROUTEFEERESPONSE, 137 | '__module__' : 'routerrpc.router_pb2' 138 | # @@protoc_insertion_point(class_scope:routerrpc.RouteFeeResponse) 139 | }) 140 | _sym_db.RegisterMessage(RouteFeeResponse) 141 | 142 | SendToRouteRequest = _reflection.GeneratedProtocolMessageType('SendToRouteRequest', (_message.Message,), { 143 | 'DESCRIPTOR' : _SENDTOROUTEREQUEST, 144 | '__module__' : 'routerrpc.router_pb2' 145 | # @@protoc_insertion_point(class_scope:routerrpc.SendToRouteRequest) 146 | }) 147 | _sym_db.RegisterMessage(SendToRouteRequest) 148 | 149 | SendToRouteResponse = _reflection.GeneratedProtocolMessageType('SendToRouteResponse', (_message.Message,), { 150 | 'DESCRIPTOR' : _SENDTOROUTERESPONSE, 151 | '__module__' : 'routerrpc.router_pb2' 152 | # @@protoc_insertion_point(class_scope:routerrpc.SendToRouteResponse) 153 | }) 154 | _sym_db.RegisterMessage(SendToRouteResponse) 155 | 156 | ResetMissionControlRequest = _reflection.GeneratedProtocolMessageType('ResetMissionControlRequest', (_message.Message,), { 157 | 'DESCRIPTOR' : _RESETMISSIONCONTROLREQUEST, 158 | '__module__' : 'routerrpc.router_pb2' 159 | # @@protoc_insertion_point(class_scope:routerrpc.ResetMissionControlRequest) 160 | }) 161 | _sym_db.RegisterMessage(ResetMissionControlRequest) 162 | 163 | ResetMissionControlResponse = _reflection.GeneratedProtocolMessageType('ResetMissionControlResponse', (_message.Message,), { 164 | 'DESCRIPTOR' : _RESETMISSIONCONTROLRESPONSE, 165 | '__module__' : 'routerrpc.router_pb2' 166 | # @@protoc_insertion_point(class_scope:routerrpc.ResetMissionControlResponse) 167 | }) 168 | _sym_db.RegisterMessage(ResetMissionControlResponse) 169 | 170 | QueryMissionControlRequest = _reflection.GeneratedProtocolMessageType('QueryMissionControlRequest', (_message.Message,), { 171 | 'DESCRIPTOR' : _QUERYMISSIONCONTROLREQUEST, 172 | '__module__' : 'routerrpc.router_pb2' 173 | # @@protoc_insertion_point(class_scope:routerrpc.QueryMissionControlRequest) 174 | }) 175 | _sym_db.RegisterMessage(QueryMissionControlRequest) 176 | 177 | QueryMissionControlResponse = _reflection.GeneratedProtocolMessageType('QueryMissionControlResponse', (_message.Message,), { 178 | 'DESCRIPTOR' : _QUERYMISSIONCONTROLRESPONSE, 179 | '__module__' : 'routerrpc.router_pb2' 180 | # @@protoc_insertion_point(class_scope:routerrpc.QueryMissionControlResponse) 181 | }) 182 | _sym_db.RegisterMessage(QueryMissionControlResponse) 183 | 184 | XImportMissionControlRequest = _reflection.GeneratedProtocolMessageType('XImportMissionControlRequest', (_message.Message,), { 185 | 'DESCRIPTOR' : _XIMPORTMISSIONCONTROLREQUEST, 186 | '__module__' : 'routerrpc.router_pb2' 187 | # @@protoc_insertion_point(class_scope:routerrpc.XImportMissionControlRequest) 188 | }) 189 | _sym_db.RegisterMessage(XImportMissionControlRequest) 190 | 191 | XImportMissionControlResponse = _reflection.GeneratedProtocolMessageType('XImportMissionControlResponse', (_message.Message,), { 192 | 'DESCRIPTOR' : _XIMPORTMISSIONCONTROLRESPONSE, 193 | '__module__' : 'routerrpc.router_pb2' 194 | # @@protoc_insertion_point(class_scope:routerrpc.XImportMissionControlResponse) 195 | }) 196 | _sym_db.RegisterMessage(XImportMissionControlResponse) 197 | 198 | PairHistory = _reflection.GeneratedProtocolMessageType('PairHistory', (_message.Message,), { 199 | 'DESCRIPTOR' : _PAIRHISTORY, 200 | '__module__' : 'routerrpc.router_pb2' 201 | # @@protoc_insertion_point(class_scope:routerrpc.PairHistory) 202 | }) 203 | _sym_db.RegisterMessage(PairHistory) 204 | 205 | PairData = _reflection.GeneratedProtocolMessageType('PairData', (_message.Message,), { 206 | 'DESCRIPTOR' : _PAIRDATA, 207 | '__module__' : 'routerrpc.router_pb2' 208 | # @@protoc_insertion_point(class_scope:routerrpc.PairData) 209 | }) 210 | _sym_db.RegisterMessage(PairData) 211 | 212 | GetMissionControlConfigRequest = _reflection.GeneratedProtocolMessageType('GetMissionControlConfigRequest', (_message.Message,), { 213 | 'DESCRIPTOR' : _GETMISSIONCONTROLCONFIGREQUEST, 214 | '__module__' : 'routerrpc.router_pb2' 215 | # @@protoc_insertion_point(class_scope:routerrpc.GetMissionControlConfigRequest) 216 | }) 217 | _sym_db.RegisterMessage(GetMissionControlConfigRequest) 218 | 219 | GetMissionControlConfigResponse = _reflection.GeneratedProtocolMessageType('GetMissionControlConfigResponse', (_message.Message,), { 220 | 'DESCRIPTOR' : _GETMISSIONCONTROLCONFIGRESPONSE, 221 | '__module__' : 'routerrpc.router_pb2' 222 | # @@protoc_insertion_point(class_scope:routerrpc.GetMissionControlConfigResponse) 223 | }) 224 | _sym_db.RegisterMessage(GetMissionControlConfigResponse) 225 | 226 | SetMissionControlConfigRequest = _reflection.GeneratedProtocolMessageType('SetMissionControlConfigRequest', (_message.Message,), { 227 | 'DESCRIPTOR' : _SETMISSIONCONTROLCONFIGREQUEST, 228 | '__module__' : 'routerrpc.router_pb2' 229 | # @@protoc_insertion_point(class_scope:routerrpc.SetMissionControlConfigRequest) 230 | }) 231 | _sym_db.RegisterMessage(SetMissionControlConfigRequest) 232 | 233 | SetMissionControlConfigResponse = _reflection.GeneratedProtocolMessageType('SetMissionControlConfigResponse', (_message.Message,), { 234 | 'DESCRIPTOR' : _SETMISSIONCONTROLCONFIGRESPONSE, 235 | '__module__' : 'routerrpc.router_pb2' 236 | # @@protoc_insertion_point(class_scope:routerrpc.SetMissionControlConfigResponse) 237 | }) 238 | _sym_db.RegisterMessage(SetMissionControlConfigResponse) 239 | 240 | MissionControlConfig = _reflection.GeneratedProtocolMessageType('MissionControlConfig', (_message.Message,), { 241 | 'DESCRIPTOR' : _MISSIONCONTROLCONFIG, 242 | '__module__' : 'routerrpc.router_pb2' 243 | # @@protoc_insertion_point(class_scope:routerrpc.MissionControlConfig) 244 | }) 245 | _sym_db.RegisterMessage(MissionControlConfig) 246 | 247 | QueryProbabilityRequest = _reflection.GeneratedProtocolMessageType('QueryProbabilityRequest', (_message.Message,), { 248 | 'DESCRIPTOR' : _QUERYPROBABILITYREQUEST, 249 | '__module__' : 'routerrpc.router_pb2' 250 | # @@protoc_insertion_point(class_scope:routerrpc.QueryProbabilityRequest) 251 | }) 252 | _sym_db.RegisterMessage(QueryProbabilityRequest) 253 | 254 | QueryProbabilityResponse = _reflection.GeneratedProtocolMessageType('QueryProbabilityResponse', (_message.Message,), { 255 | 'DESCRIPTOR' : _QUERYPROBABILITYRESPONSE, 256 | '__module__' : 'routerrpc.router_pb2' 257 | # @@protoc_insertion_point(class_scope:routerrpc.QueryProbabilityResponse) 258 | }) 259 | _sym_db.RegisterMessage(QueryProbabilityResponse) 260 | 261 | BuildRouteRequest = _reflection.GeneratedProtocolMessageType('BuildRouteRequest', (_message.Message,), { 262 | 'DESCRIPTOR' : _BUILDROUTEREQUEST, 263 | '__module__' : 'routerrpc.router_pb2' 264 | # @@protoc_insertion_point(class_scope:routerrpc.BuildRouteRequest) 265 | }) 266 | _sym_db.RegisterMessage(BuildRouteRequest) 267 | 268 | BuildRouteResponse = _reflection.GeneratedProtocolMessageType('BuildRouteResponse', (_message.Message,), { 269 | 'DESCRIPTOR' : _BUILDROUTERESPONSE, 270 | '__module__' : 'routerrpc.router_pb2' 271 | # @@protoc_insertion_point(class_scope:routerrpc.BuildRouteResponse) 272 | }) 273 | _sym_db.RegisterMessage(BuildRouteResponse) 274 | 275 | SubscribeHtlcEventsRequest = _reflection.GeneratedProtocolMessageType('SubscribeHtlcEventsRequest', (_message.Message,), { 276 | 'DESCRIPTOR' : _SUBSCRIBEHTLCEVENTSREQUEST, 277 | '__module__' : 'routerrpc.router_pb2' 278 | # @@protoc_insertion_point(class_scope:routerrpc.SubscribeHtlcEventsRequest) 279 | }) 280 | _sym_db.RegisterMessage(SubscribeHtlcEventsRequest) 281 | 282 | HtlcEvent = _reflection.GeneratedProtocolMessageType('HtlcEvent', (_message.Message,), { 283 | 'DESCRIPTOR' : _HTLCEVENT, 284 | '__module__' : 'routerrpc.router_pb2' 285 | # @@protoc_insertion_point(class_scope:routerrpc.HtlcEvent) 286 | }) 287 | _sym_db.RegisterMessage(HtlcEvent) 288 | 289 | HtlcInfo = _reflection.GeneratedProtocolMessageType('HtlcInfo', (_message.Message,), { 290 | 'DESCRIPTOR' : _HTLCINFO, 291 | '__module__' : 'routerrpc.router_pb2' 292 | # @@protoc_insertion_point(class_scope:routerrpc.HtlcInfo) 293 | }) 294 | _sym_db.RegisterMessage(HtlcInfo) 295 | 296 | ForwardEvent = _reflection.GeneratedProtocolMessageType('ForwardEvent', (_message.Message,), { 297 | 'DESCRIPTOR' : _FORWARDEVENT, 298 | '__module__' : 'routerrpc.router_pb2' 299 | # @@protoc_insertion_point(class_scope:routerrpc.ForwardEvent) 300 | }) 301 | _sym_db.RegisterMessage(ForwardEvent) 302 | 303 | ForwardFailEvent = _reflection.GeneratedProtocolMessageType('ForwardFailEvent', (_message.Message,), { 304 | 'DESCRIPTOR' : _FORWARDFAILEVENT, 305 | '__module__' : 'routerrpc.router_pb2' 306 | # @@protoc_insertion_point(class_scope:routerrpc.ForwardFailEvent) 307 | }) 308 | _sym_db.RegisterMessage(ForwardFailEvent) 309 | 310 | SettleEvent = _reflection.GeneratedProtocolMessageType('SettleEvent', (_message.Message,), { 311 | 'DESCRIPTOR' : _SETTLEEVENT, 312 | '__module__' : 'routerrpc.router_pb2' 313 | # @@protoc_insertion_point(class_scope:routerrpc.SettleEvent) 314 | }) 315 | _sym_db.RegisterMessage(SettleEvent) 316 | 317 | LinkFailEvent = _reflection.GeneratedProtocolMessageType('LinkFailEvent', (_message.Message,), { 318 | 'DESCRIPTOR' : _LINKFAILEVENT, 319 | '__module__' : 'routerrpc.router_pb2' 320 | # @@protoc_insertion_point(class_scope:routerrpc.LinkFailEvent) 321 | }) 322 | _sym_db.RegisterMessage(LinkFailEvent) 323 | 324 | PaymentStatus = _reflection.GeneratedProtocolMessageType('PaymentStatus', (_message.Message,), { 325 | 'DESCRIPTOR' : _PAYMENTSTATUS, 326 | '__module__' : 'routerrpc.router_pb2' 327 | # @@protoc_insertion_point(class_scope:routerrpc.PaymentStatus) 328 | }) 329 | _sym_db.RegisterMessage(PaymentStatus) 330 | 331 | CircuitKey = _reflection.GeneratedProtocolMessageType('CircuitKey', (_message.Message,), { 332 | 'DESCRIPTOR' : _CIRCUITKEY, 333 | '__module__' : 'routerrpc.router_pb2' 334 | # @@protoc_insertion_point(class_scope:routerrpc.CircuitKey) 335 | }) 336 | _sym_db.RegisterMessage(CircuitKey) 337 | 338 | ForwardHtlcInterceptRequest = _reflection.GeneratedProtocolMessageType('ForwardHtlcInterceptRequest', (_message.Message,), { 339 | 340 | 'CustomRecordsEntry' : _reflection.GeneratedProtocolMessageType('CustomRecordsEntry', (_message.Message,), { 341 | 'DESCRIPTOR' : _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY, 342 | '__module__' : 'routerrpc.router_pb2' 343 | # @@protoc_insertion_point(class_scope:routerrpc.ForwardHtlcInterceptRequest.CustomRecordsEntry) 344 | }) 345 | , 346 | 'DESCRIPTOR' : _FORWARDHTLCINTERCEPTREQUEST, 347 | '__module__' : 'routerrpc.router_pb2' 348 | # @@protoc_insertion_point(class_scope:routerrpc.ForwardHtlcInterceptRequest) 349 | }) 350 | _sym_db.RegisterMessage(ForwardHtlcInterceptRequest) 351 | _sym_db.RegisterMessage(ForwardHtlcInterceptRequest.CustomRecordsEntry) 352 | 353 | ForwardHtlcInterceptResponse = _reflection.GeneratedProtocolMessageType('ForwardHtlcInterceptResponse', (_message.Message,), { 354 | 'DESCRIPTOR' : _FORWARDHTLCINTERCEPTRESPONSE, 355 | '__module__' : 'routerrpc.router_pb2' 356 | # @@protoc_insertion_point(class_scope:routerrpc.ForwardHtlcInterceptResponse) 357 | }) 358 | _sym_db.RegisterMessage(ForwardHtlcInterceptResponse) 359 | 360 | UpdateChanStatusRequest = _reflection.GeneratedProtocolMessageType('UpdateChanStatusRequest', (_message.Message,), { 361 | 'DESCRIPTOR' : _UPDATECHANSTATUSREQUEST, 362 | '__module__' : 'routerrpc.router_pb2' 363 | # @@protoc_insertion_point(class_scope:routerrpc.UpdateChanStatusRequest) 364 | }) 365 | _sym_db.RegisterMessage(UpdateChanStatusRequest) 366 | 367 | UpdateChanStatusResponse = _reflection.GeneratedProtocolMessageType('UpdateChanStatusResponse', (_message.Message,), { 368 | 'DESCRIPTOR' : _UPDATECHANSTATUSRESPONSE, 369 | '__module__' : 'routerrpc.router_pb2' 370 | # @@protoc_insertion_point(class_scope:routerrpc.UpdateChanStatusResponse) 371 | }) 372 | _sym_db.RegisterMessage(UpdateChanStatusResponse) 373 | 374 | _ROUTER = DESCRIPTOR.services_by_name['Router'] 375 | if _descriptor._USE_C_DESCRIPTORS == False: 376 | 377 | DESCRIPTOR._options = None 378 | DESCRIPTOR._serialized_options = b'Z/github.com/lightningnetwork/lnd/lnrpc/routerrpc' 379 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._options = None 380 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_options = b'8\001' 381 | _SENDPAYMENTREQUEST.fields_by_name['outgoing_chan_id']._options = None 382 | _SENDPAYMENTREQUEST.fields_by_name['outgoing_chan_id']._serialized_options = b'\030\0010\001' 383 | _BUILDROUTEREQUEST.fields_by_name['outgoing_chan_id']._options = None 384 | _BUILDROUTEREQUEST.fields_by_name['outgoing_chan_id']._serialized_options = b'0\001' 385 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._options = None 386 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_options = b'8\001' 387 | _ROUTER.methods_by_name['SendToRoute']._options = None 388 | _ROUTER.methods_by_name['SendToRoute']._serialized_options = b'\210\002\001' 389 | _ROUTER.methods_by_name['SendPayment']._options = None 390 | _ROUTER.methods_by_name['SendPayment']._serialized_options = b'\210\002\001' 391 | _ROUTER.methods_by_name['TrackPayment']._options = None 392 | _ROUTER.methods_by_name['TrackPayment']._serialized_options = b'\210\002\001' 393 | _FAILUREDETAIL._serialized_start=4248 394 | _FAILUREDETAIL._serialized_end=4761 395 | _PAYMENTSTATE._serialized_start=4764 396 | _PAYMENTSTATE._serialized_end=4938 397 | _RESOLVEHOLDFORWARDACTION._serialized_start=4940 398 | _RESOLVEHOLDFORWARDACTION._serialized_end=5000 399 | _CHANSTATUSACTION._serialized_start=5002 400 | _CHANSTATUSACTION._serialized_end=5055 401 | _SENDPAYMENTREQUEST._serialized_start=55 402 | _SENDPAYMENTREQUEST._serialized_end=731 403 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_start=675 404 | _SENDPAYMENTREQUEST_DESTCUSTOMRECORDSENTRY._serialized_end=731 405 | _TRACKPAYMENTREQUEST._serialized_start=733 406 | _TRACKPAYMENTREQUEST._serialized_end=805 407 | _ROUTEFEEREQUEST._serialized_start=807 408 | _ROUTEFEEREQUEST._serialized_end=855 409 | _ROUTEFEERESPONSE._serialized_start=857 410 | _ROUTEFEERESPONSE._serialized_end=926 411 | _SENDTOROUTEREQUEST._serialized_start=928 412 | _SENDTOROUTEREQUEST._serialized_end=999 413 | _SENDTOROUTERESPONSE._serialized_start=1001 414 | _SENDTOROUTERESPONSE._serialized_end=1073 415 | _RESETMISSIONCONTROLREQUEST._serialized_start=1075 416 | _RESETMISSIONCONTROLREQUEST._serialized_end=1103 417 | _RESETMISSIONCONTROLRESPONSE._serialized_start=1105 418 | _RESETMISSIONCONTROLRESPONSE._serialized_end=1134 419 | _QUERYMISSIONCONTROLREQUEST._serialized_start=1136 420 | _QUERYMISSIONCONTROLREQUEST._serialized_end=1164 421 | _QUERYMISSIONCONTROLRESPONSE._serialized_start=1166 422 | _QUERYMISSIONCONTROLRESPONSE._serialized_end=1240 423 | _XIMPORTMISSIONCONTROLREQUEST._serialized_start=1242 424 | _XIMPORTMISSIONCONTROLREQUEST._serialized_end=1326 425 | _XIMPORTMISSIONCONTROLRESPONSE._serialized_start=1328 426 | _XIMPORTMISSIONCONTROLRESPONSE._serialized_end=1359 427 | _PAIRHISTORY._serialized_start=1361 428 | _PAIRHISTORY._serialized_end=1472 429 | _PAIRDATA._serialized_start=1475 430 | _PAIRDATA._serialized_end=1628 431 | _GETMISSIONCONTROLCONFIGREQUEST._serialized_start=1630 432 | _GETMISSIONCONTROLCONFIGREQUEST._serialized_end=1662 433 | _GETMISSIONCONTROLCONFIGRESPONSE._serialized_start=1664 434 | _GETMISSIONCONTROLCONFIGRESPONSE._serialized_end=1746 435 | _SETMISSIONCONTROLCONFIGREQUEST._serialized_start=1748 436 | _SETMISSIONCONTROLCONFIGREQUEST._serialized_end=1829 437 | _SETMISSIONCONTROLCONFIGRESPONSE._serialized_start=1831 438 | _SETMISSIONCONTROLCONFIGRESPONSE._serialized_end=1864 439 | _MISSIONCONTROLCONFIG._serialized_start=1867 440 | _MISSIONCONTROLCONFIG._serialized_end=2030 441 | _QUERYPROBABILITYREQUEST._serialized_start=2032 442 | _QUERYPROBABILITYREQUEST._serialized_end=2111 443 | _QUERYPROBABILITYRESPONSE._serialized_start=2113 444 | _QUERYPROBABILITYRESPONSE._serialized_end=2198 445 | _BUILDROUTEREQUEST._serialized_start=2201 446 | _BUILDROUTEREQUEST._serialized_end=2337 447 | _BUILDROUTERESPONSE._serialized_start=2339 448 | _BUILDROUTERESPONSE._serialized_end=2388 449 | _SUBSCRIBEHTLCEVENTSREQUEST._serialized_start=2390 450 | _SUBSCRIBEHTLCEVENTSREQUEST._serialized_end=2418 451 | _HTLCEVENT._serialized_start=2421 452 | _HTLCEVENT._serialized_end=2897 453 | _HTLCEVENT_EVENTTYPE._serialized_start=2828 454 | _HTLCEVENT_EVENTTYPE._serialized_end=2888 455 | _HTLCINFO._serialized_start=2899 456 | _HTLCINFO._serialized_end=3017 457 | _FORWARDEVENT._serialized_start=3019 458 | _FORWARDEVENT._serialized_end=3068 459 | _FORWARDFAILEVENT._serialized_start=3070 460 | _FORWARDFAILEVENT._serialized_end=3088 461 | _SETTLEEVENT._serialized_start=3090 462 | _SETTLEEVENT._serialized_end=3121 463 | _LINKFAILEVENT._serialized_start=3124 464 | _LINKFAILEVENT._serialized_end=3298 465 | _PAYMENTSTATUS._serialized_start=3300 466 | _PAYMENTSTATUS._serialized_end=3414 467 | _CIRCUITKEY._serialized_start=3416 468 | _CIRCUITKEY._serialized_end=3462 469 | _FORWARDHTLCINTERCEPTREQUEST._serialized_start=3465 470 | _FORWARDHTLCINTERCEPTREQUEST._serialized_end=3872 471 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_start=3820 472 | _FORWARDHTLCINTERCEPTREQUEST_CUSTOMRECORDSENTRY._serialized_end=3872 473 | _FORWARDHTLCINTERCEPTRESPONSE._serialized_start=3875 474 | _FORWARDHTLCINTERCEPTRESPONSE._serialized_end=4104 475 | _UPDATECHANSTATUSREQUEST._serialized_start=4106 476 | _UPDATECHANSTATUSREQUEST._serialized_end=4217 477 | _UPDATECHANSTATUSRESPONSE._serialized_start=4219 478 | _UPDATECHANSTATUSRESPONSE._serialized_end=4245 479 | _ROUTER._serialized_start=5058 480 | _ROUTER._serialized_end=6579 481 | # @@protoc_insertion_point(module_scope) 482 | -------------------------------------------------------------------------------- /lnrpc_generated/routerrpc/router_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | from lnrpc_generated import lightning_pb2 as lightning__pb2 6 | from lnrpc_generated.routerrpc import router_pb2 as routerrpc_dot_router__pb2 7 | 8 | 9 | class RouterStub(object): 10 | """Router is a service that offers advanced interaction with the router 11 | subsystem of the daemon. 12 | """ 13 | 14 | def __init__(self, channel): 15 | """Constructor. 16 | 17 | Args: 18 | channel: A grpc.Channel. 19 | """ 20 | self.SendPaymentV2 = channel.unary_stream( 21 | '/routerrpc.Router/SendPaymentV2', 22 | request_serializer=routerrpc_dot_router__pb2.SendPaymentRequest.SerializeToString, 23 | response_deserializer=lightning__pb2.Payment.FromString, 24 | ) 25 | self.TrackPaymentV2 = channel.unary_stream( 26 | '/routerrpc.Router/TrackPaymentV2', 27 | request_serializer=routerrpc_dot_router__pb2.TrackPaymentRequest.SerializeToString, 28 | response_deserializer=lightning__pb2.Payment.FromString, 29 | ) 30 | self.EstimateRouteFee = channel.unary_unary( 31 | '/routerrpc.Router/EstimateRouteFee', 32 | request_serializer=routerrpc_dot_router__pb2.RouteFeeRequest.SerializeToString, 33 | response_deserializer=routerrpc_dot_router__pb2.RouteFeeResponse.FromString, 34 | ) 35 | self.SendToRoute = channel.unary_unary( 36 | '/routerrpc.Router/SendToRoute', 37 | request_serializer=routerrpc_dot_router__pb2.SendToRouteRequest.SerializeToString, 38 | response_deserializer=routerrpc_dot_router__pb2.SendToRouteResponse.FromString, 39 | ) 40 | self.SendToRouteV2 = channel.unary_unary( 41 | '/routerrpc.Router/SendToRouteV2', 42 | request_serializer=routerrpc_dot_router__pb2.SendToRouteRequest.SerializeToString, 43 | response_deserializer=lightning__pb2.HTLCAttempt.FromString, 44 | ) 45 | self.ResetMissionControl = channel.unary_unary( 46 | '/routerrpc.Router/ResetMissionControl', 47 | request_serializer=routerrpc_dot_router__pb2.ResetMissionControlRequest.SerializeToString, 48 | response_deserializer=routerrpc_dot_router__pb2.ResetMissionControlResponse.FromString, 49 | ) 50 | self.QueryMissionControl = channel.unary_unary( 51 | '/routerrpc.Router/QueryMissionControl', 52 | request_serializer=routerrpc_dot_router__pb2.QueryMissionControlRequest.SerializeToString, 53 | response_deserializer=routerrpc_dot_router__pb2.QueryMissionControlResponse.FromString, 54 | ) 55 | self.XImportMissionControl = channel.unary_unary( 56 | '/routerrpc.Router/XImportMissionControl', 57 | request_serializer=routerrpc_dot_router__pb2.XImportMissionControlRequest.SerializeToString, 58 | response_deserializer=routerrpc_dot_router__pb2.XImportMissionControlResponse.FromString, 59 | ) 60 | self.GetMissionControlConfig = channel.unary_unary( 61 | '/routerrpc.Router/GetMissionControlConfig', 62 | request_serializer=routerrpc_dot_router__pb2.GetMissionControlConfigRequest.SerializeToString, 63 | response_deserializer=routerrpc_dot_router__pb2.GetMissionControlConfigResponse.FromString, 64 | ) 65 | self.SetMissionControlConfig = channel.unary_unary( 66 | '/routerrpc.Router/SetMissionControlConfig', 67 | request_serializer=routerrpc_dot_router__pb2.SetMissionControlConfigRequest.SerializeToString, 68 | response_deserializer=routerrpc_dot_router__pb2.SetMissionControlConfigResponse.FromString, 69 | ) 70 | self.QueryProbability = channel.unary_unary( 71 | '/routerrpc.Router/QueryProbability', 72 | request_serializer=routerrpc_dot_router__pb2.QueryProbabilityRequest.SerializeToString, 73 | response_deserializer=routerrpc_dot_router__pb2.QueryProbabilityResponse.FromString, 74 | ) 75 | self.BuildRoute = channel.unary_unary( 76 | '/routerrpc.Router/BuildRoute', 77 | request_serializer=routerrpc_dot_router__pb2.BuildRouteRequest.SerializeToString, 78 | response_deserializer=routerrpc_dot_router__pb2.BuildRouteResponse.FromString, 79 | ) 80 | self.SubscribeHtlcEvents = channel.unary_stream( 81 | '/routerrpc.Router/SubscribeHtlcEvents', 82 | request_serializer=routerrpc_dot_router__pb2.SubscribeHtlcEventsRequest.SerializeToString, 83 | response_deserializer=routerrpc_dot_router__pb2.HtlcEvent.FromString, 84 | ) 85 | self.SendPayment = channel.unary_stream( 86 | '/routerrpc.Router/SendPayment', 87 | request_serializer=routerrpc_dot_router__pb2.SendPaymentRequest.SerializeToString, 88 | response_deserializer=routerrpc_dot_router__pb2.PaymentStatus.FromString, 89 | ) 90 | self.TrackPayment = channel.unary_stream( 91 | '/routerrpc.Router/TrackPayment', 92 | request_serializer=routerrpc_dot_router__pb2.TrackPaymentRequest.SerializeToString, 93 | response_deserializer=routerrpc_dot_router__pb2.PaymentStatus.FromString, 94 | ) 95 | self.HtlcInterceptor = channel.stream_stream( 96 | '/routerrpc.Router/HtlcInterceptor', 97 | request_serializer=routerrpc_dot_router__pb2.ForwardHtlcInterceptResponse.SerializeToString, 98 | response_deserializer=routerrpc_dot_router__pb2.ForwardHtlcInterceptRequest.FromString, 99 | ) 100 | self.UpdateChanStatus = channel.unary_unary( 101 | '/routerrpc.Router/UpdateChanStatus', 102 | request_serializer=routerrpc_dot_router__pb2.UpdateChanStatusRequest.SerializeToString, 103 | response_deserializer=routerrpc_dot_router__pb2.UpdateChanStatusResponse.FromString, 104 | ) 105 | 106 | 107 | class RouterServicer(object): 108 | """Router is a service that offers advanced interaction with the router 109 | subsystem of the daemon. 110 | """ 111 | 112 | def SendPaymentV2(self, request, context): 113 | """ 114 | SendPaymentV2 attempts to route a payment described by the passed 115 | PaymentRequest to the final destination. The call returns a stream of 116 | payment updates. 117 | """ 118 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 119 | context.set_details('Method not implemented!') 120 | raise NotImplementedError('Method not implemented!') 121 | 122 | def TrackPaymentV2(self, request, context): 123 | """ 124 | TrackPaymentV2 returns an update stream for the payment identified by the 125 | payment hash. 126 | """ 127 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 128 | context.set_details('Method not implemented!') 129 | raise NotImplementedError('Method not implemented!') 130 | 131 | def EstimateRouteFee(self, request, context): 132 | """ 133 | EstimateRouteFee allows callers to obtain a lower bound w.r.t how much it 134 | may cost to send an HTLC to the target end destination. 135 | """ 136 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 137 | context.set_details('Method not implemented!') 138 | raise NotImplementedError('Method not implemented!') 139 | 140 | def SendToRoute(self, request, context): 141 | """ 142 | Deprecated, use SendToRouteV2. SendToRoute attempts to make a payment via 143 | the specified route. This method differs from SendPayment in that it 144 | allows users to specify a full route manually. This can be used for 145 | things like rebalancing, and atomic swaps. It differs from the newer 146 | SendToRouteV2 in that it doesn't return the full HTLC information. 147 | """ 148 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 149 | context.set_details('Method not implemented!') 150 | raise NotImplementedError('Method not implemented!') 151 | 152 | def SendToRouteV2(self, request, context): 153 | """ 154 | SendToRouteV2 attempts to make a payment via the specified route. This 155 | method differs from SendPayment in that it allows users to specify a full 156 | route manually. This can be used for things like rebalancing, and atomic 157 | swaps. 158 | """ 159 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 160 | context.set_details('Method not implemented!') 161 | raise NotImplementedError('Method not implemented!') 162 | 163 | def ResetMissionControl(self, request, context): 164 | """ 165 | ResetMissionControl clears all mission control state and starts with a clean 166 | slate. 167 | """ 168 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 169 | context.set_details('Method not implemented!') 170 | raise NotImplementedError('Method not implemented!') 171 | 172 | def QueryMissionControl(self, request, context): 173 | """ 174 | QueryMissionControl exposes the internal mission control state to callers. 175 | It is a development feature. 176 | """ 177 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 178 | context.set_details('Method not implemented!') 179 | raise NotImplementedError('Method not implemented!') 180 | 181 | def XImportMissionControl(self, request, context): 182 | """ 183 | XImportMissionControl is an experimental API that imports the state provided 184 | to the internal mission control's state, using all results which are more 185 | recent than our existing values. These values will only be imported 186 | in-memory, and will not be persisted across restarts. 187 | """ 188 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 189 | context.set_details('Method not implemented!') 190 | raise NotImplementedError('Method not implemented!') 191 | 192 | def GetMissionControlConfig(self, request, context): 193 | """ 194 | GetMissionControlConfig returns mission control's current config. 195 | """ 196 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 197 | context.set_details('Method not implemented!') 198 | raise NotImplementedError('Method not implemented!') 199 | 200 | def SetMissionControlConfig(self, request, context): 201 | """ 202 | SetMissionControlConfig will set mission control's config, if the config 203 | provided is valid. 204 | """ 205 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 206 | context.set_details('Method not implemented!') 207 | raise NotImplementedError('Method not implemented!') 208 | 209 | def QueryProbability(self, request, context): 210 | """ 211 | QueryProbability returns the current success probability estimate for a 212 | given node pair and amount. 213 | """ 214 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 215 | context.set_details('Method not implemented!') 216 | raise NotImplementedError('Method not implemented!') 217 | 218 | def BuildRoute(self, request, context): 219 | """ 220 | BuildRoute builds a fully specified route based on a list of hop public 221 | keys. It retrieves the relevant channel policies from the graph in order to 222 | calculate the correct fees and time locks. 223 | """ 224 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 225 | context.set_details('Method not implemented!') 226 | raise NotImplementedError('Method not implemented!') 227 | 228 | def SubscribeHtlcEvents(self, request, context): 229 | """ 230 | SubscribeHtlcEvents creates a uni-directional stream from the server to 231 | the client which delivers a stream of htlc events. 232 | """ 233 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 234 | context.set_details('Method not implemented!') 235 | raise NotImplementedError('Method not implemented!') 236 | 237 | def SendPayment(self, request, context): 238 | """ 239 | Deprecated, use SendPaymentV2. SendPayment attempts to route a payment 240 | described by the passed PaymentRequest to the final destination. The call 241 | returns a stream of payment status updates. 242 | """ 243 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 244 | context.set_details('Method not implemented!') 245 | raise NotImplementedError('Method not implemented!') 246 | 247 | def TrackPayment(self, request, context): 248 | """ 249 | Deprecated, use TrackPaymentV2. TrackPayment returns an update stream for 250 | the payment identified by the payment hash. 251 | """ 252 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 253 | context.set_details('Method not implemented!') 254 | raise NotImplementedError('Method not implemented!') 255 | 256 | def HtlcInterceptor(self, request_iterator, context): 257 | """* 258 | HtlcInterceptor dispatches a bi-directional streaming RPC in which 259 | Forwarded HTLC requests are sent to the client and the client responds with 260 | a boolean that tells LND if this htlc should be intercepted. 261 | In case of interception, the htlc can be either settled, cancelled or 262 | resumed later by using the ResolveHoldForward endpoint. 263 | """ 264 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 265 | context.set_details('Method not implemented!') 266 | raise NotImplementedError('Method not implemented!') 267 | 268 | def UpdateChanStatus(self, request, context): 269 | """ 270 | UpdateChanStatus attempts to manually set the state of a channel 271 | (enabled, disabled, or auto). A manual "disable" request will cause the 272 | channel to stay disabled until a subsequent manual request of either 273 | "enable" or "auto". 274 | """ 275 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 276 | context.set_details('Method not implemented!') 277 | raise NotImplementedError('Method not implemented!') 278 | 279 | 280 | def add_RouterServicer_to_server(servicer, server): 281 | rpc_method_handlers = { 282 | 'SendPaymentV2': grpc.unary_stream_rpc_method_handler( 283 | servicer.SendPaymentV2, 284 | request_deserializer=routerrpc_dot_router__pb2.SendPaymentRequest.FromString, 285 | response_serializer=lightning__pb2.Payment.SerializeToString, 286 | ), 287 | 'TrackPaymentV2': grpc.unary_stream_rpc_method_handler( 288 | servicer.TrackPaymentV2, 289 | request_deserializer=routerrpc_dot_router__pb2.TrackPaymentRequest.FromString, 290 | response_serializer=lightning__pb2.Payment.SerializeToString, 291 | ), 292 | 'EstimateRouteFee': grpc.unary_unary_rpc_method_handler( 293 | servicer.EstimateRouteFee, 294 | request_deserializer=routerrpc_dot_router__pb2.RouteFeeRequest.FromString, 295 | response_serializer=routerrpc_dot_router__pb2.RouteFeeResponse.SerializeToString, 296 | ), 297 | 'SendToRoute': grpc.unary_unary_rpc_method_handler( 298 | servicer.SendToRoute, 299 | request_deserializer=routerrpc_dot_router__pb2.SendToRouteRequest.FromString, 300 | response_serializer=routerrpc_dot_router__pb2.SendToRouteResponse.SerializeToString, 301 | ), 302 | 'SendToRouteV2': grpc.unary_unary_rpc_method_handler( 303 | servicer.SendToRouteV2, 304 | request_deserializer=routerrpc_dot_router__pb2.SendToRouteRequest.FromString, 305 | response_serializer=lightning__pb2.HTLCAttempt.SerializeToString, 306 | ), 307 | 'ResetMissionControl': grpc.unary_unary_rpc_method_handler( 308 | servicer.ResetMissionControl, 309 | request_deserializer=routerrpc_dot_router__pb2.ResetMissionControlRequest.FromString, 310 | response_serializer=routerrpc_dot_router__pb2.ResetMissionControlResponse.SerializeToString, 311 | ), 312 | 'QueryMissionControl': grpc.unary_unary_rpc_method_handler( 313 | servicer.QueryMissionControl, 314 | request_deserializer=routerrpc_dot_router__pb2.QueryMissionControlRequest.FromString, 315 | response_serializer=routerrpc_dot_router__pb2.QueryMissionControlResponse.SerializeToString, 316 | ), 317 | 'XImportMissionControl': grpc.unary_unary_rpc_method_handler( 318 | servicer.XImportMissionControl, 319 | request_deserializer=routerrpc_dot_router__pb2.XImportMissionControlRequest.FromString, 320 | response_serializer=routerrpc_dot_router__pb2.XImportMissionControlResponse.SerializeToString, 321 | ), 322 | 'GetMissionControlConfig': grpc.unary_unary_rpc_method_handler( 323 | servicer.GetMissionControlConfig, 324 | request_deserializer=routerrpc_dot_router__pb2.GetMissionControlConfigRequest.FromString, 325 | response_serializer=routerrpc_dot_router__pb2.GetMissionControlConfigResponse.SerializeToString, 326 | ), 327 | 'SetMissionControlConfig': grpc.unary_unary_rpc_method_handler( 328 | servicer.SetMissionControlConfig, 329 | request_deserializer=routerrpc_dot_router__pb2.SetMissionControlConfigRequest.FromString, 330 | response_serializer=routerrpc_dot_router__pb2.SetMissionControlConfigResponse.SerializeToString, 331 | ), 332 | 'QueryProbability': grpc.unary_unary_rpc_method_handler( 333 | servicer.QueryProbability, 334 | request_deserializer=routerrpc_dot_router__pb2.QueryProbabilityRequest.FromString, 335 | response_serializer=routerrpc_dot_router__pb2.QueryProbabilityResponse.SerializeToString, 336 | ), 337 | 'BuildRoute': grpc.unary_unary_rpc_method_handler( 338 | servicer.BuildRoute, 339 | request_deserializer=routerrpc_dot_router__pb2.BuildRouteRequest.FromString, 340 | response_serializer=routerrpc_dot_router__pb2.BuildRouteResponse.SerializeToString, 341 | ), 342 | 'SubscribeHtlcEvents': grpc.unary_stream_rpc_method_handler( 343 | servicer.SubscribeHtlcEvents, 344 | request_deserializer=routerrpc_dot_router__pb2.SubscribeHtlcEventsRequest.FromString, 345 | response_serializer=routerrpc_dot_router__pb2.HtlcEvent.SerializeToString, 346 | ), 347 | 'SendPayment': grpc.unary_stream_rpc_method_handler( 348 | servicer.SendPayment, 349 | request_deserializer=routerrpc_dot_router__pb2.SendPaymentRequest.FromString, 350 | response_serializer=routerrpc_dot_router__pb2.PaymentStatus.SerializeToString, 351 | ), 352 | 'TrackPayment': grpc.unary_stream_rpc_method_handler( 353 | servicer.TrackPayment, 354 | request_deserializer=routerrpc_dot_router__pb2.TrackPaymentRequest.FromString, 355 | response_serializer=routerrpc_dot_router__pb2.PaymentStatus.SerializeToString, 356 | ), 357 | 'HtlcInterceptor': grpc.stream_stream_rpc_method_handler( 358 | servicer.HtlcInterceptor, 359 | request_deserializer=routerrpc_dot_router__pb2.ForwardHtlcInterceptResponse.FromString, 360 | response_serializer=routerrpc_dot_router__pb2.ForwardHtlcInterceptRequest.SerializeToString, 361 | ), 362 | 'UpdateChanStatus': grpc.unary_unary_rpc_method_handler( 363 | servicer.UpdateChanStatus, 364 | request_deserializer=routerrpc_dot_router__pb2.UpdateChanStatusRequest.FromString, 365 | response_serializer=routerrpc_dot_router__pb2.UpdateChanStatusResponse.SerializeToString, 366 | ), 367 | } 368 | generic_handler = grpc.method_handlers_generic_handler( 369 | 'routerrpc.Router', rpc_method_handlers) 370 | server.add_generic_rpc_handlers((generic_handler,)) 371 | 372 | 373 | # This class is part of an EXPERIMENTAL API. 374 | class Router(object): 375 | """Router is a service that offers advanced interaction with the router 376 | subsystem of the daemon. 377 | """ 378 | 379 | @staticmethod 380 | def SendPaymentV2(request, 381 | target, 382 | options=(), 383 | channel_credentials=None, 384 | call_credentials=None, 385 | insecure=False, 386 | compression=None, 387 | wait_for_ready=None, 388 | timeout=None, 389 | metadata=None): 390 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SendPaymentV2', 391 | routerrpc_dot_router__pb2.SendPaymentRequest.SerializeToString, 392 | lightning__pb2.Payment.FromString, 393 | options, channel_credentials, 394 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 395 | 396 | @staticmethod 397 | def TrackPaymentV2(request, 398 | target, 399 | options=(), 400 | channel_credentials=None, 401 | call_credentials=None, 402 | insecure=False, 403 | compression=None, 404 | wait_for_ready=None, 405 | timeout=None, 406 | metadata=None): 407 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/TrackPaymentV2', 408 | routerrpc_dot_router__pb2.TrackPaymentRequest.SerializeToString, 409 | lightning__pb2.Payment.FromString, 410 | options, channel_credentials, 411 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 412 | 413 | @staticmethod 414 | def EstimateRouteFee(request, 415 | target, 416 | options=(), 417 | channel_credentials=None, 418 | call_credentials=None, 419 | insecure=False, 420 | compression=None, 421 | wait_for_ready=None, 422 | timeout=None, 423 | metadata=None): 424 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/EstimateRouteFee', 425 | routerrpc_dot_router__pb2.RouteFeeRequest.SerializeToString, 426 | routerrpc_dot_router__pb2.RouteFeeResponse.FromString, 427 | options, channel_credentials, 428 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 429 | 430 | @staticmethod 431 | def SendToRoute(request, 432 | target, 433 | options=(), 434 | channel_credentials=None, 435 | call_credentials=None, 436 | insecure=False, 437 | compression=None, 438 | wait_for_ready=None, 439 | timeout=None, 440 | metadata=None): 441 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SendToRoute', 442 | routerrpc_dot_router__pb2.SendToRouteRequest.SerializeToString, 443 | routerrpc_dot_router__pb2.SendToRouteResponse.FromString, 444 | options, channel_credentials, 445 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 446 | 447 | @staticmethod 448 | def SendToRouteV2(request, 449 | target, 450 | options=(), 451 | channel_credentials=None, 452 | call_credentials=None, 453 | insecure=False, 454 | compression=None, 455 | wait_for_ready=None, 456 | timeout=None, 457 | metadata=None): 458 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SendToRouteV2', 459 | routerrpc_dot_router__pb2.SendToRouteRequest.SerializeToString, 460 | lightning__pb2.HTLCAttempt.FromString, 461 | options, channel_credentials, 462 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 463 | 464 | @staticmethod 465 | def ResetMissionControl(request, 466 | target, 467 | options=(), 468 | channel_credentials=None, 469 | call_credentials=None, 470 | insecure=False, 471 | compression=None, 472 | wait_for_ready=None, 473 | timeout=None, 474 | metadata=None): 475 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/ResetMissionControl', 476 | routerrpc_dot_router__pb2.ResetMissionControlRequest.SerializeToString, 477 | routerrpc_dot_router__pb2.ResetMissionControlResponse.FromString, 478 | options, channel_credentials, 479 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 480 | 481 | @staticmethod 482 | def QueryMissionControl(request, 483 | target, 484 | options=(), 485 | channel_credentials=None, 486 | call_credentials=None, 487 | insecure=False, 488 | compression=None, 489 | wait_for_ready=None, 490 | timeout=None, 491 | metadata=None): 492 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/QueryMissionControl', 493 | routerrpc_dot_router__pb2.QueryMissionControlRequest.SerializeToString, 494 | routerrpc_dot_router__pb2.QueryMissionControlResponse.FromString, 495 | options, channel_credentials, 496 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 497 | 498 | @staticmethod 499 | def XImportMissionControl(request, 500 | target, 501 | options=(), 502 | channel_credentials=None, 503 | call_credentials=None, 504 | insecure=False, 505 | compression=None, 506 | wait_for_ready=None, 507 | timeout=None, 508 | metadata=None): 509 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/XImportMissionControl', 510 | routerrpc_dot_router__pb2.XImportMissionControlRequest.SerializeToString, 511 | routerrpc_dot_router__pb2.XImportMissionControlResponse.FromString, 512 | options, channel_credentials, 513 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 514 | 515 | @staticmethod 516 | def GetMissionControlConfig(request, 517 | target, 518 | options=(), 519 | channel_credentials=None, 520 | call_credentials=None, 521 | insecure=False, 522 | compression=None, 523 | wait_for_ready=None, 524 | timeout=None, 525 | metadata=None): 526 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/GetMissionControlConfig', 527 | routerrpc_dot_router__pb2.GetMissionControlConfigRequest.SerializeToString, 528 | routerrpc_dot_router__pb2.GetMissionControlConfigResponse.FromString, 529 | options, channel_credentials, 530 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 531 | 532 | @staticmethod 533 | def SetMissionControlConfig(request, 534 | target, 535 | options=(), 536 | channel_credentials=None, 537 | call_credentials=None, 538 | insecure=False, 539 | compression=None, 540 | wait_for_ready=None, 541 | timeout=None, 542 | metadata=None): 543 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/SetMissionControlConfig', 544 | routerrpc_dot_router__pb2.SetMissionControlConfigRequest.SerializeToString, 545 | routerrpc_dot_router__pb2.SetMissionControlConfigResponse.FromString, 546 | options, channel_credentials, 547 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 548 | 549 | @staticmethod 550 | def QueryProbability(request, 551 | target, 552 | options=(), 553 | channel_credentials=None, 554 | call_credentials=None, 555 | insecure=False, 556 | compression=None, 557 | wait_for_ready=None, 558 | timeout=None, 559 | metadata=None): 560 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/QueryProbability', 561 | routerrpc_dot_router__pb2.QueryProbabilityRequest.SerializeToString, 562 | routerrpc_dot_router__pb2.QueryProbabilityResponse.FromString, 563 | options, channel_credentials, 564 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 565 | 566 | @staticmethod 567 | def BuildRoute(request, 568 | target, 569 | options=(), 570 | channel_credentials=None, 571 | call_credentials=None, 572 | insecure=False, 573 | compression=None, 574 | wait_for_ready=None, 575 | timeout=None, 576 | metadata=None): 577 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/BuildRoute', 578 | routerrpc_dot_router__pb2.BuildRouteRequest.SerializeToString, 579 | routerrpc_dot_router__pb2.BuildRouteResponse.FromString, 580 | options, channel_credentials, 581 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 582 | 583 | @staticmethod 584 | def SubscribeHtlcEvents(request, 585 | target, 586 | options=(), 587 | channel_credentials=None, 588 | call_credentials=None, 589 | insecure=False, 590 | compression=None, 591 | wait_for_ready=None, 592 | timeout=None, 593 | metadata=None): 594 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SubscribeHtlcEvents', 595 | routerrpc_dot_router__pb2.SubscribeHtlcEventsRequest.SerializeToString, 596 | routerrpc_dot_router__pb2.HtlcEvent.FromString, 597 | options, channel_credentials, 598 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 599 | 600 | @staticmethod 601 | def SendPayment(request, 602 | target, 603 | options=(), 604 | channel_credentials=None, 605 | call_credentials=None, 606 | insecure=False, 607 | compression=None, 608 | wait_for_ready=None, 609 | timeout=None, 610 | metadata=None): 611 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/SendPayment', 612 | routerrpc_dot_router__pb2.SendPaymentRequest.SerializeToString, 613 | routerrpc_dot_router__pb2.PaymentStatus.FromString, 614 | options, channel_credentials, 615 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 616 | 617 | @staticmethod 618 | def TrackPayment(request, 619 | target, 620 | options=(), 621 | channel_credentials=None, 622 | call_credentials=None, 623 | insecure=False, 624 | compression=None, 625 | wait_for_ready=None, 626 | timeout=None, 627 | metadata=None): 628 | return grpc.experimental.unary_stream(request, target, '/routerrpc.Router/TrackPayment', 629 | routerrpc_dot_router__pb2.TrackPaymentRequest.SerializeToString, 630 | routerrpc_dot_router__pb2.PaymentStatus.FromString, 631 | options, channel_credentials, 632 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 633 | 634 | @staticmethod 635 | def HtlcInterceptor(request_iterator, 636 | target, 637 | options=(), 638 | channel_credentials=None, 639 | call_credentials=None, 640 | insecure=False, 641 | compression=None, 642 | wait_for_ready=None, 643 | timeout=None, 644 | metadata=None): 645 | return grpc.experimental.stream_stream(request_iterator, target, '/routerrpc.Router/HtlcInterceptor', 646 | routerrpc_dot_router__pb2.ForwardHtlcInterceptResponse.SerializeToString, 647 | routerrpc_dot_router__pb2.ForwardHtlcInterceptRequest.FromString, 648 | options, channel_credentials, 649 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 650 | 651 | @staticmethod 652 | def UpdateChanStatus(request, 653 | target, 654 | options=(), 655 | channel_credentials=None, 656 | call_credentials=None, 657 | insecure=False, 658 | compression=None, 659 | wait_for_ready=None, 660 | timeout=None, 661 | metadata=None): 662 | return grpc.experimental.unary_unary(request, target, '/routerrpc.Router/UpdateChanStatus', 663 | routerrpc_dot_router__pb2.UpdateChanStatusRequest.SerializeToString, 664 | routerrpc_dot_router__pb2.UpdateChanStatusResponse.FromString, 665 | options, channel_credentials, 666 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 667 | -------------------------------------------------------------------------------- /lnrpc_generated/signrpc/signer_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: signrpc/signer.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf import descriptor as _descriptor 6 | from google.protobuf import descriptor_pool as _descriptor_pool 7 | from google.protobuf import message as _message 8 | from google.protobuf import reflection as _reflection 9 | from google.protobuf import symbol_database as _symbol_database 10 | # @@protoc_insertion_point(imports) 11 | 12 | _sym_db = _symbol_database.Default() 13 | 14 | 15 | 16 | 17 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14signrpc/signer.proto\x12\x07signrpc\"3\n\nKeyLocator\x12\x12\n\nkey_family\x18\x01 \x01(\x05\x12\x11\n\tkey_index\x18\x02 \x01(\x05\"L\n\rKeyDescriptor\x12\x15\n\rraw_key_bytes\x18\x01 \x01(\x0c\x12$\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocator\")\n\x05TxOut\x12\r\n\x05value\x18\x01 \x01(\x03\x12\x11\n\tpk_script\x18\x02 \x01(\x0c\"\xdf\x01\n\x0eSignDescriptor\x12(\n\x08key_desc\x18\x01 \x01(\x0b\x32\x16.signrpc.KeyDescriptor\x12\x14\n\x0csingle_tweak\x18\x02 \x01(\x0c\x12\x14\n\x0c\x64ouble_tweak\x18\x03 \x01(\x0c\x12\x16\n\x0ewitness_script\x18\x04 \x01(\x0c\x12\x1e\n\x06output\x18\x05 \x01(\x0b\x32\x0e.signrpc.TxOut\x12\x0f\n\x07sighash\x18\x07 \x01(\r\x12\x13\n\x0binput_index\x18\x08 \x01(\x05\x12\x19\n\x11taproot_key_spend\x18\t \x01(\x08\"r\n\x07SignReq\x12\x14\n\x0craw_tx_bytes\x18\x01 \x01(\x0c\x12+\n\nsign_descs\x18\x02 \x03(\x0b\x32\x17.signrpc.SignDescriptor\x12$\n\x0cprev_outputs\x18\x03 \x03(\x0b\x32\x0e.signrpc.TxOut\"\x1c\n\x08SignResp\x12\x10\n\x08raw_sigs\x18\x01 \x03(\x0c\"2\n\x0bInputScript\x12\x0f\n\x07witness\x18\x01 \x03(\x0c\x12\x12\n\nsig_script\x18\x02 \x01(\x0c\">\n\x0fInputScriptResp\x12+\n\rinput_scripts\x18\x01 \x03(\x0b\x32\x14.signrpc.InputScript\"m\n\x0eSignMessageReq\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12$\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocator\x12\x13\n\x0b\x64ouble_hash\x18\x03 \x01(\x08\x12\x13\n\x0b\x63ompact_sig\x18\x04 \x01(\x08\"$\n\x0fSignMessageResp\x12\x11\n\tsignature\x18\x01 \x01(\x0c\"B\n\x10VerifyMessageReq\x12\x0b\n\x03msg\x18\x01 \x01(\x0c\x12\x11\n\tsignature\x18\x02 \x01(\x0c\x12\x0e\n\x06pubkey\x18\x03 \x01(\x0c\"\"\n\x11VerifyMessageResp\x12\r\n\x05valid\x18\x01 \x01(\x08\"\x80\x01\n\x10SharedKeyRequest\x12\x18\n\x10\x65phemeral_pubkey\x18\x01 \x01(\x0c\x12(\n\x07key_loc\x18\x02 \x01(\x0b\x32\x13.signrpc.KeyLocatorB\x02\x18\x01\x12(\n\x08key_desc\x18\x03 \x01(\x0b\x32\x16.signrpc.KeyDescriptor\"\'\n\x11SharedKeyResponse\x12\x12\n\nshared_key\x18\x01 \x01(\x0c\x32\xd4\x02\n\x06Signer\x12\x34\n\rSignOutputRaw\x12\x10.signrpc.SignReq\x1a\x11.signrpc.SignResp\x12@\n\x12\x43omputeInputScript\x12\x10.signrpc.SignReq\x1a\x18.signrpc.InputScriptResp\x12@\n\x0bSignMessage\x12\x17.signrpc.SignMessageReq\x1a\x18.signrpc.SignMessageResp\x12\x46\n\rVerifyMessage\x12\x19.signrpc.VerifyMessageReq\x1a\x1a.signrpc.VerifyMessageResp\x12H\n\x0f\x44\x65riveSharedKey\x12\x19.signrpc.SharedKeyRequest\x1a\x1a.signrpc.SharedKeyResponseB/Z-github.com/lightningnetwork/lnd/lnrpc/signrpcb\x06proto3') 18 | 19 | 20 | 21 | _KEYLOCATOR = DESCRIPTOR.message_types_by_name['KeyLocator'] 22 | _KEYDESCRIPTOR = DESCRIPTOR.message_types_by_name['KeyDescriptor'] 23 | _TXOUT = DESCRIPTOR.message_types_by_name['TxOut'] 24 | _SIGNDESCRIPTOR = DESCRIPTOR.message_types_by_name['SignDescriptor'] 25 | _SIGNREQ = DESCRIPTOR.message_types_by_name['SignReq'] 26 | _SIGNRESP = DESCRIPTOR.message_types_by_name['SignResp'] 27 | _INPUTSCRIPT = DESCRIPTOR.message_types_by_name['InputScript'] 28 | _INPUTSCRIPTRESP = DESCRIPTOR.message_types_by_name['InputScriptResp'] 29 | _SIGNMESSAGEREQ = DESCRIPTOR.message_types_by_name['SignMessageReq'] 30 | _SIGNMESSAGERESP = DESCRIPTOR.message_types_by_name['SignMessageResp'] 31 | _VERIFYMESSAGEREQ = DESCRIPTOR.message_types_by_name['VerifyMessageReq'] 32 | _VERIFYMESSAGERESP = DESCRIPTOR.message_types_by_name['VerifyMessageResp'] 33 | _SHAREDKEYREQUEST = DESCRIPTOR.message_types_by_name['SharedKeyRequest'] 34 | _SHAREDKEYRESPONSE = DESCRIPTOR.message_types_by_name['SharedKeyResponse'] 35 | KeyLocator = _reflection.GeneratedProtocolMessageType('KeyLocator', (_message.Message,), { 36 | 'DESCRIPTOR' : _KEYLOCATOR, 37 | '__module__' : 'signrpc.signer_pb2' 38 | # @@protoc_insertion_point(class_scope:signrpc.KeyLocator) 39 | }) 40 | _sym_db.RegisterMessage(KeyLocator) 41 | 42 | KeyDescriptor = _reflection.GeneratedProtocolMessageType('KeyDescriptor', (_message.Message,), { 43 | 'DESCRIPTOR' : _KEYDESCRIPTOR, 44 | '__module__' : 'signrpc.signer_pb2' 45 | # @@protoc_insertion_point(class_scope:signrpc.KeyDescriptor) 46 | }) 47 | _sym_db.RegisterMessage(KeyDescriptor) 48 | 49 | TxOut = _reflection.GeneratedProtocolMessageType('TxOut', (_message.Message,), { 50 | 'DESCRIPTOR' : _TXOUT, 51 | '__module__' : 'signrpc.signer_pb2' 52 | # @@protoc_insertion_point(class_scope:signrpc.TxOut) 53 | }) 54 | _sym_db.RegisterMessage(TxOut) 55 | 56 | SignDescriptor = _reflection.GeneratedProtocolMessageType('SignDescriptor', (_message.Message,), { 57 | 'DESCRIPTOR' : _SIGNDESCRIPTOR, 58 | '__module__' : 'signrpc.signer_pb2' 59 | # @@protoc_insertion_point(class_scope:signrpc.SignDescriptor) 60 | }) 61 | _sym_db.RegisterMessage(SignDescriptor) 62 | 63 | SignReq = _reflection.GeneratedProtocolMessageType('SignReq', (_message.Message,), { 64 | 'DESCRIPTOR' : _SIGNREQ, 65 | '__module__' : 'signrpc.signer_pb2' 66 | # @@protoc_insertion_point(class_scope:signrpc.SignReq) 67 | }) 68 | _sym_db.RegisterMessage(SignReq) 69 | 70 | SignResp = _reflection.GeneratedProtocolMessageType('SignResp', (_message.Message,), { 71 | 'DESCRIPTOR' : _SIGNRESP, 72 | '__module__' : 'signrpc.signer_pb2' 73 | # @@protoc_insertion_point(class_scope:signrpc.SignResp) 74 | }) 75 | _sym_db.RegisterMessage(SignResp) 76 | 77 | InputScript = _reflection.GeneratedProtocolMessageType('InputScript', (_message.Message,), { 78 | 'DESCRIPTOR' : _INPUTSCRIPT, 79 | '__module__' : 'signrpc.signer_pb2' 80 | # @@protoc_insertion_point(class_scope:signrpc.InputScript) 81 | }) 82 | _sym_db.RegisterMessage(InputScript) 83 | 84 | InputScriptResp = _reflection.GeneratedProtocolMessageType('InputScriptResp', (_message.Message,), { 85 | 'DESCRIPTOR' : _INPUTSCRIPTRESP, 86 | '__module__' : 'signrpc.signer_pb2' 87 | # @@protoc_insertion_point(class_scope:signrpc.InputScriptResp) 88 | }) 89 | _sym_db.RegisterMessage(InputScriptResp) 90 | 91 | SignMessageReq = _reflection.GeneratedProtocolMessageType('SignMessageReq', (_message.Message,), { 92 | 'DESCRIPTOR' : _SIGNMESSAGEREQ, 93 | '__module__' : 'signrpc.signer_pb2' 94 | # @@protoc_insertion_point(class_scope:signrpc.SignMessageReq) 95 | }) 96 | _sym_db.RegisterMessage(SignMessageReq) 97 | 98 | SignMessageResp = _reflection.GeneratedProtocolMessageType('SignMessageResp', (_message.Message,), { 99 | 'DESCRIPTOR' : _SIGNMESSAGERESP, 100 | '__module__' : 'signrpc.signer_pb2' 101 | # @@protoc_insertion_point(class_scope:signrpc.SignMessageResp) 102 | }) 103 | _sym_db.RegisterMessage(SignMessageResp) 104 | 105 | VerifyMessageReq = _reflection.GeneratedProtocolMessageType('VerifyMessageReq', (_message.Message,), { 106 | 'DESCRIPTOR' : _VERIFYMESSAGEREQ, 107 | '__module__' : 'signrpc.signer_pb2' 108 | # @@protoc_insertion_point(class_scope:signrpc.VerifyMessageReq) 109 | }) 110 | _sym_db.RegisterMessage(VerifyMessageReq) 111 | 112 | VerifyMessageResp = _reflection.GeneratedProtocolMessageType('VerifyMessageResp', (_message.Message,), { 113 | 'DESCRIPTOR' : _VERIFYMESSAGERESP, 114 | '__module__' : 'signrpc.signer_pb2' 115 | # @@protoc_insertion_point(class_scope:signrpc.VerifyMessageResp) 116 | }) 117 | _sym_db.RegisterMessage(VerifyMessageResp) 118 | 119 | SharedKeyRequest = _reflection.GeneratedProtocolMessageType('SharedKeyRequest', (_message.Message,), { 120 | 'DESCRIPTOR' : _SHAREDKEYREQUEST, 121 | '__module__' : 'signrpc.signer_pb2' 122 | # @@protoc_insertion_point(class_scope:signrpc.SharedKeyRequest) 123 | }) 124 | _sym_db.RegisterMessage(SharedKeyRequest) 125 | 126 | SharedKeyResponse = _reflection.GeneratedProtocolMessageType('SharedKeyResponse', (_message.Message,), { 127 | 'DESCRIPTOR' : _SHAREDKEYRESPONSE, 128 | '__module__' : 'signrpc.signer_pb2' 129 | # @@protoc_insertion_point(class_scope:signrpc.SharedKeyResponse) 130 | }) 131 | _sym_db.RegisterMessage(SharedKeyResponse) 132 | 133 | _SIGNER = DESCRIPTOR.services_by_name['Signer'] 134 | if _descriptor._USE_C_DESCRIPTORS == False: 135 | 136 | DESCRIPTOR._options = None 137 | DESCRIPTOR._serialized_options = b'Z-github.com/lightningnetwork/lnd/lnrpc/signrpc' 138 | _SHAREDKEYREQUEST.fields_by_name['key_loc']._options = None 139 | _SHAREDKEYREQUEST.fields_by_name['key_loc']._serialized_options = b'\030\001' 140 | _KEYLOCATOR._serialized_start=33 141 | _KEYLOCATOR._serialized_end=84 142 | _KEYDESCRIPTOR._serialized_start=86 143 | _KEYDESCRIPTOR._serialized_end=162 144 | _TXOUT._serialized_start=164 145 | _TXOUT._serialized_end=205 146 | _SIGNDESCRIPTOR._serialized_start=208 147 | _SIGNDESCRIPTOR._serialized_end=431 148 | _SIGNREQ._serialized_start=433 149 | _SIGNREQ._serialized_end=547 150 | _SIGNRESP._serialized_start=549 151 | _SIGNRESP._serialized_end=577 152 | _INPUTSCRIPT._serialized_start=579 153 | _INPUTSCRIPT._serialized_end=629 154 | _INPUTSCRIPTRESP._serialized_start=631 155 | _INPUTSCRIPTRESP._serialized_end=693 156 | _SIGNMESSAGEREQ._serialized_start=695 157 | _SIGNMESSAGEREQ._serialized_end=804 158 | _SIGNMESSAGERESP._serialized_start=806 159 | _SIGNMESSAGERESP._serialized_end=842 160 | _VERIFYMESSAGEREQ._serialized_start=844 161 | _VERIFYMESSAGEREQ._serialized_end=910 162 | _VERIFYMESSAGERESP._serialized_start=912 163 | _VERIFYMESSAGERESP._serialized_end=946 164 | _SHAREDKEYREQUEST._serialized_start=949 165 | _SHAREDKEYREQUEST._serialized_end=1077 166 | _SHAREDKEYRESPONSE._serialized_start=1079 167 | _SHAREDKEYRESPONSE._serialized_end=1118 168 | _SIGNER._serialized_start=1121 169 | _SIGNER._serialized_end=1461 170 | # @@protoc_insertion_point(module_scope) 171 | -------------------------------------------------------------------------------- /lnrpc_generated/signrpc/signer_pb2_grpc.py: -------------------------------------------------------------------------------- 1 | # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! 2 | """Client and server classes corresponding to protobuf-defined services.""" 3 | import grpc 4 | 5 | from lnrpc_generated.signrpc import signer_pb2 as signrpc_dot_signer__pb2 6 | 7 | 8 | class SignerStub(object): 9 | """Signer is a service that gives access to the signing functionality of the 10 | daemon's wallet. 11 | """ 12 | 13 | def __init__(self, channel): 14 | """Constructor. 15 | 16 | Args: 17 | channel: A grpc.Channel. 18 | """ 19 | self.SignOutputRaw = channel.unary_unary( 20 | '/signrpc.Signer/SignOutputRaw', 21 | request_serializer=signrpc_dot_signer__pb2.SignReq.SerializeToString, 22 | response_deserializer=signrpc_dot_signer__pb2.SignResp.FromString, 23 | ) 24 | self.ComputeInputScript = channel.unary_unary( 25 | '/signrpc.Signer/ComputeInputScript', 26 | request_serializer=signrpc_dot_signer__pb2.SignReq.SerializeToString, 27 | response_deserializer=signrpc_dot_signer__pb2.InputScriptResp.FromString, 28 | ) 29 | self.SignMessage = channel.unary_unary( 30 | '/signrpc.Signer/SignMessage', 31 | request_serializer=signrpc_dot_signer__pb2.SignMessageReq.SerializeToString, 32 | response_deserializer=signrpc_dot_signer__pb2.SignMessageResp.FromString, 33 | ) 34 | self.VerifyMessage = channel.unary_unary( 35 | '/signrpc.Signer/VerifyMessage', 36 | request_serializer=signrpc_dot_signer__pb2.VerifyMessageReq.SerializeToString, 37 | response_deserializer=signrpc_dot_signer__pb2.VerifyMessageResp.FromString, 38 | ) 39 | self.DeriveSharedKey = channel.unary_unary( 40 | '/signrpc.Signer/DeriveSharedKey', 41 | request_serializer=signrpc_dot_signer__pb2.SharedKeyRequest.SerializeToString, 42 | response_deserializer=signrpc_dot_signer__pb2.SharedKeyResponse.FromString, 43 | ) 44 | 45 | 46 | class SignerServicer(object): 47 | """Signer is a service that gives access to the signing functionality of the 48 | daemon's wallet. 49 | """ 50 | 51 | def SignOutputRaw(self, request, context): 52 | """ 53 | SignOutputRaw is a method that can be used to generated a signature for a 54 | set of inputs/outputs to a transaction. Each request specifies details 55 | concerning how the outputs should be signed, which keys they should be 56 | signed with, and also any optional tweaks. The return value is a fixed 57 | 64-byte signature (the same format as we use on the wire in Lightning). 58 | 59 | If we are unable to sign using the specified keys, then an error will be 60 | returned. 61 | """ 62 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 63 | context.set_details('Method not implemented!') 64 | raise NotImplementedError('Method not implemented!') 65 | 66 | def ComputeInputScript(self, request, context): 67 | """ 68 | ComputeInputScript generates a complete InputIndex for the passed 69 | transaction with the signature as defined within the passed SignDescriptor. 70 | This method should be capable of generating the proper input script for 71 | both regular p2wkh output and p2wkh outputs nested within a regular p2sh 72 | output. 73 | 74 | Note that when using this method to sign inputs belonging to the wallet, 75 | the only items of the SignDescriptor that need to be populated are pkScript 76 | in the TxOut field, the value in that same field, and finally the input 77 | index. 78 | """ 79 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 80 | context.set_details('Method not implemented!') 81 | raise NotImplementedError('Method not implemented!') 82 | 83 | def SignMessage(self, request, context): 84 | """ 85 | SignMessage signs a message with the key specified in the key locator. The 86 | returned signature is fixed-size LN wire format encoded. 87 | 88 | The main difference to SignMessage in the main RPC is that a specific key is 89 | used to sign the message instead of the node identity private key. 90 | """ 91 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 92 | context.set_details('Method not implemented!') 93 | raise NotImplementedError('Method not implemented!') 94 | 95 | def VerifyMessage(self, request, context): 96 | """ 97 | VerifyMessage verifies a signature over a message using the public key 98 | provided. The signature must be fixed-size LN wire format encoded. 99 | 100 | The main difference to VerifyMessage in the main RPC is that the public key 101 | used to sign the message does not have to be a node known to the network. 102 | """ 103 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 104 | context.set_details('Method not implemented!') 105 | raise NotImplementedError('Method not implemented!') 106 | 107 | def DeriveSharedKey(self, request, context): 108 | """ 109 | DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key 110 | derivation between the ephemeral public key in the request and the node's 111 | key specified in the key_desc parameter. Either a key locator or a raw 112 | public key is expected in the key_desc, if neither is supplied, defaults to 113 | the node's identity private key: 114 | P_shared = privKeyNode * ephemeralPubkey 115 | The resulting shared public key is serialized in the compressed format and 116 | hashed with sha256, resulting in the final key length of 256bit. 117 | """ 118 | context.set_code(grpc.StatusCode.UNIMPLEMENTED) 119 | context.set_details('Method not implemented!') 120 | raise NotImplementedError('Method not implemented!') 121 | 122 | 123 | def add_SignerServicer_to_server(servicer, server): 124 | rpc_method_handlers = { 125 | 'SignOutputRaw': grpc.unary_unary_rpc_method_handler( 126 | servicer.SignOutputRaw, 127 | request_deserializer=signrpc_dot_signer__pb2.SignReq.FromString, 128 | response_serializer=signrpc_dot_signer__pb2.SignResp.SerializeToString, 129 | ), 130 | 'ComputeInputScript': grpc.unary_unary_rpc_method_handler( 131 | servicer.ComputeInputScript, 132 | request_deserializer=signrpc_dot_signer__pb2.SignReq.FromString, 133 | response_serializer=signrpc_dot_signer__pb2.InputScriptResp.SerializeToString, 134 | ), 135 | 'SignMessage': grpc.unary_unary_rpc_method_handler( 136 | servicer.SignMessage, 137 | request_deserializer=signrpc_dot_signer__pb2.SignMessageReq.FromString, 138 | response_serializer=signrpc_dot_signer__pb2.SignMessageResp.SerializeToString, 139 | ), 140 | 'VerifyMessage': grpc.unary_unary_rpc_method_handler( 141 | servicer.VerifyMessage, 142 | request_deserializer=signrpc_dot_signer__pb2.VerifyMessageReq.FromString, 143 | response_serializer=signrpc_dot_signer__pb2.VerifyMessageResp.SerializeToString, 144 | ), 145 | 'DeriveSharedKey': grpc.unary_unary_rpc_method_handler( 146 | servicer.DeriveSharedKey, 147 | request_deserializer=signrpc_dot_signer__pb2.SharedKeyRequest.FromString, 148 | response_serializer=signrpc_dot_signer__pb2.SharedKeyResponse.SerializeToString, 149 | ), 150 | } 151 | generic_handler = grpc.method_handlers_generic_handler( 152 | 'signrpc.Signer', rpc_method_handlers) 153 | server.add_generic_rpc_handlers((generic_handler,)) 154 | 155 | 156 | # This class is part of an EXPERIMENTAL API. 157 | class Signer(object): 158 | """Signer is a service that gives access to the signing functionality of the 159 | daemon's wallet. 160 | """ 161 | 162 | @staticmethod 163 | def SignOutputRaw(request, 164 | target, 165 | options=(), 166 | channel_credentials=None, 167 | call_credentials=None, 168 | insecure=False, 169 | compression=None, 170 | wait_for_ready=None, 171 | timeout=None, 172 | metadata=None): 173 | return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/SignOutputRaw', 174 | signrpc_dot_signer__pb2.SignReq.SerializeToString, 175 | signrpc_dot_signer__pb2.SignResp.FromString, 176 | options, channel_credentials, 177 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 178 | 179 | @staticmethod 180 | def ComputeInputScript(request, 181 | target, 182 | options=(), 183 | channel_credentials=None, 184 | call_credentials=None, 185 | insecure=False, 186 | compression=None, 187 | wait_for_ready=None, 188 | timeout=None, 189 | metadata=None): 190 | return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/ComputeInputScript', 191 | signrpc_dot_signer__pb2.SignReq.SerializeToString, 192 | signrpc_dot_signer__pb2.InputScriptResp.FromString, 193 | options, channel_credentials, 194 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 195 | 196 | @staticmethod 197 | def SignMessage(request, 198 | target, 199 | options=(), 200 | channel_credentials=None, 201 | call_credentials=None, 202 | insecure=False, 203 | compression=None, 204 | wait_for_ready=None, 205 | timeout=None, 206 | metadata=None): 207 | return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/SignMessage', 208 | signrpc_dot_signer__pb2.SignMessageReq.SerializeToString, 209 | signrpc_dot_signer__pb2.SignMessageResp.FromString, 210 | options, channel_credentials, 211 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 212 | 213 | @staticmethod 214 | def VerifyMessage(request, 215 | target, 216 | options=(), 217 | channel_credentials=None, 218 | call_credentials=None, 219 | insecure=False, 220 | compression=None, 221 | wait_for_ready=None, 222 | timeout=None, 223 | metadata=None): 224 | return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/VerifyMessage', 225 | signrpc_dot_signer__pb2.VerifyMessageReq.SerializeToString, 226 | signrpc_dot_signer__pb2.VerifyMessageResp.FromString, 227 | options, channel_credentials, 228 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 229 | 230 | @staticmethod 231 | def DeriveSharedKey(request, 232 | target, 233 | options=(), 234 | channel_credentials=None, 235 | call_credentials=None, 236 | insecure=False, 237 | compression=None, 238 | wait_for_ready=None, 239 | timeout=None, 240 | metadata=None): 241 | return grpc.experimental.unary_unary(request, target, '/signrpc.Signer/DeriveSharedKey', 242 | signrpc_dot_signer__pb2.SharedKeyRequest.SerializeToString, 243 | signrpc_dot_signer__pb2.SharedKeyResponse.FromString, 244 | options, channel_credentials, 245 | insecure, call_credentials, compression, wait_for_ready, timeout, metadata) 246 | -------------------------------------------------------------------------------- /lnrpc_generated/walletrpc/walletkit_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # source: walletrpc/walletkit.proto 4 | """Generated protocol buffer code.""" 5 | from google.protobuf.internal import enum_type_wrapper 6 | from google.protobuf import descriptor as _descriptor 7 | from google.protobuf import descriptor_pool as _descriptor_pool 8 | from google.protobuf import message as _message 9 | from google.protobuf import reflection as _reflection 10 | from google.protobuf import symbol_database as _symbol_database 11 | # @@protoc_insertion_point(imports) 12 | 13 | _sym_db = _symbol_database.Default() 14 | 15 | 16 | from lnrpc_generated import lightning_pb2 as lightning__pb2 17 | from lnrpc_generated.signrpc import signer_pb2 as signrpc_dot_signer__pb2 18 | 19 | 20 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19walletrpc/walletkit.proto\x12\twalletrpc\x1a\x0flightning.proto\x1a\x14signrpc/signer.proto\"e\n\x12ListUnspentRequest\x12\x11\n\tmin_confs\x18\x01 \x01(\x05\x12\x11\n\tmax_confs\x18\x02 \x01(\x05\x12\x0f\n\x07\x61\x63\x63ount\x18\x03 \x01(\t\x12\x18\n\x10unconfirmed_only\x18\x04 \x01(\x08\"1\n\x13ListUnspentResponse\x12\x1a\n\x05utxos\x18\x01 \x03(\x0b\x32\x0b.lnrpc.Utxo\"_\n\x12LeaseOutputRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x1a\n\x12\x65xpiration_seconds\x18\x03 \x01(\x04\")\n\x13LeaseOutputResponse\x12\x12\n\nexpiration\x18\x01 \x01(\x04\"E\n\x14ReleaseOutputRequest\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\"\x17\n\x15ReleaseOutputResponse\"6\n\x06KeyReq\x12\x18\n\x10key_finger_print\x18\x01 \x01(\x05\x12\x12\n\nkey_family\x18\x02 \x01(\x05\"T\n\x0b\x41\x64\x64rRequest\x12\x0f\n\x07\x61\x63\x63ount\x18\x01 \x01(\t\x12$\n\x04type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x0e\n\x06\x63hange\x18\x03 \x01(\x08\"\x1c\n\x0c\x41\x64\x64rResponse\x12\x0c\n\x04\x61\x64\x64r\x18\x01 \x01(\t\"\xe7\x01\n\x07\x41\x63\x63ount\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x1b\n\x13\x65xtended_public_key\x18\x03 \x01(\t\x12\x1e\n\x16master_key_fingerprint\x18\x04 \x01(\x0c\x12\x17\n\x0f\x64\x65rivation_path\x18\x05 \x01(\t\x12\x1a\n\x12\x65xternal_key_count\x18\x06 \x01(\r\x12\x1a\n\x12internal_key_count\x18\x07 \x01(\r\x12\x12\n\nwatch_only\x18\x08 \x01(\x08\"Q\n\x13ListAccountsRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\"<\n\x14ListAccountsResponse\x12$\n\x08\x61\x63\x63ounts\x18\x01 \x03(\x0b\x32\x12.walletrpc.Account\"\xa0\x01\n\x14ImportAccountRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x1b\n\x13\x65xtended_public_key\x18\x02 \x01(\t\x12\x1e\n\x16master_key_fingerprint\x18\x03 \x01(\x0c\x12,\n\x0c\x61\x64\x64ress_type\x18\x04 \x01(\x0e\x32\x16.walletrpc.AddressType\x12\x0f\n\x07\x64ry_run\x18\x05 \x01(\x08\"|\n\x15ImportAccountResponse\x12#\n\x07\x61\x63\x63ount\x18\x01 \x01(\x0b\x32\x12.walletrpc.Account\x12\x1e\n\x16\x64ry_run_external_addrs\x18\x02 \x03(\t\x12\x1e\n\x16\x64ry_run_internal_addrs\x18\x03 \x03(\t\"Z\n\x16ImportPublicKeyRequest\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12,\n\x0c\x61\x64\x64ress_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.AddressType\"\x19\n\x17ImportPublicKeyResponse\",\n\x0bTransaction\x12\x0e\n\x06tx_hex\x18\x01 \x01(\x0c\x12\r\n\x05label\x18\x02 \x01(\t\"(\n\x0fPublishResponse\x12\x15\n\rpublish_error\x18\x01 \x01(\t\"\x86\x01\n\x12SendOutputsRequest\x12\x12\n\nsat_per_kw\x18\x01 \x01(\x03\x12\x1f\n\x07outputs\x18\x02 \x03(\x0b\x32\x0e.signrpc.TxOut\x12\r\n\x05label\x18\x03 \x01(\t\x12\x11\n\tmin_confs\x18\x04 \x01(\x05\x12\x19\n\x11spend_unconfirmed\x18\x05 \x01(\x08\"%\n\x13SendOutputsResponse\x12\x0e\n\x06raw_tx\x18\x01 \x01(\x0c\")\n\x12\x45stimateFeeRequest\x12\x13\n\x0b\x63onf_target\x18\x01 \x01(\x05\")\n\x13\x45stimateFeeResponse\x12\x12\n\nsat_per_kw\x18\x01 \x01(\x03\"\xd2\x02\n\x0cPendingSweep\x12!\n\x08outpoint\x18\x01 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12,\n\x0cwitness_type\x18\x02 \x01(\x0e\x32\x16.walletrpc.WitnessType\x12\x12\n\namount_sat\x18\x03 \x01(\r\x12\x18\n\x0csat_per_byte\x18\x04 \x01(\rB\x02\x18\x01\x12\x1a\n\x12\x62roadcast_attempts\x18\x05 \x01(\r\x12\x1d\n\x15next_broadcast_height\x18\x06 \x01(\r\x12\x1d\n\x15requested_conf_target\x18\x08 \x01(\r\x12\"\n\x16requested_sat_per_byte\x18\t \x01(\rB\x02\x18\x01\x12\x15\n\rsat_per_vbyte\x18\n \x01(\x04\x12\x1f\n\x17requested_sat_per_vbyte\x18\x0b \x01(\x04\x12\r\n\x05\x66orce\x18\x07 \x01(\x08\"\x16\n\x14PendingSweepsRequest\"H\n\x15PendingSweepsResponse\x12/\n\x0epending_sweeps\x18\x01 \x03(\x0b\x32\x17.walletrpc.PendingSweep\"\x88\x01\n\x0e\x42umpFeeRequest\x12!\n\x08outpoint\x18\x01 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x13\n\x0btarget_conf\x18\x02 \x01(\r\x12\x18\n\x0csat_per_byte\x18\x03 \x01(\rB\x02\x18\x01\x12\r\n\x05\x66orce\x18\x04 \x01(\x08\x12\x15\n\rsat_per_vbyte\x18\x05 \x01(\x04\"\x11\n\x0f\x42umpFeeResponse\"$\n\x11ListSweepsRequest\x12\x0f\n\x07verbose\x18\x01 \x01(\x08\"\xcc\x01\n\x12ListSweepsResponse\x12\x38\n\x13transaction_details\x18\x01 \x01(\x0b\x32\x19.lnrpc.TransactionDetailsH\x00\x12G\n\x0ftransaction_ids\x18\x02 \x01(\x0b\x32,.walletrpc.ListSweepsResponse.TransactionIDsH\x00\x1a)\n\x0eTransactionIDs\x12\x17\n\x0ftransaction_ids\x18\x01 \x03(\tB\x08\n\x06sweeps\"I\n\x17LabelTransactionRequest\x12\x0c\n\x04txid\x18\x01 \x01(\x0c\x12\r\n\x05label\x18\x02 \x01(\t\x12\x11\n\toverwrite\x18\x03 \x01(\x08\"\x1a\n\x18LabelTransactionResponse\"\xca\x01\n\x0f\x46undPsbtRequest\x12\x0e\n\x04psbt\x18\x01 \x01(\x0cH\x00\x12$\n\x03raw\x18\x02 \x01(\x0b\x32\x15.walletrpc.TxTemplateH\x00\x12\x15\n\x0btarget_conf\x18\x03 \x01(\rH\x01\x12\x17\n\rsat_per_vbyte\x18\x04 \x01(\x04H\x01\x12\x0f\n\x07\x61\x63\x63ount\x18\x05 \x01(\t\x12\x11\n\tmin_confs\x18\x06 \x01(\x05\x12\x19\n\x11spend_unconfirmed\x18\x07 \x01(\x08\x42\n\n\x08templateB\x06\n\x04\x66\x65\x65s\"p\n\x10\x46undPsbtResponse\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\x12\x1b\n\x13\x63hange_output_index\x18\x02 \x01(\x05\x12*\n\x0clocked_utxos\x18\x03 \x03(\x0b\x32\x14.walletrpc.UtxoLease\"\x92\x01\n\nTxTemplate\x12\x1f\n\x06inputs\x18\x01 \x03(\x0b\x32\x0f.lnrpc.OutPoint\x12\x33\n\x07outputs\x18\x02 \x03(\x0b\x32\".walletrpc.TxTemplate.OutputsEntry\x1a.\n\x0cOutputsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\"N\n\tUtxoLease\x12\n\n\x02id\x18\x01 \x01(\x0c\x12!\n\x08outpoint\x18\x02 \x01(\x0b\x32\x0f.lnrpc.OutPoint\x12\x12\n\nexpiration\x18\x03 \x01(\x04\"&\n\x0fSignPsbtRequest\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\"\'\n\x10SignPsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\x0c\";\n\x13\x46inalizePsbtRequest\x12\x13\n\x0b\x66unded_psbt\x18\x01 \x01(\x0c\x12\x0f\n\x07\x61\x63\x63ount\x18\x05 \x01(\t\"A\n\x14\x46inalizePsbtResponse\x12\x13\n\x0bsigned_psbt\x18\x01 \x01(\x0c\x12\x14\n\x0craw_final_tx\x18\x02 \x01(\x0c\"\x13\n\x11ListLeasesRequest\"@\n\x12ListLeasesResponse\x12*\n\x0clocked_utxos\x18\x01 \x03(\x0b\x32\x14.walletrpc.UtxoLease*\x8e\x01\n\x0b\x41\x64\x64ressType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x17\n\x13WITNESS_PUBKEY_HASH\x10\x01\x12\x1e\n\x1aNESTED_WITNESS_PUBKEY_HASH\x10\x02\x12%\n!HYBRID_NESTED_WITNESS_PUBKEY_HASH\x10\x03\x12\x12\n\x0eTAPROOT_PUBKEY\x10\x04*\x99\x03\n\x0bWitnessType\x12\x13\n\x0fUNKNOWN_WITNESS\x10\x00\x12\x18\n\x14\x43OMMITMENT_TIME_LOCK\x10\x01\x12\x17\n\x13\x43OMMITMENT_NO_DELAY\x10\x02\x12\x15\n\x11\x43OMMITMENT_REVOKE\x10\x03\x12\x17\n\x13HTLC_OFFERED_REVOKE\x10\x04\x12\x18\n\x14HTLC_ACCEPTED_REVOKE\x10\x05\x12%\n!HTLC_OFFERED_TIMEOUT_SECOND_LEVEL\x10\x06\x12&\n\"HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL\x10\x07\x12\x1f\n\x1bHTLC_OFFERED_REMOTE_TIMEOUT\x10\x08\x12 \n\x1cHTLC_ACCEPTED_REMOTE_SUCCESS\x10\t\x12\x1c\n\x18HTLC_SECOND_LEVEL_REVOKE\x10\n\x12\x14\n\x10WITNESS_KEY_HASH\x10\x0b\x12\x1b\n\x17NESTED_WITNESS_KEY_HASH\x10\x0c\x12\x15\n\x11\x43OMMITMENT_ANCHOR\x10\r2\xf7\x0b\n\tWalletKit\x12L\n\x0bListUnspent\x12\x1d.walletrpc.ListUnspentRequest\x1a\x1e.walletrpc.ListUnspentResponse\x12L\n\x0bLeaseOutput\x12\x1d.walletrpc.LeaseOutputRequest\x1a\x1e.walletrpc.LeaseOutputResponse\x12R\n\rReleaseOutput\x12\x1f.walletrpc.ReleaseOutputRequest\x1a .walletrpc.ReleaseOutputResponse\x12I\n\nListLeases\x12\x1c.walletrpc.ListLeasesRequest\x1a\x1d.walletrpc.ListLeasesResponse\x12:\n\rDeriveNextKey\x12\x11.walletrpc.KeyReq\x1a\x16.signrpc.KeyDescriptor\x12\x38\n\tDeriveKey\x12\x13.signrpc.KeyLocator\x1a\x16.signrpc.KeyDescriptor\x12;\n\x08NextAddr\x12\x16.walletrpc.AddrRequest\x1a\x17.walletrpc.AddrResponse\x12O\n\x0cListAccounts\x12\x1e.walletrpc.ListAccountsRequest\x1a\x1f.walletrpc.ListAccountsResponse\x12R\n\rImportAccount\x12\x1f.walletrpc.ImportAccountRequest\x1a .walletrpc.ImportAccountResponse\x12X\n\x0fImportPublicKey\x12!.walletrpc.ImportPublicKeyRequest\x1a\".walletrpc.ImportPublicKeyResponse\x12H\n\x12PublishTransaction\x12\x16.walletrpc.Transaction\x1a\x1a.walletrpc.PublishResponse\x12L\n\x0bSendOutputs\x12\x1d.walletrpc.SendOutputsRequest\x1a\x1e.walletrpc.SendOutputsResponse\x12L\n\x0b\x45stimateFee\x12\x1d.walletrpc.EstimateFeeRequest\x1a\x1e.walletrpc.EstimateFeeResponse\x12R\n\rPendingSweeps\x12\x1f.walletrpc.PendingSweepsRequest\x1a .walletrpc.PendingSweepsResponse\x12@\n\x07\x42umpFee\x12\x19.walletrpc.BumpFeeRequest\x1a\x1a.walletrpc.BumpFeeResponse\x12I\n\nListSweeps\x12\x1c.walletrpc.ListSweepsRequest\x1a\x1d.walletrpc.ListSweepsResponse\x12[\n\x10LabelTransaction\x12\".walletrpc.LabelTransactionRequest\x1a#.walletrpc.LabelTransactionResponse\x12\x43\n\x08\x46undPsbt\x12\x1a.walletrpc.FundPsbtRequest\x1a\x1b.walletrpc.FundPsbtResponse\x12\x43\n\x08SignPsbt\x12\x1a.walletrpc.SignPsbtRequest\x1a\x1b.walletrpc.SignPsbtResponse\x12O\n\x0c\x46inalizePsbt\x12\x1e.walletrpc.FinalizePsbtRequest\x1a\x1f.walletrpc.FinalizePsbtResponseB1Z/github.com/lightningnetwork/lnd/lnrpc/walletrpcb\x06proto3') 21 | 22 | _ADDRESSTYPE = DESCRIPTOR.enum_types_by_name['AddressType'] 23 | AddressType = enum_type_wrapper.EnumTypeWrapper(_ADDRESSTYPE) 24 | _WITNESSTYPE = DESCRIPTOR.enum_types_by_name['WitnessType'] 25 | WitnessType = enum_type_wrapper.EnumTypeWrapper(_WITNESSTYPE) 26 | UNKNOWN = 0 27 | WITNESS_PUBKEY_HASH = 1 28 | NESTED_WITNESS_PUBKEY_HASH = 2 29 | HYBRID_NESTED_WITNESS_PUBKEY_HASH = 3 30 | TAPROOT_PUBKEY = 4 31 | UNKNOWN_WITNESS = 0 32 | COMMITMENT_TIME_LOCK = 1 33 | COMMITMENT_NO_DELAY = 2 34 | COMMITMENT_REVOKE = 3 35 | HTLC_OFFERED_REVOKE = 4 36 | HTLC_ACCEPTED_REVOKE = 5 37 | HTLC_OFFERED_TIMEOUT_SECOND_LEVEL = 6 38 | HTLC_ACCEPTED_SUCCESS_SECOND_LEVEL = 7 39 | HTLC_OFFERED_REMOTE_TIMEOUT = 8 40 | HTLC_ACCEPTED_REMOTE_SUCCESS = 9 41 | HTLC_SECOND_LEVEL_REVOKE = 10 42 | WITNESS_KEY_HASH = 11 43 | NESTED_WITNESS_KEY_HASH = 12 44 | COMMITMENT_ANCHOR = 13 45 | 46 | 47 | _LISTUNSPENTREQUEST = DESCRIPTOR.message_types_by_name['ListUnspentRequest'] 48 | _LISTUNSPENTRESPONSE = DESCRIPTOR.message_types_by_name['ListUnspentResponse'] 49 | _LEASEOUTPUTREQUEST = DESCRIPTOR.message_types_by_name['LeaseOutputRequest'] 50 | _LEASEOUTPUTRESPONSE = DESCRIPTOR.message_types_by_name['LeaseOutputResponse'] 51 | _RELEASEOUTPUTREQUEST = DESCRIPTOR.message_types_by_name['ReleaseOutputRequest'] 52 | _RELEASEOUTPUTRESPONSE = DESCRIPTOR.message_types_by_name['ReleaseOutputResponse'] 53 | _KEYREQ = DESCRIPTOR.message_types_by_name['KeyReq'] 54 | _ADDRREQUEST = DESCRIPTOR.message_types_by_name['AddrRequest'] 55 | _ADDRRESPONSE = DESCRIPTOR.message_types_by_name['AddrResponse'] 56 | _ACCOUNT = DESCRIPTOR.message_types_by_name['Account'] 57 | _LISTACCOUNTSREQUEST = DESCRIPTOR.message_types_by_name['ListAccountsRequest'] 58 | _LISTACCOUNTSRESPONSE = DESCRIPTOR.message_types_by_name['ListAccountsResponse'] 59 | _IMPORTACCOUNTREQUEST = DESCRIPTOR.message_types_by_name['ImportAccountRequest'] 60 | _IMPORTACCOUNTRESPONSE = DESCRIPTOR.message_types_by_name['ImportAccountResponse'] 61 | _IMPORTPUBLICKEYREQUEST = DESCRIPTOR.message_types_by_name['ImportPublicKeyRequest'] 62 | _IMPORTPUBLICKEYRESPONSE = DESCRIPTOR.message_types_by_name['ImportPublicKeyResponse'] 63 | _TRANSACTION = DESCRIPTOR.message_types_by_name['Transaction'] 64 | _PUBLISHRESPONSE = DESCRIPTOR.message_types_by_name['PublishResponse'] 65 | _SENDOUTPUTSREQUEST = DESCRIPTOR.message_types_by_name['SendOutputsRequest'] 66 | _SENDOUTPUTSRESPONSE = DESCRIPTOR.message_types_by_name['SendOutputsResponse'] 67 | _ESTIMATEFEEREQUEST = DESCRIPTOR.message_types_by_name['EstimateFeeRequest'] 68 | _ESTIMATEFEERESPONSE = DESCRIPTOR.message_types_by_name['EstimateFeeResponse'] 69 | _PENDINGSWEEP = DESCRIPTOR.message_types_by_name['PendingSweep'] 70 | _PENDINGSWEEPSREQUEST = DESCRIPTOR.message_types_by_name['PendingSweepsRequest'] 71 | _PENDINGSWEEPSRESPONSE = DESCRIPTOR.message_types_by_name['PendingSweepsResponse'] 72 | _BUMPFEEREQUEST = DESCRIPTOR.message_types_by_name['BumpFeeRequest'] 73 | _BUMPFEERESPONSE = DESCRIPTOR.message_types_by_name['BumpFeeResponse'] 74 | _LISTSWEEPSREQUEST = DESCRIPTOR.message_types_by_name['ListSweepsRequest'] 75 | _LISTSWEEPSRESPONSE = DESCRIPTOR.message_types_by_name['ListSweepsResponse'] 76 | _LISTSWEEPSRESPONSE_TRANSACTIONIDS = _LISTSWEEPSRESPONSE.nested_types_by_name['TransactionIDs'] 77 | _LABELTRANSACTIONREQUEST = DESCRIPTOR.message_types_by_name['LabelTransactionRequest'] 78 | _LABELTRANSACTIONRESPONSE = DESCRIPTOR.message_types_by_name['LabelTransactionResponse'] 79 | _FUNDPSBTREQUEST = DESCRIPTOR.message_types_by_name['FundPsbtRequest'] 80 | _FUNDPSBTRESPONSE = DESCRIPTOR.message_types_by_name['FundPsbtResponse'] 81 | _TXTEMPLATE = DESCRIPTOR.message_types_by_name['TxTemplate'] 82 | _TXTEMPLATE_OUTPUTSENTRY = _TXTEMPLATE.nested_types_by_name['OutputsEntry'] 83 | _UTXOLEASE = DESCRIPTOR.message_types_by_name['UtxoLease'] 84 | _SIGNPSBTREQUEST = DESCRIPTOR.message_types_by_name['SignPsbtRequest'] 85 | _SIGNPSBTRESPONSE = DESCRIPTOR.message_types_by_name['SignPsbtResponse'] 86 | _FINALIZEPSBTREQUEST = DESCRIPTOR.message_types_by_name['FinalizePsbtRequest'] 87 | _FINALIZEPSBTRESPONSE = DESCRIPTOR.message_types_by_name['FinalizePsbtResponse'] 88 | _LISTLEASESREQUEST = DESCRIPTOR.message_types_by_name['ListLeasesRequest'] 89 | _LISTLEASESRESPONSE = DESCRIPTOR.message_types_by_name['ListLeasesResponse'] 90 | ListUnspentRequest = _reflection.GeneratedProtocolMessageType('ListUnspentRequest', (_message.Message,), { 91 | 'DESCRIPTOR' : _LISTUNSPENTREQUEST, 92 | '__module__' : 'walletrpc.walletkit_pb2' 93 | # @@protoc_insertion_point(class_scope:walletrpc.ListUnspentRequest) 94 | }) 95 | _sym_db.RegisterMessage(ListUnspentRequest) 96 | 97 | ListUnspentResponse = _reflection.GeneratedProtocolMessageType('ListUnspentResponse', (_message.Message,), { 98 | 'DESCRIPTOR' : _LISTUNSPENTRESPONSE, 99 | '__module__' : 'walletrpc.walletkit_pb2' 100 | # @@protoc_insertion_point(class_scope:walletrpc.ListUnspentResponse) 101 | }) 102 | _sym_db.RegisterMessage(ListUnspentResponse) 103 | 104 | LeaseOutputRequest = _reflection.GeneratedProtocolMessageType('LeaseOutputRequest', (_message.Message,), { 105 | 'DESCRIPTOR' : _LEASEOUTPUTREQUEST, 106 | '__module__' : 'walletrpc.walletkit_pb2' 107 | # @@protoc_insertion_point(class_scope:walletrpc.LeaseOutputRequest) 108 | }) 109 | _sym_db.RegisterMessage(LeaseOutputRequest) 110 | 111 | LeaseOutputResponse = _reflection.GeneratedProtocolMessageType('LeaseOutputResponse', (_message.Message,), { 112 | 'DESCRIPTOR' : _LEASEOUTPUTRESPONSE, 113 | '__module__' : 'walletrpc.walletkit_pb2' 114 | # @@protoc_insertion_point(class_scope:walletrpc.LeaseOutputResponse) 115 | }) 116 | _sym_db.RegisterMessage(LeaseOutputResponse) 117 | 118 | ReleaseOutputRequest = _reflection.GeneratedProtocolMessageType('ReleaseOutputRequest', (_message.Message,), { 119 | 'DESCRIPTOR' : _RELEASEOUTPUTREQUEST, 120 | '__module__' : 'walletrpc.walletkit_pb2' 121 | # @@protoc_insertion_point(class_scope:walletrpc.ReleaseOutputRequest) 122 | }) 123 | _sym_db.RegisterMessage(ReleaseOutputRequest) 124 | 125 | ReleaseOutputResponse = _reflection.GeneratedProtocolMessageType('ReleaseOutputResponse', (_message.Message,), { 126 | 'DESCRIPTOR' : _RELEASEOUTPUTRESPONSE, 127 | '__module__' : 'walletrpc.walletkit_pb2' 128 | # @@protoc_insertion_point(class_scope:walletrpc.ReleaseOutputResponse) 129 | }) 130 | _sym_db.RegisterMessage(ReleaseOutputResponse) 131 | 132 | KeyReq = _reflection.GeneratedProtocolMessageType('KeyReq', (_message.Message,), { 133 | 'DESCRIPTOR' : _KEYREQ, 134 | '__module__' : 'walletrpc.walletkit_pb2' 135 | # @@protoc_insertion_point(class_scope:walletrpc.KeyReq) 136 | }) 137 | _sym_db.RegisterMessage(KeyReq) 138 | 139 | AddrRequest = _reflection.GeneratedProtocolMessageType('AddrRequest', (_message.Message,), { 140 | 'DESCRIPTOR' : _ADDRREQUEST, 141 | '__module__' : 'walletrpc.walletkit_pb2' 142 | # @@protoc_insertion_point(class_scope:walletrpc.AddrRequest) 143 | }) 144 | _sym_db.RegisterMessage(AddrRequest) 145 | 146 | AddrResponse = _reflection.GeneratedProtocolMessageType('AddrResponse', (_message.Message,), { 147 | 'DESCRIPTOR' : _ADDRRESPONSE, 148 | '__module__' : 'walletrpc.walletkit_pb2' 149 | # @@protoc_insertion_point(class_scope:walletrpc.AddrResponse) 150 | }) 151 | _sym_db.RegisterMessage(AddrResponse) 152 | 153 | Account = _reflection.GeneratedProtocolMessageType('Account', (_message.Message,), { 154 | 'DESCRIPTOR' : _ACCOUNT, 155 | '__module__' : 'walletrpc.walletkit_pb2' 156 | # @@protoc_insertion_point(class_scope:walletrpc.Account) 157 | }) 158 | _sym_db.RegisterMessage(Account) 159 | 160 | ListAccountsRequest = _reflection.GeneratedProtocolMessageType('ListAccountsRequest', (_message.Message,), { 161 | 'DESCRIPTOR' : _LISTACCOUNTSREQUEST, 162 | '__module__' : 'walletrpc.walletkit_pb2' 163 | # @@protoc_insertion_point(class_scope:walletrpc.ListAccountsRequest) 164 | }) 165 | _sym_db.RegisterMessage(ListAccountsRequest) 166 | 167 | ListAccountsResponse = _reflection.GeneratedProtocolMessageType('ListAccountsResponse', (_message.Message,), { 168 | 'DESCRIPTOR' : _LISTACCOUNTSRESPONSE, 169 | '__module__' : 'walletrpc.walletkit_pb2' 170 | # @@protoc_insertion_point(class_scope:walletrpc.ListAccountsResponse) 171 | }) 172 | _sym_db.RegisterMessage(ListAccountsResponse) 173 | 174 | ImportAccountRequest = _reflection.GeneratedProtocolMessageType('ImportAccountRequest', (_message.Message,), { 175 | 'DESCRIPTOR' : _IMPORTACCOUNTREQUEST, 176 | '__module__' : 'walletrpc.walletkit_pb2' 177 | # @@protoc_insertion_point(class_scope:walletrpc.ImportAccountRequest) 178 | }) 179 | _sym_db.RegisterMessage(ImportAccountRequest) 180 | 181 | ImportAccountResponse = _reflection.GeneratedProtocolMessageType('ImportAccountResponse', (_message.Message,), { 182 | 'DESCRIPTOR' : _IMPORTACCOUNTRESPONSE, 183 | '__module__' : 'walletrpc.walletkit_pb2' 184 | # @@protoc_insertion_point(class_scope:walletrpc.ImportAccountResponse) 185 | }) 186 | _sym_db.RegisterMessage(ImportAccountResponse) 187 | 188 | ImportPublicKeyRequest = _reflection.GeneratedProtocolMessageType('ImportPublicKeyRequest', (_message.Message,), { 189 | 'DESCRIPTOR' : _IMPORTPUBLICKEYREQUEST, 190 | '__module__' : 'walletrpc.walletkit_pb2' 191 | # @@protoc_insertion_point(class_scope:walletrpc.ImportPublicKeyRequest) 192 | }) 193 | _sym_db.RegisterMessage(ImportPublicKeyRequest) 194 | 195 | ImportPublicKeyResponse = _reflection.GeneratedProtocolMessageType('ImportPublicKeyResponse', (_message.Message,), { 196 | 'DESCRIPTOR' : _IMPORTPUBLICKEYRESPONSE, 197 | '__module__' : 'walletrpc.walletkit_pb2' 198 | # @@protoc_insertion_point(class_scope:walletrpc.ImportPublicKeyResponse) 199 | }) 200 | _sym_db.RegisterMessage(ImportPublicKeyResponse) 201 | 202 | Transaction = _reflection.GeneratedProtocolMessageType('Transaction', (_message.Message,), { 203 | 'DESCRIPTOR' : _TRANSACTION, 204 | '__module__' : 'walletrpc.walletkit_pb2' 205 | # @@protoc_insertion_point(class_scope:walletrpc.Transaction) 206 | }) 207 | _sym_db.RegisterMessage(Transaction) 208 | 209 | PublishResponse = _reflection.GeneratedProtocolMessageType('PublishResponse', (_message.Message,), { 210 | 'DESCRIPTOR' : _PUBLISHRESPONSE, 211 | '__module__' : 'walletrpc.walletkit_pb2' 212 | # @@protoc_insertion_point(class_scope:walletrpc.PublishResponse) 213 | }) 214 | _sym_db.RegisterMessage(PublishResponse) 215 | 216 | SendOutputsRequest = _reflection.GeneratedProtocolMessageType('SendOutputsRequest', (_message.Message,), { 217 | 'DESCRIPTOR' : _SENDOUTPUTSREQUEST, 218 | '__module__' : 'walletrpc.walletkit_pb2' 219 | # @@protoc_insertion_point(class_scope:walletrpc.SendOutputsRequest) 220 | }) 221 | _sym_db.RegisterMessage(SendOutputsRequest) 222 | 223 | SendOutputsResponse = _reflection.GeneratedProtocolMessageType('SendOutputsResponse', (_message.Message,), { 224 | 'DESCRIPTOR' : _SENDOUTPUTSRESPONSE, 225 | '__module__' : 'walletrpc.walletkit_pb2' 226 | # @@protoc_insertion_point(class_scope:walletrpc.SendOutputsResponse) 227 | }) 228 | _sym_db.RegisterMessage(SendOutputsResponse) 229 | 230 | EstimateFeeRequest = _reflection.GeneratedProtocolMessageType('EstimateFeeRequest', (_message.Message,), { 231 | 'DESCRIPTOR' : _ESTIMATEFEEREQUEST, 232 | '__module__' : 'walletrpc.walletkit_pb2' 233 | # @@protoc_insertion_point(class_scope:walletrpc.EstimateFeeRequest) 234 | }) 235 | _sym_db.RegisterMessage(EstimateFeeRequest) 236 | 237 | EstimateFeeResponse = _reflection.GeneratedProtocolMessageType('EstimateFeeResponse', (_message.Message,), { 238 | 'DESCRIPTOR' : _ESTIMATEFEERESPONSE, 239 | '__module__' : 'walletrpc.walletkit_pb2' 240 | # @@protoc_insertion_point(class_scope:walletrpc.EstimateFeeResponse) 241 | }) 242 | _sym_db.RegisterMessage(EstimateFeeResponse) 243 | 244 | PendingSweep = _reflection.GeneratedProtocolMessageType('PendingSweep', (_message.Message,), { 245 | 'DESCRIPTOR' : _PENDINGSWEEP, 246 | '__module__' : 'walletrpc.walletkit_pb2' 247 | # @@protoc_insertion_point(class_scope:walletrpc.PendingSweep) 248 | }) 249 | _sym_db.RegisterMessage(PendingSweep) 250 | 251 | PendingSweepsRequest = _reflection.GeneratedProtocolMessageType('PendingSweepsRequest', (_message.Message,), { 252 | 'DESCRIPTOR' : _PENDINGSWEEPSREQUEST, 253 | '__module__' : 'walletrpc.walletkit_pb2' 254 | # @@protoc_insertion_point(class_scope:walletrpc.PendingSweepsRequest) 255 | }) 256 | _sym_db.RegisterMessage(PendingSweepsRequest) 257 | 258 | PendingSweepsResponse = _reflection.GeneratedProtocolMessageType('PendingSweepsResponse', (_message.Message,), { 259 | 'DESCRIPTOR' : _PENDINGSWEEPSRESPONSE, 260 | '__module__' : 'walletrpc.walletkit_pb2' 261 | # @@protoc_insertion_point(class_scope:walletrpc.PendingSweepsResponse) 262 | }) 263 | _sym_db.RegisterMessage(PendingSweepsResponse) 264 | 265 | BumpFeeRequest = _reflection.GeneratedProtocolMessageType('BumpFeeRequest', (_message.Message,), { 266 | 'DESCRIPTOR' : _BUMPFEEREQUEST, 267 | '__module__' : 'walletrpc.walletkit_pb2' 268 | # @@protoc_insertion_point(class_scope:walletrpc.BumpFeeRequest) 269 | }) 270 | _sym_db.RegisterMessage(BumpFeeRequest) 271 | 272 | BumpFeeResponse = _reflection.GeneratedProtocolMessageType('BumpFeeResponse', (_message.Message,), { 273 | 'DESCRIPTOR' : _BUMPFEERESPONSE, 274 | '__module__' : 'walletrpc.walletkit_pb2' 275 | # @@protoc_insertion_point(class_scope:walletrpc.BumpFeeResponse) 276 | }) 277 | _sym_db.RegisterMessage(BumpFeeResponse) 278 | 279 | ListSweepsRequest = _reflection.GeneratedProtocolMessageType('ListSweepsRequest', (_message.Message,), { 280 | 'DESCRIPTOR' : _LISTSWEEPSREQUEST, 281 | '__module__' : 'walletrpc.walletkit_pb2' 282 | # @@protoc_insertion_point(class_scope:walletrpc.ListSweepsRequest) 283 | }) 284 | _sym_db.RegisterMessage(ListSweepsRequest) 285 | 286 | ListSweepsResponse = _reflection.GeneratedProtocolMessageType('ListSweepsResponse', (_message.Message,), { 287 | 288 | 'TransactionIDs' : _reflection.GeneratedProtocolMessageType('TransactionIDs', (_message.Message,), { 289 | 'DESCRIPTOR' : _LISTSWEEPSRESPONSE_TRANSACTIONIDS, 290 | '__module__' : 'walletrpc.walletkit_pb2' 291 | # @@protoc_insertion_point(class_scope:walletrpc.ListSweepsResponse.TransactionIDs) 292 | }) 293 | , 294 | 'DESCRIPTOR' : _LISTSWEEPSRESPONSE, 295 | '__module__' : 'walletrpc.walletkit_pb2' 296 | # @@protoc_insertion_point(class_scope:walletrpc.ListSweepsResponse) 297 | }) 298 | _sym_db.RegisterMessage(ListSweepsResponse) 299 | _sym_db.RegisterMessage(ListSweepsResponse.TransactionIDs) 300 | 301 | LabelTransactionRequest = _reflection.GeneratedProtocolMessageType('LabelTransactionRequest', (_message.Message,), { 302 | 'DESCRIPTOR' : _LABELTRANSACTIONREQUEST, 303 | '__module__' : 'walletrpc.walletkit_pb2' 304 | # @@protoc_insertion_point(class_scope:walletrpc.LabelTransactionRequest) 305 | }) 306 | _sym_db.RegisterMessage(LabelTransactionRequest) 307 | 308 | LabelTransactionResponse = _reflection.GeneratedProtocolMessageType('LabelTransactionResponse', (_message.Message,), { 309 | 'DESCRIPTOR' : _LABELTRANSACTIONRESPONSE, 310 | '__module__' : 'walletrpc.walletkit_pb2' 311 | # @@protoc_insertion_point(class_scope:walletrpc.LabelTransactionResponse) 312 | }) 313 | _sym_db.RegisterMessage(LabelTransactionResponse) 314 | 315 | FundPsbtRequest = _reflection.GeneratedProtocolMessageType('FundPsbtRequest', (_message.Message,), { 316 | 'DESCRIPTOR' : _FUNDPSBTREQUEST, 317 | '__module__' : 'walletrpc.walletkit_pb2' 318 | # @@protoc_insertion_point(class_scope:walletrpc.FundPsbtRequest) 319 | }) 320 | _sym_db.RegisterMessage(FundPsbtRequest) 321 | 322 | FundPsbtResponse = _reflection.GeneratedProtocolMessageType('FundPsbtResponse', (_message.Message,), { 323 | 'DESCRIPTOR' : _FUNDPSBTRESPONSE, 324 | '__module__' : 'walletrpc.walletkit_pb2' 325 | # @@protoc_insertion_point(class_scope:walletrpc.FundPsbtResponse) 326 | }) 327 | _sym_db.RegisterMessage(FundPsbtResponse) 328 | 329 | TxTemplate = _reflection.GeneratedProtocolMessageType('TxTemplate', (_message.Message,), { 330 | 331 | 'OutputsEntry' : _reflection.GeneratedProtocolMessageType('OutputsEntry', (_message.Message,), { 332 | 'DESCRIPTOR' : _TXTEMPLATE_OUTPUTSENTRY, 333 | '__module__' : 'walletrpc.walletkit_pb2' 334 | # @@protoc_insertion_point(class_scope:walletrpc.TxTemplate.OutputsEntry) 335 | }) 336 | , 337 | 'DESCRIPTOR' : _TXTEMPLATE, 338 | '__module__' : 'walletrpc.walletkit_pb2' 339 | # @@protoc_insertion_point(class_scope:walletrpc.TxTemplate) 340 | }) 341 | _sym_db.RegisterMessage(TxTemplate) 342 | _sym_db.RegisterMessage(TxTemplate.OutputsEntry) 343 | 344 | UtxoLease = _reflection.GeneratedProtocolMessageType('UtxoLease', (_message.Message,), { 345 | 'DESCRIPTOR' : _UTXOLEASE, 346 | '__module__' : 'walletrpc.walletkit_pb2' 347 | # @@protoc_insertion_point(class_scope:walletrpc.UtxoLease) 348 | }) 349 | _sym_db.RegisterMessage(UtxoLease) 350 | 351 | SignPsbtRequest = _reflection.GeneratedProtocolMessageType('SignPsbtRequest', (_message.Message,), { 352 | 'DESCRIPTOR' : _SIGNPSBTREQUEST, 353 | '__module__' : 'walletrpc.walletkit_pb2' 354 | # @@protoc_insertion_point(class_scope:walletrpc.SignPsbtRequest) 355 | }) 356 | _sym_db.RegisterMessage(SignPsbtRequest) 357 | 358 | SignPsbtResponse = _reflection.GeneratedProtocolMessageType('SignPsbtResponse', (_message.Message,), { 359 | 'DESCRIPTOR' : _SIGNPSBTRESPONSE, 360 | '__module__' : 'walletrpc.walletkit_pb2' 361 | # @@protoc_insertion_point(class_scope:walletrpc.SignPsbtResponse) 362 | }) 363 | _sym_db.RegisterMessage(SignPsbtResponse) 364 | 365 | FinalizePsbtRequest = _reflection.GeneratedProtocolMessageType('FinalizePsbtRequest', (_message.Message,), { 366 | 'DESCRIPTOR' : _FINALIZEPSBTREQUEST, 367 | '__module__' : 'walletrpc.walletkit_pb2' 368 | # @@protoc_insertion_point(class_scope:walletrpc.FinalizePsbtRequest) 369 | }) 370 | _sym_db.RegisterMessage(FinalizePsbtRequest) 371 | 372 | FinalizePsbtResponse = _reflection.GeneratedProtocolMessageType('FinalizePsbtResponse', (_message.Message,), { 373 | 'DESCRIPTOR' : _FINALIZEPSBTRESPONSE, 374 | '__module__' : 'walletrpc.walletkit_pb2' 375 | # @@protoc_insertion_point(class_scope:walletrpc.FinalizePsbtResponse) 376 | }) 377 | _sym_db.RegisterMessage(FinalizePsbtResponse) 378 | 379 | ListLeasesRequest = _reflection.GeneratedProtocolMessageType('ListLeasesRequest', (_message.Message,), { 380 | 'DESCRIPTOR' : _LISTLEASESREQUEST, 381 | '__module__' : 'walletrpc.walletkit_pb2' 382 | # @@protoc_insertion_point(class_scope:walletrpc.ListLeasesRequest) 383 | }) 384 | _sym_db.RegisterMessage(ListLeasesRequest) 385 | 386 | ListLeasesResponse = _reflection.GeneratedProtocolMessageType('ListLeasesResponse', (_message.Message,), { 387 | 'DESCRIPTOR' : _LISTLEASESRESPONSE, 388 | '__module__' : 'walletrpc.walletkit_pb2' 389 | # @@protoc_insertion_point(class_scope:walletrpc.ListLeasesResponse) 390 | }) 391 | _sym_db.RegisterMessage(ListLeasesResponse) 392 | 393 | _WALLETKIT = DESCRIPTOR.services_by_name['WalletKit'] 394 | if _descriptor._USE_C_DESCRIPTORS == False: 395 | 396 | DESCRIPTOR._options = None 397 | DESCRIPTOR._serialized_options = b'Z/github.com/lightningnetwork/lnd/lnrpc/walletrpc' 398 | _PENDINGSWEEP.fields_by_name['sat_per_byte']._options = None 399 | _PENDINGSWEEP.fields_by_name['sat_per_byte']._serialized_options = b'\030\001' 400 | _PENDINGSWEEP.fields_by_name['requested_sat_per_byte']._options = None 401 | _PENDINGSWEEP.fields_by_name['requested_sat_per_byte']._serialized_options = b'\030\001' 402 | _BUMPFEEREQUEST.fields_by_name['sat_per_byte']._options = None 403 | _BUMPFEEREQUEST.fields_by_name['sat_per_byte']._serialized_options = b'\030\001' 404 | _TXTEMPLATE_OUTPUTSENTRY._options = None 405 | _TXTEMPLATE_OUTPUTSENTRY._serialized_options = b'8\001' 406 | _ADDRESSTYPE._serialized_start=3568 407 | _ADDRESSTYPE._serialized_end=3710 408 | _WITNESSTYPE._serialized_start=3713 409 | _WITNESSTYPE._serialized_end=4122 410 | _LISTUNSPENTREQUEST._serialized_start=79 411 | _LISTUNSPENTREQUEST._serialized_end=180 412 | _LISTUNSPENTRESPONSE._serialized_start=182 413 | _LISTUNSPENTRESPONSE._serialized_end=231 414 | _LEASEOUTPUTREQUEST._serialized_start=233 415 | _LEASEOUTPUTREQUEST._serialized_end=328 416 | _LEASEOUTPUTRESPONSE._serialized_start=330 417 | _LEASEOUTPUTRESPONSE._serialized_end=371 418 | _RELEASEOUTPUTREQUEST._serialized_start=373 419 | _RELEASEOUTPUTREQUEST._serialized_end=442 420 | _RELEASEOUTPUTRESPONSE._serialized_start=444 421 | _RELEASEOUTPUTRESPONSE._serialized_end=467 422 | _KEYREQ._serialized_start=469 423 | _KEYREQ._serialized_end=523 424 | _ADDRREQUEST._serialized_start=525 425 | _ADDRREQUEST._serialized_end=609 426 | _ADDRRESPONSE._serialized_start=611 427 | _ADDRRESPONSE._serialized_end=639 428 | _ACCOUNT._serialized_start=642 429 | _ACCOUNT._serialized_end=873 430 | _LISTACCOUNTSREQUEST._serialized_start=875 431 | _LISTACCOUNTSREQUEST._serialized_end=956 432 | _LISTACCOUNTSRESPONSE._serialized_start=958 433 | _LISTACCOUNTSRESPONSE._serialized_end=1018 434 | _IMPORTACCOUNTREQUEST._serialized_start=1021 435 | _IMPORTACCOUNTREQUEST._serialized_end=1181 436 | _IMPORTACCOUNTRESPONSE._serialized_start=1183 437 | _IMPORTACCOUNTRESPONSE._serialized_end=1307 438 | _IMPORTPUBLICKEYREQUEST._serialized_start=1309 439 | _IMPORTPUBLICKEYREQUEST._serialized_end=1399 440 | _IMPORTPUBLICKEYRESPONSE._serialized_start=1401 441 | _IMPORTPUBLICKEYRESPONSE._serialized_end=1426 442 | _TRANSACTION._serialized_start=1428 443 | _TRANSACTION._serialized_end=1472 444 | _PUBLISHRESPONSE._serialized_start=1474 445 | _PUBLISHRESPONSE._serialized_end=1514 446 | _SENDOUTPUTSREQUEST._serialized_start=1517 447 | _SENDOUTPUTSREQUEST._serialized_end=1651 448 | _SENDOUTPUTSRESPONSE._serialized_start=1653 449 | _SENDOUTPUTSRESPONSE._serialized_end=1690 450 | _ESTIMATEFEEREQUEST._serialized_start=1692 451 | _ESTIMATEFEEREQUEST._serialized_end=1733 452 | _ESTIMATEFEERESPONSE._serialized_start=1735 453 | _ESTIMATEFEERESPONSE._serialized_end=1776 454 | _PENDINGSWEEP._serialized_start=1779 455 | _PENDINGSWEEP._serialized_end=2117 456 | _PENDINGSWEEPSREQUEST._serialized_start=2119 457 | _PENDINGSWEEPSREQUEST._serialized_end=2141 458 | _PENDINGSWEEPSRESPONSE._serialized_start=2143 459 | _PENDINGSWEEPSRESPONSE._serialized_end=2215 460 | _BUMPFEEREQUEST._serialized_start=2218 461 | _BUMPFEEREQUEST._serialized_end=2354 462 | _BUMPFEERESPONSE._serialized_start=2356 463 | _BUMPFEERESPONSE._serialized_end=2373 464 | _LISTSWEEPSREQUEST._serialized_start=2375 465 | _LISTSWEEPSREQUEST._serialized_end=2411 466 | _LISTSWEEPSRESPONSE._serialized_start=2414 467 | _LISTSWEEPSRESPONSE._serialized_end=2618 468 | _LISTSWEEPSRESPONSE_TRANSACTIONIDS._serialized_start=2567 469 | _LISTSWEEPSRESPONSE_TRANSACTIONIDS._serialized_end=2608 470 | _LABELTRANSACTIONREQUEST._serialized_start=2620 471 | _LABELTRANSACTIONREQUEST._serialized_end=2693 472 | _LABELTRANSACTIONRESPONSE._serialized_start=2695 473 | _LABELTRANSACTIONRESPONSE._serialized_end=2721 474 | _FUNDPSBTREQUEST._serialized_start=2724 475 | _FUNDPSBTREQUEST._serialized_end=2926 476 | _FUNDPSBTRESPONSE._serialized_start=2928 477 | _FUNDPSBTRESPONSE._serialized_end=3040 478 | _TXTEMPLATE._serialized_start=3043 479 | _TXTEMPLATE._serialized_end=3189 480 | _TXTEMPLATE_OUTPUTSENTRY._serialized_start=3143 481 | _TXTEMPLATE_OUTPUTSENTRY._serialized_end=3189 482 | _UTXOLEASE._serialized_start=3191 483 | _UTXOLEASE._serialized_end=3269 484 | _SIGNPSBTREQUEST._serialized_start=3271 485 | _SIGNPSBTREQUEST._serialized_end=3309 486 | _SIGNPSBTRESPONSE._serialized_start=3311 487 | _SIGNPSBTRESPONSE._serialized_end=3350 488 | _FINALIZEPSBTREQUEST._serialized_start=3352 489 | _FINALIZEPSBTREQUEST._serialized_end=3411 490 | _FINALIZEPSBTRESPONSE._serialized_start=3413 491 | _FINALIZEPSBTRESPONSE._serialized_end=3478 492 | _LISTLEASESREQUEST._serialized_start=3480 493 | _LISTLEASESREQUEST._serialized_end=3499 494 | _LISTLEASESRESPONSE._serialized_start=3501 495 | _LISTLEASESRESPONSE._serialized_end=3565 496 | _WALLETKIT._serialized_start=4125 497 | _WALLETKIT._serialized_end=5652 498 | # @@protoc_insertion_point(module_scope) 499 | -------------------------------------------------------------------------------- /nodeview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Plots some node statistics for comparing with neighbours 4 | This script requires describegraph.json 5 | 6 | The blue dot represents the node being viewed relative to peers 7 | """ 8 | import sys 9 | 10 | import matplotlib.pyplot as plt 11 | import networkx as nx 12 | import numpy as np 13 | 14 | from lib.lnGraph import lnGraphV2 15 | 16 | median_payment = 200e3 # sat, Payment size for calculating routing costs 17 | # TODO: Should ignore channels smaller than (2x? 4-5x?) the above 18 | feeceiling = 5000 # PPM, ignore fees higher than this 19 | 20 | if len(sys.argv) > 1 and len(sys.argv[1]) <= 66: 21 | node2view = sys.argv[1] 22 | else: 23 | print('Please enter the pubkey or alias of the node of interest') 24 | node2view = input('Pubkey: ') 25 | 26 | print('Loading graph') 27 | # Using the simplified graph to avoid double counting parallel links 28 | g = lnGraphV2.autoload().simple() 29 | 30 | if len(node2view) < 66: 31 | try: 32 | node2view = g.nodes.find(alias=node2view)['pub_key'] 33 | except ValueError: 34 | print(f'Could not find node with alias "{node2view}"') 35 | exit() 36 | 37 | node = g.nodes.find(node2view) 38 | 39 | print('Viewing', node['alias']) 40 | 41 | # Collect data for histogram 42 | chanstats = dict(chansizes=[], peersizes=[], peerchancounts=[], 43 | fees=dict(inrate=[], outrate=[], inbase=[], outbase=[])) 44 | 45 | channel_data = g.channels.select(_from=node.index) 46 | for chan in channel_data: 47 | assert node2view == chan['local_pubkey'] 48 | # ~ assert isinstance(chan['capacity'], int) 49 | 50 | chanstats['chansizes'].append(chan['capacity']) 51 | 52 | peerkey = chan['remote_pubkey'] 53 | peer = g.nodes.find(pub_key=peerkey) 54 | chanstats['peersizes'].append(peer['capacity']) 55 | chanstats['peerchancounts'].append(peer['num_channels']) 56 | 57 | def appendfeedata(outrate, outbase, inrate, inbase): 58 | # Rates are in PPM, /1e6 to get a fraction 59 | # Base is in msat, /1e3 to get sat 60 | chanstats['fees']['outrate'].append(int(outrate)) 61 | chanstats['fees']['outbase'].append(int(outbase)) 62 | chanstats['fees']['inrate'].append(int(inrate)) 63 | chanstats['fees']['inbase'].append(int(inbase)) 64 | 65 | appendfeedata(chan['fee_rate_milli_msat_out'], 66 | chan['fee_base_msat_out'], 67 | chan['fee_rate_milli_msat_in'], 68 | chan['fee_base_msat_in']) 69 | 70 | # Convert to array 71 | chanstats['chansizes'] = np.asarray(chanstats['chansizes']) 72 | chanstats['peersizes'] = np.asarray(chanstats['peersizes']) 73 | for k, v in chanstats['fees'].items(): 74 | chanstats['fees'][k] = np.array(v) 75 | 76 | # ~ exit() 77 | 78 | def plothists(): 79 | chansizebins = np.array([0, 2e6, 5e6, 10e6, 17e6, 20e6]) 80 | 81 | if max(node['capacities']) > chansizebins[-1]: 82 | chansizebins[-1] = max(node['capacities']) 83 | 84 | fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2) 85 | fig.tight_layout() 86 | ax1.hist(chanstats['chansizes'] / 1e6, align='right') 87 | ax1.set_title('Channel Size Distribution') 88 | ax1.set_xlabel('Channel size (Msat)') 89 | ax2.hist(chanstats['peersizes'] / 1e8, align='right') 90 | ax2.set_title('Peer Size Distribution') 91 | ax2.set_xlabel('Peer capacity (BTC)') 92 | 93 | median_in_fees = (chanstats['fees']['inbase'] / 1e3 + chanstats['fees']['inrate'] / 1e6 * median_payment) 94 | median_out_fees = (chanstats['fees']['outbase'] / 1e3 + chanstats['fees']['outrate'] / 1e6 * median_payment) 95 | 96 | ax3.hist(median_in_fees, align='right') 97 | ax4.hist(median_out_fees, align='right') 98 | ax3.set_title('Receiving Fee Distribution') 99 | ax4.set_title('Sending Fee Distribution') 100 | 101 | ax3.set_xlabel(f'Sats to route {median_payment / 1e3:.0f}ksat in') 102 | ax4.set_xlabel(f'Sats to route {median_payment / 1e3:.0f}ksat out') 103 | 104 | plt.show() 105 | 106 | 107 | # ~ plothists() 108 | 109 | def pltboxes(): 110 | ncols = 5 111 | fig = plt.figure() 112 | 113 | # ~ fig.canvas.set_window_title('Viewing '+node['alias']) 114 | 115 | peerchanax = plt.subplot(1, ncols, 1) 116 | peersizeax = plt.subplot(1, ncols, 2) 117 | chansizeax = plt.subplot(1, ncols, 3) 118 | feeax = plt.subplot(1, ncols, (4, 5)) 119 | 120 | feeax.set_title(f'Fee for {median_payment / 1e3:.0f}ksat Forward') 121 | 122 | # ~ print(list(zip(chanstats['peerchanco unts'], [g.nodes[k]['alias'] for k in g.adj[node2view]]))) 123 | peerchanax.boxplot(chanstats['peerchancounts'], showmeans=True) 124 | peerchanax.scatter([1], [node['num_channels']]) 125 | peerchanax.set_title('Peer Channel Counts') 126 | peerchanax.set_ylabel('Peer Channel Count') 127 | 128 | peersizeax.boxplot(chanstats['peersizes'] / 1e8, showmeans=True) 129 | peersizeax.scatter([1], [node['capacity'] / 1e8]) 130 | peersizeax.set_title('Peer Sizes') 131 | peersizeax.set_ylabel('Peer Capacity (BTC)') 132 | 133 | chansizeax.boxplot(chanstats['chansizes'] / 1e6, showmeans=True) 134 | chansizeax.set_title('Channel Sizes') 135 | chansizeax.set_ylabel('Channel Size (Msat)') 136 | 137 | median_in_fees = (chanstats['fees']['inbase'] / 1e3 + chanstats['fees']['inrate'] / 1e6 * median_payment) 138 | median_out_fees = (chanstats['fees']['outbase'] / 1e3 + chanstats['fees']['outrate'] / 1e6 * median_payment) 139 | 140 | median_in_fees_ppm = median_in_fees * 1e6/median_payment 141 | median_out_fees_ppm = median_out_fees * 1e6/median_payment 142 | 143 | # Remove outliers 144 | median_in_fees_ppm = median_in_fees_ppm[np.where(median_in_fees_ppm <= feeceiling)[0]] 145 | median_out_fees_ppm = median_out_fees_ppm[np.where(median_out_fees_ppm <= feeceiling)[0]] 146 | 147 | feeax.boxplot([median_in_fees_ppm, median_out_fees_ppm], 148 | labels=['Receiving', 'Sending'], showmeans=True) 149 | feeax.set_ylabel('Fee (PPM)') 150 | 151 | fig.tight_layout() 152 | plt.show() 153 | 154 | 155 | pltboxes() 156 | -------------------------------------------------------------------------------- /plotforwards.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Creates a heatmap of recent forwards 4 | Run `python3 plotforwards -h` for options` 5 | """ 6 | 7 | import argparse 8 | import time 9 | from datetime import timedelta 10 | import random 11 | 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | 15 | from lib.nodeinterface import NodeInterface 16 | 17 | parser = argparse.ArgumentParser(description='Plot a diagram of recent forwards') 18 | parser.add_argument('--days', type=int, default=90, 19 | help='Specify lookback time, default 90') 20 | 21 | # Below 2 are exclusive 22 | group = parser.add_mutually_exclusive_group() 23 | group.add_argument('--count', action="store_true", 24 | help='Plot forward count instead of volume') 25 | group.add_argument('--fees', action="store_true", 26 | help='Plot fee returns instead of volume') 27 | 28 | parser.add_argument('--perpeer', action="store_true", 29 | help='Collect redundant channels by peer') 30 | parser.add_argument('--nolabels', action="store_true", 31 | help='Do not plot grid labels, for privacy when sharing #nodeart') 32 | parser.add_argument('--nocbar', action="store_true", 33 | help='Do not plot colourbar, for privacy when sharing #nodeart') 34 | parser.add_argument('--shuffle', action="store_true", 35 | help='Randomize order of channels, for privacy when sharing #nodeart') 36 | 37 | parser.add_argument('--max', type=int, default=-1, 38 | help='Don\'t plot pairs above this activity threshold') 39 | parser.add_argument('--min', type=int, default=0, 40 | help='Don\'t plot pairs below this activity threshold') 41 | 42 | parser.add_argument('--aliasclip', type=int, default=20, 43 | help='Shorten long aliases to this many characters, default 20') 44 | 45 | parser.add_argument('--nogrid', action="store_true", 46 | help='Do not plot grid lines') 47 | parser.add_argument('--cmap', type=str, default='Blues', 48 | help='Use an alternate colormap') 49 | 50 | args = parser.parse_args() 51 | 52 | mynode = NodeInterface.fromconfig() 53 | fwdhisttime = timedelta(days=args.days) 54 | print('Fetching forwarding history') 55 | fwdhist = mynode.getForwardingHistory( 56 | int(time.time()-fwdhisttime.total_seconds())) 57 | 58 | if args.count: 59 | plotattr = 'count' 60 | elif args.fees: 61 | plotattr = 'fees' 62 | else: 63 | plotattr = 'amount' 64 | 65 | print('Fetching channel data') 66 | channeldata = {} 67 | for c in mynode.ListChannels().channels: 68 | channeldata[c.chan_id] = c 69 | 70 | print('Processing data') 71 | 72 | labels = [] 73 | if args.perpeer: 74 | id_list = [] 75 | for i, c in sorted(channeldata.items()): 76 | rpk = c.remote_pubkey 77 | if rpk not in id_list: 78 | id_list.append(rpk) 79 | else: 80 | # Sort to keep results consistent 81 | id_list = sorted(channeldata.keys()) 82 | 83 | if args.shuffle: 84 | random.shuffle(id_list) 85 | 86 | if args.perpeer: 87 | for cid in id_list: 88 | labels.append( 89 | mynode.getAlias(cid)[:args.aliasclip]) 90 | else: 91 | for cid in id_list: 92 | labels.append( 93 | mynode.getAlias(channeldata[cid].remote_pubkey)[:args.aliasclip]) 94 | 95 | num_channels = len(labels) 96 | dataarray = np.zeros((num_channels, num_channels)) 97 | 98 | for event in fwdhist: 99 | fee = event.fee_msat/1000 100 | 101 | id_in = event.chan_id_in 102 | id_out = event.chan_id_out 103 | if id_in not in channeldata or id_out not in channeldata: 104 | continue # Catch recently closed channels 105 | 106 | if args.perpeer: 107 | # Convert chan id to remote node id 108 | id_in = channeldata[id_in].remote_pubkey 109 | id_out = channeldata[id_out].remote_pubkey 110 | 111 | i1 = id_list.index(id_in) 112 | i2 = id_list.index(id_out) 113 | 114 | if args.count: 115 | v = 1 116 | elif args.fees: 117 | v = event.fee_msat/1000 118 | else: 119 | v = event.amt_out_msat 120 | 121 | dataarray[i1, i2] += v 122 | 123 | if args.min > 0 or args.max > 0: 124 | print('Filtering output array') 125 | 126 | todelete = [] 127 | for i, l in enumerate(id_list): 128 | activity = max(dataarray[i, :].max(), dataarray[:, i].max()) 129 | if activity < args.min or (args.max > 0 and activity > args.max): 130 | todelete.append(i) 131 | 132 | # Have to reverse if deleting by index, remove highest index first 133 | for i in reversed(todelete): 134 | del id_list[i] 135 | del labels[i] 136 | 137 | dataarray = np.delete(dataarray, todelete, axis=0) 138 | dataarray = np.delete(dataarray, todelete, axis=1) 139 | 140 | print('Plotting output') 141 | fig, ax = plt.subplots() 142 | im = ax.imshow(dataarray, cmap=args.cmap, vmin=0, vmax=dataarray.max()) 143 | 144 | # Force all ticks visible 145 | tickrange = np.arange(len(labels)) 146 | ax.set_xticks(tickrange) 147 | ax.set_yticks(tickrange) 148 | ax.set_xticks(tickrange-.5, minor=True) 149 | ax.set_yticks(tickrange-.51, minor=True) 150 | 151 | # Hide ugly ticks + spines 152 | ax.spines[:].set_visible(False) 153 | ax.tick_params(which="minor", bottom=False, left=False) 154 | 155 | # Label ticks 156 | if args.nolabels: 157 | ax.set_xticklabels('') 158 | ax.set_yticklabels('') 159 | ax.tick_params(color='w') 160 | else: 161 | ax.set_xticklabels(labels) 162 | ax.set_yticklabels(labels) 163 | ax.tick_params(color='silver') 164 | 165 | # Rotate tick labels 166 | plt.setp(ax.get_xticklabels(), rotation=-45, ha="left", 167 | rotation_mode="anchor") 168 | 169 | if not args.nocbar: 170 | # Add colorbar 171 | cbar = ax.figure.colorbar(im, ax=ax) 172 | 173 | if not args.nogrid: 174 | ax.grid(which="minor", color="w", linestyle='-', linewidth=2) 175 | 176 | ax.set_title(f'Forwarding {plotattr} in the last {args.days} days') 177 | ax.set_ylabel('Channel in') 178 | ax.set_xlabel('Channel out') 179 | fig.tight_layout() 180 | 181 | # Adjust borders to keep labels on-screen 182 | plt.subplots_adjust(bottom=0.2) 183 | 184 | plt.show() 185 | -------------------------------------------------------------------------------- /relativefees.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Check how our fees compare to other fees peers are charged 4 | This script requires describegraph.json 5 | """ 6 | import sys 7 | 8 | import numpy as np 9 | import networkx as nx 10 | 11 | from lib.lnGraph import lnGraph 12 | 13 | if len(sys.argv) > 1 and len(sys.argv[1]) == 66: 14 | mynodekey = sys.argv[1] 15 | else: 16 | print('Please enter the pubkey of the node of interest') 17 | mynodekey = input('Pubkey: ') 18 | 19 | # Payment size for calculating routing costs 20 | median_payment = 100e3 # sat 21 | 22 | # ignore the fees on channels smaller than this 23 | minchancapacity = median_payment * 4 24 | 25 | print('Loading graph') 26 | g = lnGraph.autoload() 27 | nx.freeze(g) 28 | 29 | 30 | def getNodesChannels(pubkey): 31 | pass 32 | 33 | 34 | inboundfees = {} 35 | print('Checking peer fees') 36 | for peerkey in g.adj[mynodekey]: 37 | inboundfees[peerkey] = [] 38 | 39 | for peerpeerkey in g.adj[peerkey]: 40 | peerchan = g.edges[peerkey, peerpeerkey] 41 | 42 | commonchannels = [g.edges[peerkey, peerpeerkey]] 43 | commonchannels.extend(commonchannels[0]['redundant_edges']) 44 | 45 | thispairinfees = [] 46 | for peerchan in commonchannels: 47 | 48 | if any((peerchan['capacity'] < minchancapacity, 49 | peerchan['node1_policy'] is None, 50 | peerchan['node2_policy'] is None, 51 | )): 52 | continue # ignore this insignificant channel 53 | 54 | if peerpeerkey == peerchan['node1_pub']: 55 | inboundrate = peerchan['node1_policy']['fee_rate_milli_msat'] 56 | inboundbase = peerchan['node1_policy']['fee_base_msat'] 57 | elif peerpeerkey == peerchan['node2_pub']: 58 | inboundrate = peerchan['node2_policy']['fee_rate_milli_msat'] 59 | inboundbase = peerchan['node2_policy']['fee_base_msat'] 60 | else: 61 | assert False 62 | 63 | thispairinfees.append(int(inboundbase) / 1e3 + int(inboundrate) / 1e6 * median_payment) 64 | 65 | if len(thispairinfees) > 0: # Can happen with outbound-only peers 66 | inboundfees[peerkey].append(np.median(thispairinfees)) 67 | 68 | print('Checking our fees') 69 | myfeepercentiles = [] 70 | print('My_fee %ile Chan_cap_ksat Alias') 71 | for peerkey in g.adj[mynodekey]: 72 | peer = g.nodes[peerkey] 73 | peerinfees = np.array(sorted(inboundfees[peerkey])) 74 | 75 | commonchannels = [g.edges[mynodekey, peerkey]] 76 | commonchannels.extend(commonchannels[0]['redundant_edges']) 77 | 78 | for chan in commonchannels: 79 | 80 | if chan['capacity'] < minchancapacity: 81 | continue # can't check a channel we skipped earlier 82 | 83 | if mynodekey == chan['node1_pub']: 84 | myfeerate = chan['node1_policy']['fee_rate_milli_msat'] 85 | myfeebase = chan['node1_policy']['fee_base_msat'] 86 | elif mynodekey == chan['node2_pub']: 87 | myfeerate = chan['node2_policy']['fee_rate_milli_msat'] 88 | myfeebase = chan['node2_policy']['fee_base_msat'] 89 | else: 90 | assert False 91 | 92 | myfee = (int(myfeebase) / 1e3 + int(myfeerate) / 1e6 * median_payment) 93 | myfeep = np.sum(peerinfees < myfee) / len(peerinfees) 94 | myfeepercentiles.append(myfeep) 95 | 96 | print(f"{myfee:6.1f} {myfeep:5.1%} {chan['capacity'] / 1e3:5.0f}", peer['alias']) 97 | 98 | print('\n STATS') 99 | print(f'Max {max(myfeepercentiles):.1%}') 100 | print(f'Min {min(myfeepercentiles):.1%}') 101 | print(f'Median {np.median(myfeepercentiles):.1%}') 102 | print(f'Average {np.mean(myfeepercentiles):.1%}') 103 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas 3 | requests 4 | requests[socks] 5 | networkx 6 | igraph 7 | matplotlib 8 | grpcio 9 | grpcio-tools 10 | scipy 11 | tabulate 12 | -------------------------------------------------------------------------------- /setfeepolicy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script is provided for educational purposes, it sets channel 4 | fees based on chain costs and imbalance. 5 | This far more basic than similar tools and likely not flexible enough 6 | for advanced operators, but it should give a good idea of what a fair 7 | fee is to beginners. 8 | Run `python3 setfeepolicy.py -h` for parameters. 9 | """ 10 | 11 | import argparse 12 | 13 | import numpy as np 14 | 15 | from lib.nodeinterface import NodeInterface 16 | 17 | # Handle arguments 18 | parser = argparse.ArgumentParser(description='Set channel rate fees based on simple heuristics') 19 | parser.add_argument('--chainfee', type=int, default=6000, 20 | help='The estimated cost in sats of opening and closing a channel, accounting for force close risk and hiring fees, default 6000') 21 | parser.add_argument('--passes', type=float, default=2, 22 | help='The number of times channels must be fully used to break even, default 2, fixed to 1 for sink channels') 23 | parser.add_argument('--rebalfactor', type=int, default=10, 24 | help='This factor controls how agressively fees will adjust with channel imbalance, default 10') 25 | parser.add_argument('--basefee', type=int, default=50, 26 | help='The base fee in msat to apply to all channels, default 50') 27 | parser.add_argument('--minhtlc', type=int, 28 | help='If basefee is 0 this will be automatically set to prevent free forwards') 29 | parser.add_argument('--sink', action='append', default=[], 30 | help='Specify the pubkeys of nodes that receive vastly more than they send') 31 | parser.add_argument('--sinkpenalty', type=float, default=1.5, 32 | help='Fee multiplier for sink channels, default 1.5') 33 | parser.add_argument('--timelockdelta', type=int, default=40, 34 | help='The time lock delta to apply to all channels, default 40') 35 | parser.add_argument('--apply', action="store_true", 36 | help='By default fees are suggested but not applied, set this flag to apply them') 37 | parser.add_argument('--setmaxhtlc', action="store_true", 38 | help='Adjust max htlc to try to avoid failed forwards') 39 | parser.add_argument('--override', action='append', default=[], 40 | help='Use this fee policy for this node. Format: pubkey,ppm') 41 | args = parser.parse_args() 42 | 43 | mynode = NodeInterface.fromconfig() 44 | 45 | # Find channel ratios and median channel size 46 | # Using median prevents overcharging for small channels and 47 | # undercharging for large ones 48 | chansizes = [] 49 | chanratios = {} 50 | mychannels = mynode.ListChannels().channels 51 | balancesbypeer = {} 52 | for chan in mychannels: 53 | # Need channel size to get average 54 | effective_capacity = (chan.capacity 55 | - chan.local_constraints.chan_reserve_sat 56 | - chan.remote_constraints.chan_reserve_sat) 57 | chansizes.append(effective_capacity) 58 | 59 | # Need these ratios when printing 60 | remote_ratio = chan.remote_balance / chan.capacity 61 | chanratios[chan.channel_point] = remote_ratio 62 | 63 | if chan.remote_pubkey not in balancesbypeer: 64 | balancesbypeer[chan.remote_pubkey] = { 65 | 'local':0,'remote':0, 'total':0} 66 | 67 | balancesbypeer[chan.remote_pubkey]['local' 68 | ] += chan.local_balance - chan.local_constraints.chan_reserve_sat 69 | balancesbypeer[chan.remote_pubkey]['remote' 70 | ] += chan.remote_balance - chan.remote_constraints.chan_reserve_sat 71 | balancesbypeer[chan.remote_pubkey]['total'] += effective_capacity 72 | 73 | # Find the basic rate fee 74 | basicratefee = args.chainfee / np.median(chansizes) / args.passes 75 | 76 | # Modify the rate fee for each channel for channel balance 77 | def imbalancemodifier(remote_ratio): 78 | # The [0-1] integral of the function must equal 1 79 | factor = 1+args.rebalfactor*(remote_ratio-0.5)**5 80 | return max(factor, 0) # Make sure it's not negative 81 | 82 | if imbalancemodifier(0) <= 0: 83 | raise ValueError('--rebalfactor is too large, fees could be zero or negative') 84 | 85 | ppm_overrides = {k:int(p) for k, p in map(lambda s: s.split(','), args.override)} 86 | 87 | newratefeesbypeer = {} 88 | minhtlcsbypeer = {} 89 | maxhtlcsbypeer = {} 90 | for rkey, balances in balancesbypeer.items(): 91 | ratio = balances['remote'] / balances['total'] 92 | ratefee = basicratefee * imbalancemodifier(ratio) 93 | 94 | if rkey in args.sink: 95 | # We only have one pass to profit from, account for this 96 | ratefee *= args.passes * args.sinkpenalty 97 | 98 | if rkey in ppm_overrides: 99 | ratefee = ppm_overrides[rkey]/1e6 100 | 101 | newratefeesbypeer[rkey] = ratefee 102 | 103 | # If base fee is 0, set min htlc to prevent free forwards 104 | if args.basefee <= 0: 105 | minhtlc = int(np.ceil(1/ratefee)) 106 | if args.minhtlc: 107 | minhtlc = max(minhtlc, args.minhtlc) 108 | minhtlcsbypeer[rkey] = minhtlc 109 | elif args.minhtlc: 110 | minhtlcsbypeer[rkey] = args.minhtlc 111 | 112 | if args.setmaxhtlc: 113 | b = max(10e6, balances['local']*1000*0.6) 114 | mh = round(b, -int(np.log10(b+1))+1) 115 | maxhtlcsbypeer[rkey] = int(mh) 116 | 117 | # Print the proposed fees 118 | print('basefee rate minhtlc maxhtlc remote cap Alias') 119 | print(' (msat) fee (sat) (ksat) ratio (ksat) ') 120 | for chan in mychannels: 121 | rate_fee = newratefeesbypeer[chan.remote_pubkey] 122 | remote_ratio = chanratios[chan.channel_point] 123 | base_fee = args.basefee 124 | 125 | if chan.remote_pubkey in minhtlcsbypeer: 126 | minhtlc = minhtlcsbypeer[chan.remote_pubkey] 127 | else: 128 | minhtlc = chan.local_constraints.min_htlc_msat 129 | 130 | if chan.remote_pubkey in maxhtlcsbypeer: 131 | maxhtlc = maxhtlcsbypeer[chan.remote_pubkey] 132 | else: 133 | maxhtlc = chan.local_constraints.max_pending_amt_msat 134 | maxhtlc = int(maxhtlc/1e6) 135 | if maxhtlc >= 1e7: 136 | maxhtlc = ' ...' 137 | 138 | print('{:6} {:.4%} {:6} {:7} {:6.0%} {:6.0f} {}'.format( 139 | base_fee, rate_fee, minhtlc/1e3, maxhtlc, remote_ratio, 140 | chan.capacity/1e3, mynode.getAlias(chan.remote_pubkey))) 141 | 142 | if args.apply: 143 | print('Applying fees') 144 | 145 | for chan in mychannels: 146 | kwargs = dict( 147 | chan_point = chan.channel_point, 148 | fee_rate = newratefeesbypeer[chan.remote_pubkey], 149 | base_fee_msat = args.basefee, 150 | time_lock_delta = args.timelockdelta, 151 | ) 152 | if chan.remote_pubkey in minhtlcsbypeer: 153 | kwargs['min_htlc_msat_specified'] = True 154 | kwargs['min_htlc_msat'] = minhtlcsbypeer[chan.remote_pubkey] 155 | 156 | if maxhtlcsbypeer: 157 | kwargs['max_htlc_msat'] = maxhtlcsbypeer[chan.remote_pubkey] 158 | 159 | mynode.UpdateChannelPolicy(**kwargs) 160 | 161 | print('Fees applied') 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /watchhtlcstream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This is my take on a script for monitoring/dumping htlc events. 4 | It prints to stdout and in CSV format to htlcstream.csv 5 | There is no configurability unlike smallworlnd's stream-lnd-htlcs 6 | """ 7 | 8 | import time 9 | import csv 10 | import traceback 11 | import argparse 12 | 13 | from lib.nodeinterface import NodeInterface 14 | 15 | mynode = NodeInterface.fromconfig() 16 | 17 | mychannels = {} 18 | lastchannelfetchtime = 0 19 | chandatatimeout = 15 20 | 21 | def getChanInfo(chanid): 22 | """ 23 | Fetches channel data from LND 24 | Uses a cache that times out after `chandatatimeout` seconds 25 | Also queries closed channels if `chanid` is not in open channels 26 | """ 27 | global lastchannelfetchtime 28 | uptodate = (time.time() - lastchannelfetchtime < chandatatimeout) 29 | 30 | if uptodate and chanid in mychannels: 31 | return mychannels[chanid] 32 | 33 | for chan in mynode.ListChannels().channels: 34 | mychannels[chan.chan_id] = chan 35 | 36 | lastchannelfetchtime = time.time() 37 | 38 | if chanid in mychannels: 39 | return mychannels[chanid] 40 | 41 | for chan in mynode.ClosedChannels().channels: 42 | mychannels[chan.chan_id] = chan 43 | 44 | if chanid in mychannels: 45 | return mychannels[chanid] 46 | 47 | print('ERROR: Unknown chanid', chanid) 48 | return None 49 | 50 | def getAlias4ChanID(chanid): 51 | chan = getChanInfo(chanid) 52 | if chan is None: 53 | return chanid 54 | alias = mynode.getAlias(chan.remote_pubkey) 55 | return alias 56 | 57 | def getFailureAttribute(einfo, attr): 58 | i = getattr(einfo, attr) 59 | x = einfo.DESCRIPTOR.fields_by_name[attr] 60 | 61 | return x.enum_type.values_by_number[i].name 62 | 63 | forward_event_cache = {} 64 | def popamountsfromcache(key): 65 | amount = forward_event_cache[key]['amt'] 66 | fee = forward_event_cache[key]['fee'] 67 | del forward_event_cache[key] 68 | return amount, fee 69 | 70 | def subscribeEventsPersistent(): 71 | failures = 0 72 | 73 | while True: 74 | events = mynode.router.SubscribeHtlcEvents() 75 | try: 76 | _ = mynode.GetInfo() # test connection 77 | failures = 0 78 | print('Connected to LND. Waiting for first event...') 79 | for e in events: 80 | yield e 81 | except StopIteration: 82 | raise 83 | except Exception as e: 84 | details = 'no details' 85 | try: 86 | details = e.details() 87 | except: 88 | pass 89 | 90 | print('Error:', details) 91 | unavailable = ('Connection refused' in details or 'Connection reset' in details) 92 | unready = ('not yet ready' in details or 'wallet locked' in details) 93 | terminated = (details == "htlc event subscription terminated") 94 | 95 | if any((unavailable, unready, terminated)): 96 | failures += 1 97 | timeout = min(4**failures, 60*60*2) 98 | print(f'Could not connect to lnd, retrying in {timeout}s') 99 | time.sleep(timeout) 100 | continue 101 | 102 | print('Unhandled exception:', repr(e)) 103 | raise e 104 | 105 | 106 | def main(): 107 | parser = argparse.ArgumentParser(description='Script for monitoring/dumping htlc events') 108 | parser.add_argument('--persist', action="store_true", 109 | help='Automatically reconnect to LND') 110 | args = parser.parse_args() 111 | 112 | if args.persist: 113 | events = subscribeEventsPersistent() 114 | else: 115 | events = mynode.router.SubscribeHtlcEvents() 116 | print('Now listening for events') 117 | 118 | for i, event in enumerate(events): 119 | try: 120 | inchanid = event.incoming_channel_id 121 | outchanid = event.outgoing_channel_id 122 | 123 | outcome = event.ListFields()[-1][0].name 124 | eventinfo = getattr(event, outcome) 125 | eventtype = event.EventType.keys()[event.event_type] 126 | timetext = time.ctime(event.timestamp_ns/1e9) 127 | 128 | in_htlc_id = event.incoming_htlc_id 129 | out_htlc_id = event.outgoing_htlc_id 130 | 131 | inalias = outalias = 'N/A' 132 | inrbal = incap = outlbal = outcap = '-' 133 | if inchanid: 134 | inalias = getAlias4ChanID(inchanid) 135 | inchan = getChanInfo(inchanid) 136 | incap = inchan.capacity 137 | inrbal = inchan.remote_balance 138 | 139 | if outchanid: 140 | outalias = getAlias4ChanID(outchanid) 141 | outchan = getChanInfo(outchanid) 142 | # If channel is unknown (closed?) cannot guarantee these values exist 143 | outcap = getattr(outchan, 'capacity', 'UNKNOWN') 144 | outlbal = getattr(outchan, 'local_balance', 'UNKNOWN') 145 | 146 | # Extract forward amount data, if available 147 | amount = fee = '-' 148 | if hasattr(eventinfo, 'info'): 149 | if eventinfo.info.outgoing_amt_msat > 0: 150 | amt_msat = eventinfo.info.outgoing_amt_msat 151 | amount = amt_msat/1000 152 | fee = (eventinfo.info.incoming_amt_msat - amt_msat)/1000 153 | 154 | elif eventinfo.info.incoming_amt_msat > 0: 155 | amt_msat = eventinfo.info.incoming_amt_msat 156 | amount = amt_msat/1000 157 | 158 | # Add a note to quickly point out common scenarios 159 | note = '' 160 | fwdcachekey = (in_htlc_id, out_htlc_id, inchanid, outchanid) 161 | if outcome == 'forward_event': 162 | note = '💸 HTLC in flight.' 163 | forward_event_cache[fwdcachekey] = {'amt':amount, 'fee':fee} 164 | 165 | elif outcome == 'forward_fail_event': 166 | note = '❌ Downstream fwding failure.' 167 | if fwdcachekey in forward_event_cache: 168 | # This data is only found in forward_event, need to fetch it from cache 169 | amount, fee = popamountsfromcache(fwdcachekey) 170 | 171 | elif outcome == 'link_fail_event': 172 | failure_string = eventinfo.failure_string 173 | failure_detail = getFailureAttribute(eventinfo, 'failure_detail') 174 | wire_failure = getFailureAttribute(eventinfo, 'wire_failure') 175 | 176 | if eventtype == 'RECEIVE' and failure_detail == 'UNKNOWN_INVOICE': 177 | note += '🛸 Probe detected. ' 178 | 179 | note += f'❌ Failure(wire: {wire_failure}, detail: {failure_detail}, string: {failure_string})' 180 | 181 | 182 | elif outcome == 'settle_event' and eventtype == 'FORWARD': 183 | note = '✅ Forward successful.' 184 | if fwdcachekey in forward_event_cache: 185 | # This data is only found in forward_event, need to fetch it from cache 186 | amount, fee = popamountsfromcache(fwdcachekey) 187 | 188 | elif outcome == 'settle_event': 189 | note = '✅' 190 | 191 | print(eventtype, 192 | in_htlc_id, out_htlc_id, 193 | timetext, amount,'for', fee, 194 | inalias, f'{inrbal}/{incap}', 195 | '➜', 196 | outalias, f'{outlbal}/{outcap}', 197 | # ~ inchanid, '➜', outchanid, 198 | outcome, 199 | # ~ eventinfo, 200 | note, 201 | ) 202 | 203 | with open('htlcstream.csv', 'a', newline='') as f: 204 | writer = csv.writer(f) 205 | 206 | if i % 30 == 0: 207 | writer.writerow(['Eventtype', 'Htlc_id_in', 'Htlc_id_out', 208 | 'Timestamp', 'Amount', 'Fee', 209 | 'Alias_in','Alias_out', 210 | 'Balance_in','Capacity_in', 211 | 'Balance_out', 'Capacity_out', 212 | 'Chanid_in','Chanid_out', 213 | 'Outcome', 'Details', 'Note']) 214 | 215 | writer.writerow([eventtype, 216 | event.incoming_htlc_id, 217 | event.outgoing_htlc_id, 218 | timetext, amount, fee, 219 | inalias, outalias, 220 | inrbal, incap, 221 | outlbal, outcap, 222 | f"{inchanid}", f"{outchanid}", 223 | outcome, eventinfo, note]) 224 | 225 | except Exception as e: 226 | print('Exception while handling event.', repr(e)) 227 | print(event) 228 | traceback.print_exc() 229 | 230 | if __name__ == '__main__': 231 | main() 232 | --------------------------------------------------------------------------------