mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-19 14:14:20 +01:00
This updates all plugins *which already have requirements.txt* to pyln-client from pylightning.
183 lines
6.1 KiB
Python
Executable File
183 lines
6.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from bech32 import bech32_decode, CHARSET, convertbits
|
|
from lib_autopilot import Autopilot, Strategy
|
|
from pyln.client import LightningRpc, Plugin, RpcError
|
|
import random
|
|
import math
|
|
import networkx as nx
|
|
import dns.resolver
|
|
import time
|
|
|
|
class CLightning_autopilot(Autopilot):
|
|
|
|
def __init__(self, rpc):
|
|
self.__rpc_interface = rpc
|
|
|
|
print("No input specified download graph from peers")
|
|
G = self.__download_graph()
|
|
Autopilot.__init__(self, G)
|
|
|
|
def __get_seed_keys(self):
|
|
"""
|
|
retrieve the nodeids of the ln seed nodes from lseed.bitcoinstats.com
|
|
"""
|
|
domain = "lseed.bitcoinstats.com"
|
|
srv_records = dns.resolver.query(domain,"SRV")
|
|
res = []
|
|
for srv in srv_records:
|
|
bech32 = str(srv.target).rstrip(".").split(".")[0]
|
|
data = bech32_decode(bech32)[1]
|
|
decoded = convertbits(data, 5, 4)
|
|
res.append("".join(
|
|
['{:1x}'.format(integer) for integer in decoded])[:-1])
|
|
return res
|
|
|
|
def __connect_to_seeds(self):
|
|
"""
|
|
sets up peering connection to seed nodes of the lightning network
|
|
|
|
This is necessary in case the node operating the autopilot has never
|
|
been connected to the lightning network.
|
|
"""
|
|
seed_keys = self.__get_seed_keys()
|
|
random.shuffle(seed_keys)
|
|
for nodeid in seed_keys:
|
|
try:
|
|
print("peering with node: {}".format(nodeid))
|
|
self.__rpc_interface.connect(nodeid)
|
|
# FIXME: better strategy than sleep(2) for building up
|
|
time.sleep(2)
|
|
except RpcError as e:
|
|
print("Unable to connect to node: {}".format(nodeid))
|
|
print(e)
|
|
|
|
def __download_graph(self):
|
|
"""
|
|
Downloads a local copy of the nodes view of the lightning network
|
|
|
|
This copy is retrieved by listnodes and listedges RPC calls and will
|
|
thus be incomplete as peering might not be ready yet.
|
|
"""
|
|
|
|
# FIXME: it is a real problem that we don't know how many nodes there
|
|
# could be. In particular billion nodes networks will outgrow memory
|
|
G = nx.Graph()
|
|
print("Instantiated networkx graph to store the lightning network")
|
|
|
|
nodes = []
|
|
print("Attempt RPC-call to download nodes from the lightning network")
|
|
try:
|
|
while len(nodes) == 0:
|
|
peers = self.__rpc_interface.listpeers()["peers"]
|
|
if len(peers) < 1:
|
|
self.__connect_to_seeds()
|
|
nodes = self.__rpc_interface.listnodes()["nodes"]
|
|
except ValueError as e:
|
|
print("Node list could not be retrieved from the peers of the lightning network")
|
|
raise e
|
|
|
|
for node in nodes:
|
|
G.add_node(node["nodeid"], **node)
|
|
|
|
print("Number of nodes found and added to the local networkx graph: {}".format(len(nodes)))
|
|
|
|
channels = {}
|
|
try:
|
|
print("Attempt RPC-call to download channels from the lightning network")
|
|
channels = self.__rpc_interface.listchannels()["channels"]
|
|
print("Number of retrieved channels: {}".format(len(channels)))
|
|
except ValueError as e:
|
|
print("Channel list could not be retrieved from the peers of the lightning network")
|
|
return False
|
|
|
|
for channel in channels:
|
|
G.add_edge(
|
|
channel["source"],
|
|
channel["destination"],
|
|
**channel)
|
|
|
|
return G
|
|
|
|
def connect(self, candidates, balance=1000000, dryrun=False):
|
|
pdf = self.calculate_statistics(candidates)
|
|
connection_dict = self.calculate_proposed_channel_capacities(pdf, balance)
|
|
for nodeid, fraction in connection_dict.items():
|
|
try:
|
|
satoshis = math.ceil(balance * fraction)
|
|
print("Try to open channel with a capacity of {} to node {}".format(satoshis, nodeid))
|
|
if not dryrun:
|
|
self.__rpc_interface.connect(nodeid)
|
|
self.__rpc_interface.fundchannel(nodeid, satoshis)
|
|
except ValueError as e:
|
|
print("Could not open a channel to {} with capacity of {}. Error: {}".format(nodeid, satoshis, str(e)))
|
|
|
|
|
|
try:
|
|
# C-lightning v0.7.2
|
|
plugin = Plugin(dynamic=False)
|
|
except:
|
|
plugin = Plugin()
|
|
|
|
|
|
@plugin.init()
|
|
def init(configuration, options, plugin):
|
|
plugin.num_channels = int(options['autopilot-num-channels'])
|
|
plugin.percent = int(options['autopilot-percent'])
|
|
plugin.min_capacity_sat = int(options['autopilot-min-channel-size-msat']) / 1000
|
|
|
|
plugin.autopilot = CLightning_autopilot(plugin.rpc)
|
|
|
|
|
|
@plugin.method('autopilot-run-once')
|
|
def run_once(plugin, dryrun=False):
|
|
# Let's start by inspecting the current state of the node
|
|
funds = plugin.rpc.listfunds()
|
|
output_funds = sum([o['value'] for o in funds['outputs'] if o['status'] == 'confirmed'])
|
|
channels = funds['channels']
|
|
available_funds = output_funds / 100.0 * plugin.percent
|
|
|
|
# Now we can look whether and how we'd like to open new channels. This
|
|
# depends on available funds and the number of channels we were configured
|
|
# to open
|
|
num_channels = min(
|
|
int(available_funds / plugin.min_capacity_sat),
|
|
plugin.num_channels - len(channels)
|
|
)
|
|
|
|
# Each channel will have this capacity
|
|
channel_capacity = math.floor(available_funds / num_channels)
|
|
|
|
print("I'd like to open {} new channels with {} satoshis each".format(num_channels, channel_capacity))
|
|
|
|
candidates = plugin.autopilot.find_candidates(
|
|
num_channels,
|
|
strategy=Strategy.DIVERSE,
|
|
percentile=0.5
|
|
)
|
|
plugin.autopilot.connect(candidates, available_funds, dryrun=dryrun)
|
|
|
|
|
|
plugin.add_option(
|
|
'autopilot-percent',
|
|
'75',
|
|
'What percentage of funds should be under the autopilots control?'
|
|
)
|
|
|
|
|
|
plugin.add_option(
|
|
'autopilot-num-channels',
|
|
'10',
|
|
'How many channels should the autopilot aim for?'
|
|
)
|
|
|
|
|
|
plugin.add_option(
|
|
'autopilot-min-channel-size-msat',
|
|
'100000000',
|
|
'Minimum channel size to open.',
|
|
)
|
|
|
|
|
|
plugin.run()
|