Skip to content

Commit 95563a7

Browse files
committed
pytest: test Bitcoin plugin registration and the bcli plugin
1 parent 765033d commit 95563a7

File tree

5 files changed

+150
-0
lines changed

5 files changed

+150
-0
lines changed

lightningd/bitcoind.c

+3
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ void bitcoind_check_commands(struct bitcoind *bitcoind)
8080
for (i = 0; i < ARRAY_SIZE(methods); i++) {
8181
p = find_plugin_for_command(bitcoind->ld, methods[i]);
8282
if (p == NULL) {
83+
/* For testing .. */
84+
log_debug(bitcoind->ld->log, "Missing a Bitcoin plugin"
85+
" command");
8386
fatal("Could not access the plugin for %s, is a "
8487
"Bitcoin plugin (by default plugins/bcli) "
8588
"registered ?", methods[i]);

lightningd/chaintopology.c

+3
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,9 @@ void setup_topology(struct chain_topology *topo,
10011001
/* This waits for bitcoind. */
10021002
bitcoind_check_commands(topo->bitcoind);
10031003

1004+
/* For testing.. */
1005+
log_debug(topo->ld->log, "All Bitcoin plugin commands registered");
1006+
10041007
/* Sanity checks, then topology initialization. */
10051008
bitcoind_getchaininfo(topo->bitcoind, true, check_chain, topo);
10061009

tests/plugins/bitcoin/part1.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python3
2+
"""
3+
This registers part of the Bitcoin backend methods.
4+
We only use it for testing startup and we don't care about the actual values.
5+
"""
6+
import time
7+
8+
from pyln.client import Plugin
9+
10+
11+
plugin = Plugin()
12+
13+
14+
@plugin.method("getfeerate")
15+
def getfeerate(plugin, **kwargs):
16+
time.sleep(1)
17+
return {}
18+
19+
20+
@plugin.method("getrawblockbyheight")
21+
def getblock(plugin, **kwargs):
22+
time.sleep(1)
23+
return {}
24+
25+
26+
@plugin.method("getchaininfo")
27+
def getchaininfo(plugin, **kwargs):
28+
time.sleep(1)
29+
return {}
30+
31+
32+
# We don't use these options, but it allows us to get to the expected failure.
33+
plugin.add_option("bitcoin-rpcuser", "", "")
34+
plugin.add_option("bitcoin-rpcpassword", "", "")
35+
plugin.add_option("bitcoin-rpcport", "", "")
36+
37+
plugin.run()

tests/plugins/bitcoin/part2.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
"""
3+
This registers part of the Bitcoin backend methods.
4+
We only use it for testing startup and we don't care about the actual values.
5+
"""
6+
import time
7+
8+
from pyln.client import Plugin
9+
10+
11+
plugin = Plugin()
12+
13+
14+
@plugin.method("sendrawtransaction")
15+
def sendtx(plugin, **kwargs):
16+
time.sleep(1)
17+
return {}
18+
19+
20+
@plugin.method("getutxout")
21+
def gettxout(plugin, **kwargs):
22+
time.sleep(1)
23+
return {}
24+
25+
26+
plugin.run()

tests/test_plugin.py

+81
Original file line numberDiff line numberDiff line change
@@ -932,3 +932,84 @@ def test_hook_chaining(node_factory):
932932
assert(l2.daemon.is_in_log(
933933
r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash {}'.format(hash3)
934934
))
935+
936+
937+
def test_bitcoin_backend(node_factory, bitcoind):
938+
"""
939+
This tests interaction with the Bitcoin backend, but not specifically bcli
940+
"""
941+
l1 = node_factory.get_node(start=False, options={"disable-plugin": "bcli"},
942+
may_fail=True, allow_broken_log=True)
943+
944+
# We don't start if we haven't all the required methods registered.
945+
plugin = os.path.join(os.getcwd(), "tests/plugins/bitcoin/part1.py")
946+
l1.daemon.opts["plugin"] = plugin
947+
try:
948+
l1.daemon.start()
949+
except ValueError:
950+
assert l1.daemon.is_in_log("Missing a Bitcoin plugin command")
951+
# Now we should start if all the commands are registered, even if they
952+
# are registered by two distincts plugins.
953+
del l1.daemon.opts["plugin"]
954+
l1.daemon.opts["plugin-dir"] = os.path.join(os.getcwd(),
955+
"tests/plugins/bitcoin/")
956+
try:
957+
l1.daemon.start()
958+
except ValueError:
959+
msg = "All Bitcoin plugin commands registered"
960+
assert l1.daemon.is_in_log(msg)
961+
else:
962+
raise Exception("We registered all commands but couldn't start!")
963+
else:
964+
raise Exception("We could start without all commands registered !!")
965+
966+
# But restarting with just bcli is ok
967+
del l1.daemon.opts["plugin-dir"]
968+
del l1.daemon.opts["disable-plugin"]
969+
l1.start()
970+
assert l1.daemon.is_in_log("bitcoin-cli initialized and connected to"
971+
" bitcoind")
972+
973+
974+
def test_bcli(node_factory, bitcoind):
975+
"""
976+
This tests the bcli plugin, used to gather Bitcoin data from a local
977+
bitcoind.
978+
Mostly sanity checks of the interface..
979+
"""
980+
l1, l2 = node_factory.get_nodes(2)
981+
982+
# We cant stop it dynamically
983+
with pytest.raises(RpcError):
984+
l1.rpc.plugin_stop("bcli")
985+
986+
# Failure case of feerate is tested in test_misc.py
987+
assert "feerate" in l1.rpc.call("getfeerate", {"blocks": 3,
988+
"mode": "CONSERVATIVE"})
989+
990+
resp = l1.rpc.call("getchaininfo")
991+
assert resp["chain"] == "regtest"
992+
for field in ["headercount", "blockcount", "ibd"]:
993+
assert field in resp
994+
995+
# We shouldn't get upset if we ask for an unknown-yet block
996+
resp = l1.rpc.call("getrawblockbyheight", {"height": 500})
997+
assert resp["blockhash"] is resp["block"] is None
998+
resp = l1.rpc.call("getrawblockbyheight", {"height": 50})
999+
assert resp["blockhash"] is not None and resp["blockhash"] is not None
1000+
# Some other bitcoind-failure cases for this call are covered in
1001+
# tests/test_misc.py
1002+
1003+
l1.fundwallet(10**5)
1004+
l1.connect(l2)
1005+
txid = l1.rpc.fundchannel(l2.info["id"], 10**4)["txid"]
1006+
txo = l1.rpc.call("getutxout", {"txid": txid, "vout": 0})
1007+
assert (Millisatoshi(txo["amount"]) == Millisatoshi(10**4 * 10**3)
1008+
and txo["script"].startswith("0020"))
1009+
l1.rpc.close(l2.info["id"])
1010+
# When output is spent, it should give us null !
1011+
txo = l1.rpc.call("getutxout", {"txid": txid, "vout": 0})
1012+
assert txo["amount"] is txo["script"] is None
1013+
1014+
resp = l1.rpc.call("sendrawtransaction", {"tx": "dummy"})
1015+
assert not resp["success"] and "decode failed" in resp["errmsg"]

0 commit comments

Comments
 (0)