Skip to content

Commit

Permalink
store channel data from network.yaml in configmaps to open channels
Browse files Browse the repository at this point in the history
  • Loading branch information
pinheadmz committed Nov 8, 2024
1 parent e9dfabe commit fac8d11
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 57 deletions.
12 changes: 12 additions & 0 deletions resources/charts/bitcoincore/charts/lnd/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,15 @@ data:
alias={{ include "lnd.fullname" . }}
externalhosts={{ include "lnd.fullname" . }}
tlsextradomain={{ include "lnd.fullname" . }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "lnd.fullname" . }}-channels
labels:
channels: "true"
{{- include "lnd.labels" . | nindent 4 }}
data:
source: {{ include "lnd.fullname" . }}
channels: |
{{ .Values.channels | toJson }}
3 changes: 0 additions & 3 deletions resources/charts/bitcoincore/charts/lnd/templates/pod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ metadata:
name: {{ include "lnd.fullname" . }}
labels:
{{- include "lnd.labels" . | nindent 4 }}
{{- with .Values.extraLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
Expand Down
2 changes: 1 addition & 1 deletion resources/charts/bitcoincore/charts/lnd/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ config: ""

defaultConfig: ""

extraLabels: {}
channels: []
2 changes: 1 addition & 1 deletion src/warnet/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def deploy_network(directory: Path, debug: bool = False, namespace: Optional[str
try:
temp_override_file_path = ""
node_name = node.get("name")
node_config_override = {k: v for k, v in node.items() if k != "name" and k != "lnd"}
node_config_override = {k: v for k, v in node.items() if k != "name"}

cmd = f"{HELM_COMMAND} {node_name} {BITCOIN_CHART_LOCATION} --namespace {namespace} -f {defaults_file_path}"
if debug:
Expand Down
14 changes: 11 additions & 3 deletions src/warnet/k8s.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,19 @@ def get_pod_exit_status(pod_name, namespace: Optional[str] = None):
return None


def get_edges(namespace: Optional[str] = None) -> any:
def get_channels(namespace: Optional[str] = None) -> any:
namespace = get_default_namespace_or(namespace)
sclient = get_static_client()
configmap = sclient.read_namespaced_config_map(name="edges", namespace=namespace)
return json.loads(configmap.data["data"])
config_maps = sclient.list_namespaced_config_map(
namespace=namespace, label_selector="channels=true"
)
channels = []
for cm in config_maps.items:
channel_jsons = json.loads(cm.data["channels"])
for channel_json in channel_jsons:
channel_json["source"] = cm.data["source"]
channels.append(channel_json)
return channels


def create_kubernetes_object(
Expand Down
51 changes: 44 additions & 7 deletions src/warnet/ln.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

import click

from .k8s import get_default_namespace_or, get_pod
from .k8s import (
get_channels,
get_default_namespace_or,
get_pod,
)
from .process import run_command


Expand All @@ -25,8 +29,6 @@ def rpc(pod: str, method: str, params: str, namespace: Optional[str]):


def _rpc(pod_name: str, method: str, params: str = "", namespace: Optional[str] = None):
# TODO: when we add back cln we'll need to describe the pod,
# get a label with implementation type and then adjust command
pod = get_pod(pod_name)
namespace = get_default_namespace_or(namespace)
chain = pod.metadata.labels["chain"]
Expand All @@ -42,9 +44,12 @@ def pubkey(
"""
Get lightning node pub key from <ln pod name>
"""
# TODO: again here, cln will need a different command
print(_pubkey(pod))


def _pubkey(pod: str):
info = _rpc(pod, "getinfo")
print(json.loads(info)["identity_pubkey"])
return json.loads(info)["identity_pubkey"]


@ln.command()
Expand All @@ -55,8 +60,40 @@ def host(
"""
Get lightning node host from <ln pod name>
"""
# TODO: again here, cln will need a different command
print(_host(pod))


def _host(pod):
info = _rpc(pod, "getinfo")
uris = json.loads(info)["uris"]
if uris and len(uris) >= 0:
print(uris[0].split("@")[1])
return uris[0].split("@")[1]
else:
return ""


@ln.command()
def open_all_channels():
"""
Open all channels with source policies defined in the network.yaml
<!> IGNORES HARD CODED CHANNEL IDs <!>
<!> Should only be run once or you'll end up with duplicate channels <!>
"""
channels = get_channels()
commands = []
for ch in channels:
pk = _pubkey(ch["target"])
host = _host(ch["target"])
local_amt = ch["local_amt"]
push_amt = ch.get("push_amt", 0)
assert pk, f"{ch['target']} has no public key"
assert host, f"{ch['target']} has no host"
assert local_amt, "Channel has no local_amount"
commands.append(
(
ch["source"],
f"openchannel --node_key {pk} --connect {host} --local_amt {local_amt} --push_amt {push_amt}",
)
)
for command in commands:
_rpc(*command)
45 changes: 44 additions & 1 deletion test/data/ln/network.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,47 @@ nodes:
addnode:
- tank-0000
ln:
lnd: true
lnd: true

- name: tank-0003
addnode:
- tank-0000
ln:
lnd: true
lnd:
config: |
bitcoin.timelockdelta=33
channels:
- id:
block: 300
index: 1
target: tank-0004-ln
local_amt: 100000
push_amt: 50000

- name: tank-0004
addnode:
- tank-0000
ln:
lnd: true
lnd:
channels:
- id:
block: 300
index: 2
target: tank-0005-ln
local_amt: 50000
push_amt: 25000

- name: tank-0005
addnode:
- tank-0000
ln:
lnd: true
lnd:
channels:
- id:
block: 301
index: 1
target: tank-0000-ln
local_amt: 25000
4 changes: 4 additions & 0 deletions test/data/ln/node-defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ image:
repository: bitcoindevproject/bitcoin
pullPolicy: IfNotPresent
tag: "27.0"

lnd:
defaultConfig: |
color=#000000
106 changes: 65 additions & 41 deletions test/ln_basic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,33 @@ class LNBasicTest(TestBase):
def __init__(self):
super().__init__()
self.network_dir = Path(os.path.dirname(__file__)) / "data" / "ln"
self.miner_addr = ""
self.lns = [
"tank-0000-ln",
"tank-0001-ln",
"tank-0002-ln",
"tank-0003-ln",
"tank-0004-ln",
"tank-0005-ln",
]

def run_test(self):
try:
# Wait for all nodes to wake up
self.setup_network()
# Send money to all LN nodes
self.fund_wallets()

# Manually open two channels between first three nodes
# and send a payment
self.manual_open_channels()
self.wait_for_gossip_sync()
self.pay_invoice()
self.wait_for_gossip_sync(self.lns[:3], 2)
self.pay_invoice(sender="tank-0000-ln", recipient="tank-0002-ln")

# Automatically open channels from network.yaml
self.automatic_open_channels()
self.wait_for_gossip_sync(self.lns[3:], 3)
# push_amt should enable payments from target to source
self.pay_invoice(sender="tank-0005-ln", recipient="tank-0003-ln")
finally:
self.cleanup()

Expand All @@ -29,43 +47,41 @@ def setup_network(self):
self.log.info(self.warnet(f"deploy {self.network_dir}"))
self.wait_for_all_tanks_status(target="running")

self.warnet("bitcoin rpc tank-0000 createwallet miner")
self.warnet("bitcoin rpc tank-0000 -generate 110")
self.wait_for_predicate(
lambda: int(self.warnet("bitcoin rpc tank-0000 getblockcount")) > 100
)

def wait_for_all_ln_rpc():
nodes = ["tank-0000-ln", "tank-0001-ln", "tank-0002-ln"]
for node in nodes:
for ln in self.lns:
try:
self.warnet(f"ln rpc {node} getinfo")
self.warnet(f"ln rpc {ln} getinfo")
except Exception:
print(f"LN node {node} not ready for rpc yet")
print(f"LN node {ln} not ready for rpc yet")
return False
return True

self.wait_for_predicate(wait_for_all_ln_rpc)

def fund_wallets(self):
self.warnet("bitcoin rpc tank-0000 createwallet miner")
self.warnet("bitcoin rpc tank-0000 -generate 110")
self.wait_for_predicate(
lambda: int(self.warnet("bitcoin rpc tank-0000 getblockcount")) > 100
)

addrs = []
for lnd in ["tank-0000-ln", "tank-0001-ln", "tank-0002-ln"]:
addrs.append(json.loads(self.warnet(f"ln rpc {lnd} newaddress p2wkh"))["address"])

self.warnet(
"bitcoin rpc tank-0000 sendmany '' '{"
+ f'"{addrs[0]}":10,"{addrs[1]}":10,"{addrs[2]}":10'
+ "}'"
)
outputs = ""
for lnd in self.lns:
addr = json.loads(self.warnet(f"ln rpc {lnd} newaddress p2wkh"))["address"]
outputs += f',"{addr}":10'
# trim first comma
outputs = outputs[1:]

self.warnet("bitcoin rpc tank-0000 sendmany '' '{" + outputs + "}'")
self.warnet("bitcoin rpc tank-0000 -generate 1")

def manual_open_channels(self):
# 0 -> 1 -> 2
pk1 = self.warnet("ln pubkey tank-0001-ln")
pk2 = self.warnet("ln pubkey tank-0002-ln")

host1 = None
host2 = None
host1 = ""
host2 = ""

while not host1 or not host2:
if not host1:
Expand All @@ -92,28 +108,36 @@ def wait_for_two_txs():

self.warnet("bitcoin rpc tank-0000 -generate 10")

def wait_for_gossip_sync(self):
chs0 = []
chs1 = []
chs2 = []

while len(chs0) != 2 or len(chs1) != 2 or len(chs2) != 2:
if len(chs0) != 2:
chs0 = json.loads(self.warnet("ln rpc tank-0000-ln describegraph"))["edges"]
if len(chs1) != 2:
chs1 = json.loads(self.warnet("ln rpc tank-0001-ln describegraph"))["edges"]
if len(chs2) != 2:
chs2 = json.loads(self.warnet("ln rpc tank-0002-ln describegraph"))["edges"]
def wait_for_gossip_sync(self, nodes, expected):
while len(nodes) > 0:
for node in nodes:
chs = json.loads(self.warnet(f"ln rpc {node} describegraph"))["edges"]
if len(chs) >= expected:
nodes.remove(node)
sleep(1)

def pay_invoice(self):
inv = json.loads(self.warnet("ln rpc tank-0002-ln addinvoice --amt 1000"))
def pay_invoice(self, sender: str, recipient: str):
init_balance = int(json.loads(self.warnet(f"ln rpc {recipient} channelbalance"))["balance"])
inv = json.loads(self.warnet(f"ln rpc {recipient} addinvoice --amt 1000"))
print(inv)
print(self.warnet(f"ln rpc tank-0000-ln payinvoice -f {inv['payment_request']}"))
print(self.warnet(f"ln rpc {sender} payinvoice -f {inv['payment_request']}"))

def wait_for_success():
return json.loads(self.warnet("ln rpc tank-0002-ln channelbalance"))["balance"] == 1000
self.wait_for_predicate(wait_for_success)
return (
int(json.loads(self.warnet(f"ln rpc {recipient} channelbalance"))["balance"])
== init_balance + 1000
)

self.wait_for_predicate(wait_for_success)

def automatic_open_channels(self):
self.warnet("ln open-all-channels")

def wait_for_three_txs():
return json.loads(self.warnet("bitcoin rpc tank-0000 getmempoolinfo"))["size"] == 3

self.wait_for_predicate(wait_for_three_txs)
self.warnet("bitcoin rpc tank-0000 -generate 10")


if __name__ == "__main__":
Expand Down

0 comments on commit fac8d11

Please sign in to comment.