Skip to content

Commit 6d466ef

Browse files
committed
ln: move constants to common file and refactor miner funds distro
1 parent 6de4b72 commit 6d466ef

File tree

3 files changed

+92
-27
lines changed

3 files changed

+92
-27
lines changed

resources/scenarios/ln_framework/ln.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@
1515
INSECURE_CONTEXT.check_hostname = False
1616
INSECURE_CONTEXT.verify_mode = ssl.CERT_NONE
1717

18+
# These values may need to be tweaked depending on the network being deployed.
19+
# Currently passes all tests and ln_init succeeds on these examples:
20+
# test/data/LN_10.json
21+
# test/data/LN_50.json
22+
# test/data/LN_100.json
23+
# If any values are changed, you may need to re-build network.yaml with import-network.
24+
# Issues I encountered while setting on these values:
25+
# - Too many blocks generated, ln_init takes too long
26+
# - TX that distributes miner funds to LN wallets exceeds standard weight limit
27+
# - Too many miner distribution TXs result in too-long-mempool-chain
28+
# - Not enough UTXO value, forcing LN nodes to combine UTXOs to open large channels
29+
# which results in the change output being too big which results in the tx
30+
# outputs being ordered unexpectedly (which change at 0 and channel open at 1)
31+
# - LND actual fee rate ends up way off from the expected value
32+
# LN networks with more than 100 nodes and 500 channels may also need to tweak ln_init.py
33+
CHANNEL_OPEN_START_HEIGHT = 500
34+
CHANNEL_OPENS_PER_BLOCK = 200
35+
MAX_FEE_RATE = 40006 # s/vB
36+
FEE_RATE_DECREMENT = 200
37+
assert MAX_FEE_RATE - (FEE_RATE_DECREMENT * CHANNEL_OPENS_PER_BLOCK) > 1
38+
1839

1940
# https://github.com/lightningcn/lightning-rfc/blob/master/07-routing-gossip.md#the-channel_update-message
2041
# We use the field names as written in the BOLT as our canonical, internal field names.

resources/scenarios/ln_init.py

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@
44
from time import sleep
55

66
from commander import Commander
7-
from ln_framework.ln import Policy
7+
from ln_framework.ln import (
8+
Policy,
9+
CHANNEL_OPEN_START_HEIGHT,
10+
CHANNEL_OPENS_PER_BLOCK,
11+
MAX_FEE_RATE,
12+
FEE_RATE_DECREMENT
13+
)
14+
from test_framework.messages import (
15+
COIN,
16+
CTransaction,
17+
CTxOut,
18+
)
19+
from test_framework.address import address_to_scriptpubkey
820

921

1022
class LNInit(Commander):
@@ -39,13 +51,13 @@ def gen(n):
3951
# WALLET ADDRESSES
4052
##
4153
self.log.info("Getting LN wallet addresses...")
42-
ln_addrs = []
54+
ln_addrs = {}
4355

4456
def get_ln_addr(self, ln):
4557
while True:
4658
try:
4759
address = ln.newaddress()
48-
ln_addrs.append(address)
60+
ln_addrs[ln.name] = address
4961
self.log.info(f"Got wallet address {address} from {ln.name}")
5062
break
5163
except Exception as e:
@@ -67,45 +79,71 @@ def get_ln_addr(self, ln):
6779
# FUNDS
6880
##
6981
self.log.info("Funding LN wallets...")
70-
# 298 block base for miner wallet
71-
gen(297)
82+
# One past block generated already to lock out IBD
83+
# One next block to consolidate the miner's coins
84+
# One next block to confirm the distributed coins
85+
# Then the channel open TXs go in the expected block height
86+
gen(CHANNEL_OPEN_START_HEIGHT - 4)
7287
# divvy up the goods, except fee.
73-
# 10 UTXOs per node means 10 channel opens per node per block
74-
split = (miner.getbalance() - 1) // len(ln_addrs) // 10
75-
sends = {}
76-
for _ in range(10):
77-
for addr in ln_addrs:
78-
sends[addr] = split
79-
miner.sendmany("", sends)
80-
# confirm funds in block 299
88+
# Multiple UTXOs per LN wallet so multiple channels can be opened per block
89+
miner_balance = int(miner.getbalance())
90+
# To reduce individual TX weight, consolidate all outputs before distribution
91+
miner.sendtoaddress(miner_addr, miner_balance - 1)
8192
gen(1)
93+
helicopter = CTransaction()
8294

95+
# Provide the source LN node for each channel with a UTXO just big enough
96+
# to open that channel with its capacity plus fee.
97+
channel_openers = []
98+
for ch in self.channels:
99+
if ch["source"] not in channel_openers:
100+
channel_openers.append(ch["source"])
101+
addr = ln_addrs[ch["source"]]
102+
# More than enough to open the channel plus fee and cover LND's "maxFeeRatio"
103+
# As long as all channel capacities are < 4 BTC the change output will be
104+
# larger and occupy tx output 1, leaving the actual channel open at output 0
105+
sat_amt = 10 * COIN
106+
helicopter.vout.append(CTxOut(sat_amt, address_to_scriptpubkey(addr)))
107+
rawtx = miner.fundrawtransaction(helicopter.serialize().hex())
108+
signed_tx = miner.signrawtransactionwithwallet(rawtx['hex'])['hex']
109+
txid = miner.sendrawtransaction(signed_tx)
110+
# confirm funds in last block before channel opens
111+
gen(1)
112+
113+
txstats = miner.gettransaction(txid)
83114
self.log.info(
84-
f"Waiting for funds to be spendable: 10x{split} BTC UTXOs each for {len(ln_addrs)} LN nodes"
115+
"Funds distribution from miner:\n "
116+
+ f"txid: {txid}\n "
117+
+ f"# outputs: {len(txstats['details'])}\n "
118+
+ f"total amount: {txstats['amount']}\n "
119+
+ f"remaining miner balance: {miner.getbalance()}"
85120
)
86121

87-
def confirm_ln_balance(self, ln):
122+
self.log.info("Waiting for funds to be spendable by channel-openers")
123+
124+
def confirm_ln_balance(self, ln_name):
125+
ln = self.lns[ln_name]
88126
while True:
89127
try:
90128
bal = ln.walletbalance()
91-
if bal >= (split * 100000000):
92-
self.log.info(f"LN node {ln.name} confirmed funds")
129+
if bal >= 0:
130+
self.log.info(f"LN node {ln_name} confirmed funds")
93131
break
94132
else:
95-
self.log.info(f"Got balance from {ln.name} but less than expected, retrying in 5 seconds...")
133+
self.log.info(f"Got 0 balance from {ln_name} retrying in 5 seconds...")
96134
sleep(5)
97135
except Exception as e:
98-
self.log.info(f"Couldn't get balance from {ln.name} because {e}, retrying in 5 seconds...")
136+
self.log.info(f"Couldn't get balance from {ln_name} because {e}, retrying in 5 seconds...")
99137
sleep(5)
100138

101139
fund_threads = [
102-
threading.Thread(target=confirm_ln_balance, args=(self, ln)) for ln in self.lns.values()
140+
threading.Thread(target=confirm_ln_balance, args=(self, ln_name)) for ln_name in channel_openers
103141
]
104142
for thread in fund_threads:
105143
thread.start()
106144

107145
all(thread.join() is None for thread in fund_threads)
108-
self.log.info("All LN nodes are funded")
146+
self.log.info("All channel-opening LN nodes are funded")
109147

110148
##
111149
# URIs
@@ -247,12 +285,14 @@ def open_channel(self, ch, fee_rate):
247285
sleep(5)
248286

249287
channels = sorted(ch_by_block[target_block], key=lambda ch: ch["id"]["index"])
288+
if len(channels) > CHANNEL_OPENS_PER_BLOCK:
289+
raise Exception(f"Too many channels in block {target_block}: {len(channels)} / Maximum: {CHANNEL_OPENS_PER_BLOCK}")
250290
index = 0
251-
fee_rate = 5006 # s/vB, decreases by 20 per tx for up to 250 txs per block
291+
fee_rate = MAX_FEE_RATE
252292
ch_threads = []
253293
for ch in channels:
254294
index += 1 # noqa
255-
fee_rate -= 20
295+
fee_rate -= FEE_RATE_DECREMENT
256296
assert index == ch["id"]["index"], "Channel ID indexes are not consecutive"
257297
assert fee_rate >= 1, "Too many TXs in block, out of fee range"
258298
t = threading.Thread(target=open_channel, args=(self, ch, fee_rate))

src/warnet/graph.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
from rich.console import Console
1212
from rich.table import Table
1313

14-
from resources.scenarios.ln_framework.ln import Policy
14+
from resources.scenarios.ln_framework.ln import (
15+
Policy,
16+
CHANNEL_OPEN_START_HEIGHT,
17+
CHANNEL_OPENS_PER_BLOCK
18+
)
1519

1620
from .constants import (
1721
DEFAULT_IMAGE_REPO,
@@ -339,8 +343,8 @@ def _import_network(graph_file_path, output_path):
339343

340344
sorted_edges = sorted(graph["edges"], key=lambda x: int(x["channel_id"]))
341345

342-
# By default we start including channel open txs in block 300
343-
block = 300
346+
# Start including channel open txs at this block height
347+
block = CHANNEL_OPEN_START_HEIGHT
344348
# Coinbase occupies the 0 position!
345349
index = 1
346350
count = 0
@@ -356,7 +360,7 @@ def _import_network(graph_file_path, output_path):
356360
}
357361
tanks[source]["lnd"]["channels"].append(channel)
358362
index += 1
359-
if index > 250:
363+
if index > CHANNEL_OPENS_PER_BLOCK:
360364
index = 1
361365
block += 1
362366
count += 1

0 commit comments

Comments
 (0)