diff --git a/sauron/sauron.py b/sauron/sauron.py index 4165ef100..cb0d1b58c 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -46,22 +46,16 @@ def init(plugin, options, **kwargs): raise SauronError("You need to specify the sauron-api-endpoint option.") sys.exit(1) - # Test for Esplora or mempoolspace + # Testing for Esplora or mempool.space API try: - # Esplora API - feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) + # MutinyNet API + feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) feerate_req = fetch(feerate_url) - assert feerate_req.status_code == 200 and feerate_req.content != b'{}' - plugin.is_mempoolspace = False + assert feerate_req.status_code == 200 + plugin.is_mempoolspace = True except AssertionError: - try: - # MutinyNet API - feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) - feerate_req = fetch(feerate_url) - assert feerate_req.status_code == 200 - plugin.is_mempoolspace = True - except AssertionError as e1: - raise Exception("Sauron API cannot be reached") from e1 + # Esplora API + plugin.is_mempoolspace = False if options["sauron-tor-proxy"]: address, port = options["sauron-tor-proxy"].split(":") diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora_bitcoin.py similarity index 90% rename from sauron/tests/test_sauron_esplora.py rename to sauron/tests/test_sauron_esplora_bitcoin.py index 952fb7feb..88e474095 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora_bitcoin.py @@ -5,7 +5,8 @@ import pyln import pytest from pyln.testing import utils -from util import * # noqa: F403 +from pyln.testing.fixtures import * # noqa: F403 +from util import LightningD pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD @@ -36,10 +37,11 @@ def node_cls(monkeypatch): yield LightningNode -def test_rpc_getchaininfo(ln_node): +def test_rpc_getchaininfo(node_factory): """ Test getchaininfo """ + ln_node = node_factory.get_node() response = ln_node.rpc.call("getchaininfo") @@ -51,10 +53,11 @@ def test_rpc_getchaininfo(ln_node): assert not response["ibd"] -def test_rpc_getrawblockbyheight(ln_node): +def test_rpc_getrawblockbyheight(node_factory): """ Test getrawblockbyheight """ + ln_node = node_factory.get_node() response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) @@ -65,10 +68,11 @@ def test_rpc_getrawblockbyheight(ln_node): assert response == expected_response -def test_rpc_sendrawtransaction_invalid(ln_node): +def test_rpc_sendrawtransaction_invalid(node_factory): """ Test sendrawtransaction """ + ln_node = node_factory.get_node() expected_response = { "errmsg": 'sendrawtransaction RPC error: {"code":-22,"message":"TX decode failed. Make sure the tx has at least one input."}', @@ -82,10 +86,11 @@ def test_rpc_sendrawtransaction_invalid(ln_node): assert response == expected_response -def test_rpc_getutxout(ln_node): +def test_rpc_getutxout(node_factory): """ Test getutxout """ + ln_node = node_factory.get_node() expected_response = { "amount": 1000000000, @@ -102,10 +107,11 @@ def test_rpc_getutxout(ln_node): assert response == expected_response -def test_rpc_estimatefees(ln_node): +def test_rpc_estimatefees(node_factory): """ Test estimatefees """ + ln_node = node_factory.get_node() # Sample response # { diff --git a/sauron/tests/test_sauron_esplora_signet.py b/sauron/tests/test_sauron_esplora_signet.py new file mode 100644 index 000000000..8ab376178 --- /dev/null +++ b/sauron/tests/test_sauron_esplora_signet.py @@ -0,0 +1,159 @@ +#!/usr/bin/python + +import os + +import pyln +import pytest +from pyln.testing import utils +from pyln.testing.fixtures import * # noqa: F403 +from util import LightningD + +pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + pyln.testing.utils.TEST_NETWORK = "signet" + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "signet", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/signet/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(monkeypatch): + monkeypatch.setenv("TEST_NETWORK", "signet") + yield LightningNode + + +def test_rpc_getchaininfo(node_factory): + """ + Test getchaininfo + """ + ln_node = node_factory.get_node() + + response = ln_node.rpc.call("getchaininfo") + + assert ln_node.daemon.is_in_log("Sauron plugin initialized using Esplora API") + + expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] + assert list(response.keys()) == expected_response_keys + assert response["chain"] == "signet" + assert not response["ibd"] + + +def test_rpc_getrawblockbyheight(node_factory): + """ + Test getrawblockbyheight + """ + ln_node = node_factory.get_node() + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad222030101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(node_factory): + """ + Test sendrawtransaction + """ + ln_node = node_factory.get_node() + + expected_response = { + "errmsg": 'sendrawtransaction RPC error: {"code":-22,"message":"TX decode failed. Make sure the tx has at least one input."}', + "success": False, + } + response = ln_node.rpc.call( + "sendrawtransaction", + {"tx": "invalid-raw-tx"}, + ) + + assert response == expected_response + + +def test_rpc_getutxout(node_factory): + """ + Test getutxout + """ + ln_node = node_factory.get_node() + + expected_response = { + "amount": 5000000000, + "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", + } + response = ln_node.rpc.call( + "getutxout", + { + # block 217883 + "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(node_factory): + """ + Test estimatefees + """ + ln_node = node_factory.get_node() + + # Sample response + # { + # "opening": 4477, + # "mutual_close": 4477, + # "unilateral_close": 11929, + # "delayed_to_us": 4477, + # "htlc_resolution": 5652, + # "penalty": 5652, + # "min_acceptable": 1060, + # "max_acceptable": 119290, + # "feerate_floor": 1520, + # "feerates": [ + # {"blocks": 2, "feerate": 11929}, + # {"blocks": 6, "feerate": 5652}, + # {"blocks": 12, "feerate": 4477}, + # {"blocks": 144, "feerate": 2120} + # ] + # } + response = ln_node.rpc.call("estimatefees") + + expected_response_keys = [ + "opening", + "mutual_close", + "unilateral_close", + "delayed_to_us", + "htlc_resolution", + "penalty", + "min_acceptable", + "max_acceptable", + "feerate_floor", + "feerates", + ] + assert list(response.keys()) == expected_response_keys + + expected_feerates_keys = ("blocks", "feerate") + assert ( + list(set([tuple(entry.keys()) for entry in response["feerates"]]))[0] + == expected_feerates_keys + ) + + expected_feerates_blocks = [2, 6, 12, 144] + assert [ + entry["blocks"] for entry in response["feerates"] + ] == expected_feerates_blocks diff --git a/sauron/tests/test_sauron_esplora_testnet.py b/sauron/tests/test_sauron_esplora_testnet.py new file mode 100644 index 000000000..e135967de --- /dev/null +++ b/sauron/tests/test_sauron_esplora_testnet.py @@ -0,0 +1,159 @@ +#!/usr/bin/python + +import os + +import pyln +import pytest +from pyln.testing import utils +from pyln.testing.fixtures import * # noqa: F403 +from util import LightningD + +pyln.testing.fixtures.network_daemons["testnet"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + pyln.testing.utils.TEST_NETWORK = "testnet" + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "testnet", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/testnet/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(monkeypatch): + monkeypatch.setenv("TEST_NETWORK", "testnet4") + yield LightningNode + + +def test_rpc_getchaininfo(node_factory): + """ + Test getchaininfo + """ + ln_node = node_factory.get_node() + + response = ln_node.rpc.call("getchaininfo") + + assert ln_node.daemon.is_in_log("Sauron plugin initialized using Esplora API") + + expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] + assert list(response.keys()) == expected_response_keys + assert response["chain"] == "test" + assert not response["ibd"] + + +def test_rpc_getrawblockbyheight(node_factory): + """ + Test getrawblockbyheight + """ + ln_node = node_factory.get_node() + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff001d1aa4ae180101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(node_factory): + """ + Test sendrawtransaction + """ + ln_node = node_factory.get_node() + + expected_response = { + "errmsg": 'sendrawtransaction RPC error: {"code":-22,"message":"TX decode failed. Make sure the tx has at least one input."}', + "success": False, + } + response = ln_node.rpc.call( + "sendrawtransaction", + {"tx": "invalid-raw-tx"}, + ) + + assert response == expected_response + + +def test_rpc_getutxout(node_factory): + """ + Test getutxout + """ + ln_node = node_factory.get_node() + + expected_response = { + "amount": 5000000000, + "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", + } + response = ln_node.rpc.call( + "getutxout", + { + # block 0 + "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(node_factory): + """ + Test estimatefees + """ + ln_node = node_factory.get_node() + + # Sample response + # { + # "opening": 4477, + # "mutual_close": 4477, + # "unilateral_close": 11929, + # "delayed_to_us": 4477, + # "htlc_resolution": 5652, + # "penalty": 5652, + # "min_acceptable": 1060, + # "max_acceptable": 119290, + # "feerate_floor": 1520, + # "feerates": [ + # {"blocks": 2, "feerate": 11929}, + # {"blocks": 6, "feerate": 5652}, + # {"blocks": 12, "feerate": 4477}, + # {"blocks": 144, "feerate": 2120} + # ] + # } + response = ln_node.rpc.call("estimatefees") + + expected_response_keys = [ + "opening", + "mutual_close", + "unilateral_close", + "delayed_to_us", + "htlc_resolution", + "penalty", + "min_acceptable", + "max_acceptable", + "feerate_floor", + "feerates", + ] + assert list(response.keys()) == expected_response_keys + + expected_feerates_keys = ("blocks", "feerate") + assert ( + list(set([tuple(entry.keys()) for entry in response["feerates"]]))[0] + == expected_feerates_keys + ) + + expected_feerates_blocks = [2, 6, 12, 144] + assert [ + entry["blocks"] for entry in response["feerates"] + ] == expected_feerates_blocks diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 1ffd41d2d..26321cf1e 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -5,7 +5,8 @@ import pyln import pytest from pyln.testing import utils -from util import * # noqa: F403 +from pyln.testing.fixtures import * # noqa: F403 +from util import LightningD pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD @@ -36,11 +37,13 @@ def node_cls(monkeypatch): monkeypatch.setenv("TEST_NETWORK", "bitcoin") yield LightningNode -@pytest.mark.skip(reason="TODO: Mock tor") -def test_tor_proxy(ln_node): + +@pytest.mark.skip(reason="TODO: Add mock for tor proxy") +def test_tor_proxy(node_factory): """ Test for tor proxy """ + ln_node = node_factory.get_node() assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" assert ln_node.daemon.is_in_log("Using proxy socks5h://localhost:9050 for requests") diff --git a/sauron/tests/test_sauron_mempoolspace.py b/sauron/tests/test_sauron_mempoolspace_signet.py similarity index 90% rename from sauron/tests/test_sauron_mempoolspace.py rename to sauron/tests/test_sauron_mempoolspace_signet.py index c3e93b287..5b0bcbe04 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace_signet.py @@ -5,7 +5,8 @@ import pyln import pytest from pyln.testing import utils -from util import * # noqa: F403 +from pyln.testing.fixtures import * # noqa: F403 +from util import LightningD pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD @@ -36,10 +37,11 @@ def node_cls(monkeypatch): yield LightningNode -def test_rpc_getchaininfo(ln_node): +def test_rpc_getchaininfo(node_factory): """ Test getchaininfo """ + ln_node = node_factory.get_node() response = ln_node.rpc.call("getchaininfo") @@ -51,10 +53,11 @@ def test_rpc_getchaininfo(ln_node): assert not response["ibd"] -def test_rpc_getrawblockbyheight(ln_node): +def test_rpc_getrawblockbyheight(node_factory): """ Test getrawblockbyheight """ + ln_node = node_factory.get_node() response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) @@ -65,10 +68,11 @@ def test_rpc_getrawblockbyheight(ln_node): assert response == expected_response -def test_rpc_sendrawtransaction_invalid(ln_node): +def test_rpc_sendrawtransaction_invalid(node_factory): """ Test sendrawtransaction """ + ln_node = node_factory.get_node() expected_response = { "errmsg": 'sendrawtransaction RPC error: {"code":-22,"message":"TX decode failed. Make sure the tx has at least one input."}', @@ -82,10 +86,11 @@ def test_rpc_sendrawtransaction_invalid(ln_node): assert response == expected_response -def test_rpc_getutxout(ln_node): +def test_rpc_getutxout(node_factory): """ Test getutxout """ + ln_node = node_factory.get_node() expected_response = { "amount": 5000000000, @@ -102,10 +107,11 @@ def test_rpc_getutxout(ln_node): assert response == expected_response -def test_rpc_estimatefees(ln_node): +def test_rpc_estimatefees(node_factory): """ Test estimatefees """ + ln_node = node_factory.get_node() # Sample response # { diff --git a/sauron/tests/util.py b/sauron/tests/util.py index c0e01d2d1..f060aa844 100644 --- a/sauron/tests/util.py +++ b/sauron/tests/util.py @@ -1,8 +1,6 @@ import logging -import pytest from pyln.testing import utils -from pyln.testing.fixtures import * # noqa: F403 class LightningD(utils.LightningD): @@ -27,8 +25,3 @@ def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): if wait_for_initialized: self.wait_for_log("Server started with public key") logging.info("LightningD started") - - -@pytest.fixture -def ln_node(node_factory): # noqa: F811 - yield node_factory.get_node()