4
4
from time import sleep
5
5
6
6
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
8
20
9
21
10
22
class LNInit (Commander ):
@@ -39,13 +51,13 @@ def gen(n):
39
51
# WALLET ADDRESSES
40
52
##
41
53
self .log .info ("Getting LN wallet addresses..." )
42
- ln_addrs = []
54
+ ln_addrs = {}
43
55
44
56
def get_ln_addr (self , ln ):
45
57
while True :
46
58
try :
47
59
address = ln .newaddress ()
48
- ln_addrs . append ( address )
60
+ ln_addrs [ ln . name ] = address
49
61
self .log .info (f"Got wallet address { address } from { ln .name } " )
50
62
break
51
63
except Exception as e :
@@ -67,45 +79,71 @@ def get_ln_addr(self, ln):
67
79
# FUNDS
68
80
##
69
81
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 )
72
87
# 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 )
81
92
gen (1 )
93
+ helicopter = CTransaction ()
82
94
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 )
83
114
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 ()} "
85
120
)
86
121
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 ]
88
126
while True :
89
127
try :
90
128
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" )
93
131
break
94
132
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..." )
96
134
sleep (5 )
97
135
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..." )
99
137
sleep (5 )
100
138
101
139
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
103
141
]
104
142
for thread in fund_threads :
105
143
thread .start ()
106
144
107
145
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" )
109
147
110
148
##
111
149
# URIs
@@ -247,12 +285,14 @@ def open_channel(self, ch, fee_rate):
247
285
sleep (5 )
248
286
249
287
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 } " )
250
290
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
252
292
ch_threads = []
253
293
for ch in channels :
254
294
index += 1 # noqa
255
- fee_rate -= 20
295
+ fee_rate -= FEE_RATE_DECREMENT
256
296
assert index == ch ["id" ]["index" ], "Channel ID indexes are not consecutive"
257
297
assert fee_rate >= 1 , "Too many TXs in block, out of fee range"
258
298
t = threading .Thread (target = open_channel , args = (self , ch , fee_rate ))
0 commit comments