diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 47c905e65..cb35f951f 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -80,6 +80,9 @@ void bitcoind_check_commands(struct bitcoind *bitcoind) for (i = 0; i < ARRAY_SIZE(methods); i++) { p = find_plugin_for_command(bitcoind->ld, methods[i]); if (p == NULL) { + /* For testing .. */ + log_debug(bitcoind->ld->log, "Missing a Bitcoin plugin" + " command"); fatal("Could not access the plugin for %s, is a " "Bitcoin plugin (by default plugins/bcli) " "registered ?", methods[i]); diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 1d5cd6a68..9c86e9efd 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -1001,6 +1001,9 @@ void setup_topology(struct chain_topology *topo, /* This waits for bitcoind. */ bitcoind_check_commands(topo->bitcoind); + /* For testing.. */ + log_debug(topo->ld->log, "All Bitcoin plugin commands registered"); + /* Sanity checks, then topology initialization. */ bitcoind_getchaininfo(topo->bitcoind, true, check_chain, topo); diff --git a/tests/plugins/bitcoin/part1.py b/tests/plugins/bitcoin/part1.py new file mode 100755 index 000000000..930b786ca --- /dev/null +++ b/tests/plugins/bitcoin/part1.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +""" +This registers part of the Bitcoin backend methods. +We only use it for testing startup and we don't care about the actual values. +""" +import time + +from pyln.client import Plugin + + +plugin = Plugin() + + +@plugin.method("getfeerate") +def getfeerate(plugin, **kwargs): + time.sleep(1) + return {} + + +@plugin.method("getrawblockbyheight") +def getblock(plugin, **kwargs): + time.sleep(1) + return {} + + +@plugin.method("getchaininfo") +def getchaininfo(plugin, **kwargs): + time.sleep(1) + return {} + + +# We don't use these options, but it allows us to get to the expected failure. +plugin.add_option("bitcoin-rpcuser", "", "") +plugin.add_option("bitcoin-rpcpassword", "", "") +plugin.add_option("bitcoin-rpcport", "", "") + +plugin.run() diff --git a/tests/plugins/bitcoin/part2.py b/tests/plugins/bitcoin/part2.py new file mode 100755 index 000000000..33c64e6ff --- /dev/null +++ b/tests/plugins/bitcoin/part2.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +""" +This registers part of the Bitcoin backend methods. +We only use it for testing startup and we don't care about the actual values. +""" +import time + +from pyln.client import Plugin + + +plugin = Plugin() + + +@plugin.method("sendrawtransaction") +def sendtx(plugin, **kwargs): + time.sleep(1) + return {} + + +@plugin.method("getutxout") +def gettxout(plugin, **kwargs): + time.sleep(1) + return {} + + +plugin.run() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 72fc78d60..fb06f70be 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -931,3 +931,84 @@ def test_hook_chaining(node_factory): assert(l2.daemon.is_in_log( r'plugin-hook-chain-even.py: htlc_accepted called for payment_hash {}'.format(hash3) )) + + +def test_bitcoin_backend(node_factory, bitcoind): + """ + This tests interaction with the Bitcoin backend, but not specifically bcli + """ + l1 = node_factory.get_node(start=False, options={"disable-plugin": "bcli"}, + may_fail=True, allow_broken_log=True) + + # We don't start if we haven't all the required methods registered. + plugin = os.path.join(os.getcwd(), "tests/plugins/bitcoin/part1.py") + l1.daemon.opts["plugin"] = plugin + try: + l1.daemon.start() + except ValueError: + assert l1.daemon.is_in_log("Missing a Bitcoin plugin command") + # Now we should start if all the commands are registered, even if they + # are registered by two distincts plugins. + del l1.daemon.opts["plugin"] + l1.daemon.opts["plugin-dir"] = os.path.join(os.getcwd(), + "tests/plugins/bitcoin/") + try: + l1.daemon.start() + except ValueError: + msg = "All Bitcoin plugin commands registered" + assert l1.daemon.is_in_log(msg) + else: + raise Exception("We registered all commands but couldn't start!") + else: + raise Exception("We could start without all commands registered !!") + + # But restarting with just bcli is ok + del l1.daemon.opts["plugin-dir"] + del l1.daemon.opts["disable-plugin"] + l1.start() + assert l1.daemon.is_in_log("bitcoin-cli initialized and connected to" + " bitcoind") + + +def test_bcli(node_factory, bitcoind, chainparams): + """ + This tests the bcli plugin, used to gather Bitcoin data from a local + bitcoind. + Mostly sanity checks of the interface.. + """ + l1, l2 = node_factory.get_nodes(2) + + # We cant stop it dynamically + with pytest.raises(RpcError): + l1.rpc.plugin_stop("bcli") + + # Failure case of feerate is tested in test_misc.py + assert "feerate" in l1.rpc.call("getfeerate", {"blocks": 3, + "mode": "CONSERVATIVE"}) + + resp = l1.rpc.call("getchaininfo") + assert resp["chain"] == chainparams['name'] + for field in ["headercount", "blockcount", "ibd"]: + assert field in resp + + # We shouldn't get upset if we ask for an unknown-yet block + resp = l1.rpc.call("getrawblockbyheight", {"height": 500}) + assert resp["blockhash"] is resp["block"] is None + resp = l1.rpc.call("getrawblockbyheight", {"height": 50}) + assert resp["blockhash"] is not None and resp["blockhash"] is not None + # Some other bitcoind-failure cases for this call are covered in + # tests/test_misc.py + + l1.fundwallet(10**5) + l1.connect(l2) + txid = l1.rpc.fundchannel(l2.info["id"], 10**4)["txid"] + txo = l1.rpc.call("getutxout", {"txid": txid, "vout": 0}) + assert (Millisatoshi(txo["amount"]) == Millisatoshi(10**4 * 10**3) + and txo["script"].startswith("0020")) + l1.rpc.close(l2.info["id"]) + # When output is spent, it should give us null ! + txo = l1.rpc.call("getutxout", {"txid": txid, "vout": 0}) + assert txo["amount"] is txo["script"] is None + + resp = l1.rpc.call("sendrawtransaction", {"tx": "dummy"}) + assert not resp["success"] and "decode failed" in resp["errmsg"]