Skip to content

Commit 328d8bb

Browse files
committed
pyln-testing: introduce canned blocks support to bitcoind fixture.
We have to add a send_and_mine_block() for cases where we want to get a txid and then mine it (for canned blocks, we mine it then figure out which tx it was!). And fix up out-by-one in saving blocks. Signed-off-by: Rusty Russell <[email protected]>
1 parent 10afda7 commit 328d8bb

File tree

2 files changed

+88
-37
lines changed

2 files changed

+88
-37
lines changed

contrib/pyln-testing/pyln/testing/fixtures.py

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -125,36 +125,40 @@ def node_cls():
125125

126126

127127
@pytest.fixture
128-
def bitcoind(directory, teardown_checks):
128+
def bitcoind(request, directory, teardown_checks):
129129
chaind = network_daemons[env('TEST_NETWORK', 'regtest')]
130130
bitcoind = chaind(bitcoin_dir=directory)
131131

132-
try:
133-
bitcoind.start()
134-
except Exception:
135-
bitcoind.stop()
136-
raise
137-
138-
info = bitcoind.rpc.getnetworkinfo()
139-
140-
# FIXME: include liquid-regtest in this check after elementsd has been
141-
# updated
142-
if info['version'] < 200100 and env('TEST_NETWORK') != 'liquid-regtest':
143-
bitcoind.rpc.stop()
144-
raise ValueError("bitcoind is too old. At least version 20100 (v0.20.1)"
145-
" is needed, current version is {}".format(info['version']))
146-
elif info['version'] < 160000:
147-
bitcoind.rpc.stop()
148-
raise ValueError("elementsd is too old. At least version 160000 (v0.16.0)"
149-
" is needed, current version is {}".format(info['version']))
150-
151-
info = bitcoind.rpc.getblockchaininfo()
152-
# Make sure we have some spendable funds
153-
if info['blocks'] < 101:
154-
bitcoind.generate_block(101 - info['blocks'])
155-
elif bitcoind.rpc.getwalletinfo()['balance'] < 1:
156-
logging.debug("Insufficient balance, generating 1 block")
157-
bitcoind.generate_block(1)
132+
# @pytest.mark.parametrize('bitcoind', [False], indirect=True) if you don't
133+
# want bitcoind started!
134+
if getattr(request, 'param', True):
135+
try:
136+
bitcoind.start()
137+
except Exception:
138+
bitcoind.stop()
139+
raise
140+
141+
info = bitcoind.rpc.getnetworkinfo()
142+
143+
# FIXME: include liquid-regtest in this check after elementsd has been
144+
# updated
145+
if info['version'] < 200100 and env('TEST_NETWORK') != 'liquid-regtest':
146+
bitcoind.rpc.stop()
147+
raise ValueError("bitcoind is too old. At least version 20100 (v0.20.1)"
148+
" is needed, current version is {}".format(info['version']))
149+
elif info['version'] < 160000:
150+
bitcoind.rpc.stop()
151+
raise ValueError("elementsd is too old. At least version 160000 (v0.16.0)"
152+
" is needed, current version is {}".format(info['version']))
153+
154+
info = bitcoind.rpc.getblockchaininfo()
155+
156+
# Make sure we have some spendable funds
157+
if info['blocks'] < 101:
158+
bitcoind.generate_block(101 - info['blocks'])
159+
elif bitcoind.rpc.getwalletinfo()['balance'] < 1:
160+
logging.debug("Insufficient balance, generating 1 block")
161+
bitcoind.generate_block(1)
158162

159163
yield bitcoind
160164

contrib/pyln-testing/pyln/testing/utils.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ def __init__(self, bitcoin_dir="/tmp/bitcoind-test", rpcport=None):
411411
self.bitcoin_dir = bitcoin_dir
412412
self.rpcport = rpcport
413413
self.prefix = 'bitcoind'
414+
self.canned_blocks = None
414415

415416
regtestdir = os.path.join(bitcoin_dir, 'regtest')
416417
if not os.path.exists(regtestdir):
@@ -446,15 +447,18 @@ def __del__(self):
446447
if self.reserved_rpcport is not None:
447448
drop_unused_port(self.reserved_rpcport)
448449

449-
def start(self):
450+
def start(self, wallet_file=None):
450451
TailableProc.start(self)
451452
self.wait_for_log("Done loading", timeout=TIMEOUT)
452453

453454
logging.info("BitcoinD started")
454-
try:
455-
self.rpc.createwallet("lightningd-tests")
456-
except JSONRPCError:
457-
self.rpc.loadwallet("lightningd-tests")
455+
if wallet_file:
456+
self.rpc.restorewallet("lightningd-tests", wallet_file)
457+
else:
458+
try:
459+
self.rpc.createwallet("lightningd-tests")
460+
except JSONRPCError:
461+
self.rpc.loadwallet("lightningd-tests")
458462

459463
def stop(self):
460464
for p in self.proxies:
@@ -468,6 +472,10 @@ def get_proxy(self):
468472
proxy.start()
469473
return proxy
470474

475+
def set_canned_blocks(self, blocks):
476+
"""Set blocks as an array of blocks to "generate", or None to reset"""
477+
self.canned_blocks = blocks
478+
471479
# wait_for_mempool can be used to wait for the mempool before generating blocks:
472480
# True := wait for at least 1 transation
473481
# int > 0 := wait for at least N transactions
@@ -482,6 +490,16 @@ def generate_block(self, numblocks=1, wait_for_mempool=0, to_addr=None, needfeer
482490
else:
483491
wait_for(lambda: len(self.rpc.getrawmempool()) >= wait_for_mempool)
484492

493+
# Use canned blocks if we have them (fails if we run out!).
494+
if self.canned_blocks is not None:
495+
ret = []
496+
while numblocks > 0:
497+
self.rpc.submitblock(self.canned_blocks[0])
498+
ret.append(self.rpc.getbestblockhash())
499+
numblocks -= 1
500+
del self.canned_blocks[0]
501+
return ret
502+
485503
mempool = self.rpc.getrawmempool(True)
486504
logging.debug("Generating {numblocks}, confirming {lenmempool} transactions: {mempool}".format(
487505
numblocks=numblocks,
@@ -509,6 +527,21 @@ def generate_block(self, numblocks=1, wait_for_mempool=0, to_addr=None, needfeer
509527

510528
return self.rpc.generatetoaddress(numblocks, to_addr)
511529

530+
def send_and_mine_block(self, addr, sats):
531+
"""Sometimes we want the txid. We assume it's the first tx for canned blocks"""
532+
if self.canned_blocks:
533+
self.generate_block(1)
534+
# Find which non-coinbase txs sent to this address: return txid
535+
for txid in self.rpc.getblock(self.rpc.getbestblockhash())['tx'][1:]:
536+
for out in self.rpc.getrawtransaction(txid, 1)['vout']:
537+
if out['scriptPubKey'].get('address') == addr:
538+
return txid
539+
assert False, f"No address {addr} in block {self.rpc.getblock(self.rpc.getbestblockhash())}"
540+
541+
txid = self.rpc.sendtoaddress(addr, sats / 10**8)
542+
self.generate_block(1)
543+
return txid
544+
512545
def simple_reorg(self, height, shift=0):
513546
"""
514547
Reorganize chain by creating a fork at height=[height] and re-mine all mempool
@@ -526,6 +559,7 @@ def simple_reorg(self, height, shift=0):
526559
forward to h1.
527560
2. Set [height]=h2 and [shift]= h1-h2
528561
"""
562+
assert self.canned_blocks is None
529563
hashes = []
530564
fee_delta = 1000000
531565
orig_len = self.rpc.getblockcount()
@@ -559,7 +593,7 @@ def save_blocks(self):
559593
"""Bundle up blocks into an array, for restore_blocks"""
560594
blocks = []
561595
numblocks = self.rpc.getblockcount()
562-
for bnum in range(1, numblocks):
596+
for bnum in range(1, numblocks + 1):
563597
bhash = self.rpc.getblockhash(bnum)
564598
blocks.append(self.rpc.getblock(bhash, False))
565599
return blocks
@@ -892,6 +926,10 @@ def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fai
892926
self.daemon.opts['grpc-port'] = grpc_port
893927
self.grpc_port = grpc_port or 9736
894928

929+
# If bitcoind is serving canned blocks, it will keep initialblockdownload on true!
930+
if self.bitcoin.canned_blocks is not None:
931+
self.daemon.opts['dev-ignore-ibd'] = True
932+
895933
def _create_rpc(self, jsonschemas):
896934
"""Prepares anything related to the RPC.
897935
"""
@@ -987,10 +1025,12 @@ def openchannel(self, remote_node, capacity=FUNDAMOUNT, addrtype="bech32", confi
9871025

9881026
def fundwallet(self, sats, addrtype="bech32", mine_block=True):
9891027
addr = self.rpc.newaddr(addrtype)[addrtype]
990-
txid = self.bitcoin.rpc.sendtoaddress(addr, sats / 10**8)
9911028
if mine_block:
992-
self.bitcoin.generate_block(1)
1029+
txid = self.bitcoin.send_and_mine_block(addr, sats)
9931030
self.daemon.wait_for_log('Owning output .* txid {} CONFIRMED'.format(txid))
1031+
else:
1032+
txid = self.bitcoin.rpc.sendtoaddress(addr, sats / 10**8)
1033+
9941034
return addr, txid
9951035

9961036
def fundbalancedchannel(self, remote_node, total_capacity=FUNDAMOUNT, announce=True):
@@ -1118,8 +1158,7 @@ def has_funds_on_addr(addr):
11181158
# We should not have funds on that address yet, we just generated it.
11191159
assert not has_funds_on_addr(addr)
11201160

1121-
self.bitcoin.rpc.sendtoaddress(addr, (amount + 1000000) / 10**8)
1122-
self.bitcoin.generate_block(1)
1161+
self.bitcoin.send_and_mine_block(addr, amount + 1000000)
11231162

11241163
# Now we should.
11251164
wait_for(lambda: has_funds_on_addr(addr))
@@ -1134,10 +1173,18 @@ def has_funds_on_addr(addr):
11341173
**kwargs)
11351174
blockid = self.bitcoin.generate_block(1, wait_for_mempool=res['txid'])[0]
11361175

1176+
txnum = None
11371177
for i, txid in enumerate(self.bitcoin.rpc.getblock(blockid)['tx']):
11381178
if txid == res['txid']:
11391179
txnum = i
11401180

1181+
if txnum is None:
1182+
print(f"mempool = {self.bitcoin.rpc.getrawmempool()}")
1183+
print("txs:")
1184+
for txid in self.bitcoin.rpc.getblock(blockid)['tx'][1:]:
1185+
print(f"txid {txid}: {self.bitcoin.rpc.getrawtransaction(txid)} {self.bitcoin.rpc.getrawtransaction(txid, 1)}")
1186+
assert False, f"txid {res['txid']} not found"
1187+
11411188
scid = "{}x{}x{}".format(self.bitcoin.rpc.getblockcount(),
11421189
txnum, res['outnum'])
11431190

0 commit comments

Comments
 (0)