From 3e64f8558f46b6c193d4eb5de9689745e6cbea23 Mon Sep 17 00:00:00 2001 From: Carlos Ruz Date: Mon, 2 Sep 2024 15:39:06 -0600 Subject: [PATCH 01/30] Add mutinynet --- sauron/sauron.py | 148 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 46 deletions(-) diff --git a/sauron/sauron.py b/sauron/sauron.py index 3e2e34c9a..54ae05dd4 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -2,22 +2,20 @@ import requests import sys import time +from pprint import pprint -from requests.packages.urllib3.util.retry import Retry +from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter from art import sauron_eye from pyln.client import Plugin - plugin = Plugin(dynamic=False) plugin.sauron_socks_proxies = None plugin.sauron_network = "test" - class SauronError(Exception): pass - def fetch(url): """Fetch this {url}, maybe through a pre-defined proxy.""" # FIXME: Maybe try to be smart and renew circuit to broadcast different @@ -38,10 +36,11 @@ def fetch(url): return session.get(url) - @plugin.init() -def init(plugin, options, configuration, **kwargs): - plugin.api_endpoint = options["sauron-api-endpoint"] +def init(plugin, options, **kwargs): + plugin.api_endpoint = options.get("sauron-api-endpoint", None) + plugin.log("plugin.api_endpoint = %s" % plugin.api_endpoint) + if not plugin.api_endpoint: raise SauronError("You need to specify the sauron-api-endpoint option.") sys.exit(1) @@ -58,7 +57,6 @@ def init(plugin, options, configuration, **kwargs): plugin.log("Sauron plugin initialized") plugin.log(sauron_eye) - @plugin.method("getchaininfo") def getchaininfo(plugin, **kwargs): blockhash_url = "{}/block-height/0".format(plugin.api_endpoint) @@ -99,7 +97,6 @@ def getchaininfo(plugin, **kwargs): "ibd": False, } - @plugin.method("getrawblockbyheight") def getrawblock(plugin, height, **kwargs): blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) @@ -110,7 +107,11 @@ def getrawblock(plugin, height, **kwargs): "block": None, } - block_url = "{}/block/{}/raw".format(plugin.api_endpoint, blockhash_req.text) + block_hash = blockhash_req.text.strip() # Ensure no extra spaces or newlines + + # Step 2: Determine the block URL and fetch the block data + block_url = "{}/block/{}/raw".format(plugin.api_endpoint, block_hash) + while True: block_req = fetch(block_url) if block_req.status_code != 200: @@ -130,16 +131,22 @@ def getrawblock(plugin, height, **kwargs): plugin.log("Esplora gave us an incomplete block, retrying in 2s", level="error") time.sleep(2) + plugin.log("block_req = %s" % pprint(vars(block_req))) + + # Step 3: Process the block data + # Blockstream and Mutinynet returns raw binary data + block_data = block_req.content.hex() + plugin.log("block_data = %s" % block_data) + return { - "blockhash": blockhash_req.text, - "block": block_req.content.hex(), + "blockhash": block_hash, + "block": block_data, } @plugin.method("sendrawtransaction") def sendrawtx(plugin, tx, **kwargs): sendtx_url = "{}/tx".format(plugin.api_endpoint) - sendtx_req = requests.post(sendtx_url, data=tx) if sendtx_req.status_code != 200: return { @@ -154,57 +161,107 @@ def sendrawtx(plugin, tx, **kwargs): @plugin.method("getutxout") -def getutxout(plugin, txid, vout, **kwargs): - gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) - status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) +def getutxout(plugin, address, txid, vout, **kwargs): + # Determine the API endpoint type based on the URL structure + if "mutinynet" in plugin.api_endpoint: + # MutinyNet API + utxo_url = "{}/address/{}/utxo".format(plugin.api_endpoint, address) + + # Fetch the list of UTXOs for the given address + utxo_req = fetch(utxo_url) + if not utxo_req.status_code == 200: + raise SauronError( + "Endpoint at {} returned {} ({}) when trying to get UTXOs.".format( + utxo_url, utxo_req.status_code, utxo_req.text + ) + ) - gettx_req = fetch(gettx_url) - if not gettx_req.status_code == 200: - raise SauronError( - "Endpoint at {} returned {} ({}) when trying to " "get transaction.".format( - gettx_url, gettx_req.status_code, gettx_req.text + # Parse the UTXO data + utxos = utxo_req.json() + # Find the UTXO with the given txid and vout + for utxo in utxos: + if utxo['txid'] == txid and utxo['vout'] == vout: + return { + "amount": utxo["value"], + "script": None # MutinyNet API does not provide script information + } + + # If the specific UTXO is not found + return { + "amount": None, + "script": None + } + + else: + # Blockstream API + gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) + status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) + + gettx_req = fetch(gettx_url) + if not gettx_req.status_code == 200: + raise SauronError( + "Endpoint at {} returned {} ({}) when trying to get transaction.".format( + gettx_url, gettx_req.status_code, gettx_req.text + ) ) - ) - status_req = fetch(status_url) - if not status_req.status_code == 200: - raise SauronError( - "Endpoint at {} returned {} ({}) when trying to " "get utxo status.".format( - status_url, status_req.status_code, status_req.text + status_req = fetch(status_url) + if not status_req.status_code == 200: + raise SauronError( + "Endpoint at {} returned {} ({}) when trying to get UTXO status.".format( + status_url, status_req.status_code, status_req.text + ) ) - ) - if status_req.json()["spent"]: + if status_req.json()["spent"]: + return { + "amount": None, + "script": None + } + + txo = gettx_req.json()["vout"][vout] return { - "amount": None, - "script": None, + "amount": txo["value"], + "script": txo["scriptpubkey"] } - txo = gettx_req.json()["vout"][vout] - return { - "amount": txo["value"], - "script": txo["scriptpubkey"], - } @plugin.method("estimatefees") def estimatefees(plugin, **kwargs): - feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) + # Define the URL based on the selected API + if "mutinynet" in plugin.api_endpoint: + # MutinyNet API + feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) + plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) + plugin.log("estimatefees: feerate_url = %s" % feerate_url) + + else: + # Blockstream API + feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) + plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) + plugin.log("estimatefees: feerate_url = %s" % feerate_url) feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() - if plugin.sauron_network == "test" or plugin.sauron_network == "signet": - # FIXME: remove the hack if the test API is "fixed" + plugin.log("estimatefees: feerates = %s" % feerates) + + # Define the multiply factor for sat/vB to sat/kVB conversion + multiply_factor = 10**3 + + if plugin.sauron_network in ["test", "signet"]: + # Apply the fallback for test/signet networks feerate = feerates.get("144", 1) - slow = normal = urgent = very_urgent = int(feerate * 10**3) + slow = normal = urgent = very_urgent = int(feerate * multiply_factor) else: + # Adjust fee rates based on the specific API # It returns sat/vB, we want sat/kVB, so multiply everything by 10**3 - slow = int(feerates["144"] * 10**3) - normal = int(feerates["12"] * 10**3) - urgent = int(feerates["6"] * 10**3) - very_urgent = int(feerates["2"] * 10**3) + slow = int(feerates["144"] * multiply_factor) + normal = int(feerates["12"] * multiply_factor) + urgent = int(feerates["6"] * multiply_factor) + very_urgent = int(feerates["2"] * multiply_factor) - feerate_floor = int(feerates["1008"] * 10**3) + feerate_floor = int(feerates.get("1008", slow) * multiply_factor) feerates = [ {"blocks": 2, "feerate": very_urgent}, {"blocks": 6, "feerate": urgent}, @@ -225,7 +282,6 @@ def estimatefees(plugin, **kwargs): "feerates": feerates } - plugin.add_option( "sauron-api-endpoint", "", From acbd990b04db79945b7f71abff490f69c121483c Mon Sep 17 00:00:00 2001 From: Jorge Martinez Ortega Date: Tue, 17 Sep 2024 13:54:40 -0600 Subject: [PATCH 02/30] test if backend is Esplora or Mempool space remove some redundant code lines --- sauron/sauron.py | 69 +++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/sauron/sauron.py b/sauron/sauron.py index 54ae05dd4..0884fc1c9 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -45,6 +45,24 @@ def init(plugin, options, **kwargs): raise SauronError("You need to specify the sauron-api-endpoint option.") sys.exit(1) + # Test for Esplora or mempoolspace + 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 e0: + try: + # Blockstream API + feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) + feerate_req = fetch(feerate_url) + assert feerate_req.status_code == 200 + plugin.is_mempoolspace = False + except AssertionError as e1: + raise Exception("Sauron API cannot be reached") from e1 + + if options["sauron-tor-proxy"]: address, port = options["sauron-tor-proxy"].split(":") socks5_proxy = "socks5h://{}:{}".format(address, port) @@ -163,24 +181,29 @@ def sendrawtx(plugin, tx, **kwargs): @plugin.method("getutxout") def getutxout(plugin, address, txid, vout, **kwargs): # Determine the API endpoint type based on the URL structure - if "mutinynet" in plugin.api_endpoint: + if plugin.is_mempoolspace: # MutinyNet API - utxo_url = "{}/address/{}/utxo".format(plugin.api_endpoint, address) + gettx_url = "{}/address/{}/utxo".format(plugin.api_endpoint, address) + else: + # Blockstream API + gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) + status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) - # Fetch the list of UTXOs for the given address - utxo_req = fetch(utxo_url) - if not utxo_req.status_code == 200: - raise SauronError( - "Endpoint at {} returned {} ({}) when trying to get UTXOs.".format( - utxo_url, utxo_req.status_code, utxo_req.text - ) + # Fetch the list of UTXOs for the given address + gettx_req = fetch(gettx_url) + if not gettx_req.status_code == 200: + raise SauronError( + "Endpoint at {} returned {} ({}) when trying to get transaction.".format( + gettx_url, gettx_req.status_code, gettx_req.text ) - + ) + if plugin.is_mempoolspace: + # Building response from MutinyNet API # Parse the UTXO data - utxos = utxo_req.json() + utxos = gettx_req.json() # Find the UTXO with the given txid and vout for utxo in utxos: - if utxo['txid'] == txid and utxo['vout'] == vout: + if utxo['txid'] == txid: return { "amount": utxo["value"], "script": None # MutinyNet API does not provide script information @@ -191,19 +214,8 @@ def getutxout(plugin, address, txid, vout, **kwargs): "amount": None, "script": None } - else: - # Blockstream API - gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) - status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) - - gettx_req = fetch(gettx_url) - if not gettx_req.status_code == 200: - raise SauronError( - "Endpoint at {} returned {} ({}) when trying to get transaction.".format( - gettx_url, gettx_req.status_code, gettx_req.text - ) - ) + # Building response from Blockstream API status_req = fetch(status_url) if not status_req.status_code == 200: raise SauronError( @@ -229,18 +241,15 @@ def getutxout(plugin, address, txid, vout, **kwargs): @plugin.method("estimatefees") def estimatefees(plugin, **kwargs): # Define the URL based on the selected API - if "mutinynet" in plugin.api_endpoint: + if plugin.is_mempoolspace: # MutinyNet API feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) - plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) - plugin.log("estimatefees: feerate_url = %s" % feerate_url) - else: # Blockstream API feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) - plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) - plugin.log("estimatefees: feerate_url = %s" % feerate_url) + plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) + plugin.log("estimatefees: feerate_url = %s" % feerate_url) feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() From 652fbd6baf620aa883fe2258ac1d9ca056bb2dc9 Mon Sep 17 00:00:00 2001 From: Carlos Ruz Date: Tue, 24 Sep 2024 15:01:29 -0600 Subject: [PATCH 03/30] Update requirements.txt and README.md for use with mutinynet --- sauron/README.md | 8 ++++++++ sauron/requirements.txt | 2 +- sauron/sauron.py | 7 ++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/sauron/README.md b/sauron/README.md index d02dbe10e..ce023c808 100644 --- a/sauron/README.md +++ b/sauron/README.md @@ -24,6 +24,14 @@ Here is a fully reptilian example running against [blockstream.info](https://blo lightningd --mainnet --disable-plugin bcli --plugin $PWD/sauron.py --sauron-api-endpoint https://blockstream.info/api/ ``` + +Here is an example running against [mutinynet.com](https://mutinynet.com/): + +``` +lightningd --signet --disable-plugin bcli --plugin $PWD/sauron.py --sauron-api-endpoint https://mutinynet.com/api/ +``` + + You can use also proxy your requests through [Tor](https://www.torproject.org/) by specifying a SOCKS proxy to use with the `--sauron-tor-proxy` startup option, in the form `address:port`. diff --git a/sauron/requirements.txt b/sauron/requirements.txt index 1794cb833..f8b0c253f 100644 --- a/sauron/requirements.txt +++ b/sauron/requirements.txt @@ -1,2 +1,2 @@ -pyln-client>=23.2 +pyln-client==24.5 requests[socks]>=2.23.0 diff --git a/sauron/sauron.py b/sauron/sauron.py index 0884fc1c9..f58208e7a 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -17,7 +17,7 @@ class SauronError(Exception): pass def fetch(url): - """Fetch this {url}, maybe through a pre-defined proxy.""" + """Fetch the given {url}, maybe through a pre-defined proxy.""" # FIXME: Maybe try to be smart and renew circuit to broadcast different # transactions ? Hint: lightningd will agressively send us the same # transaction a certain amount of times. @@ -79,6 +79,7 @@ def init(plugin, options, **kwargs): def getchaininfo(plugin, **kwargs): blockhash_url = "{}/block-height/0".format(plugin.api_endpoint) blockcount_url = "{}/blocks/tip/height".format(plugin.api_endpoint) + chains = { "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f": "main", "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943": "test", @@ -117,7 +118,9 @@ def getchaininfo(plugin, **kwargs): @plugin.method("getrawblockbyheight") def getrawblock(plugin, height, **kwargs): + # Step 1: Get the block hash by height blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) + blockhash_req = fetch(blockhash_url) if blockhash_req.status_code != 200: return { @@ -278,6 +281,7 @@ def estimatefees(plugin, **kwargs): {"blocks": 144, "feerate": slow} ] + # Return the estimated fees return { "opening": normal, "mutual_close": normal, @@ -295,6 +299,7 @@ def estimatefees(plugin, **kwargs): "sauron-api-endpoint", "", "The URL of the esplora instance to hit (including '/api').", + "The URL of the mutinynet instance to hit (including '/api').", ) plugin.add_option( From 8cee2e490777754c80878016db9758f9c10e10ff Mon Sep 17 00:00:00 2001 From: Carlos Ruz Date: Fri, 27 Sep 2024 17:25:43 -0600 Subject: [PATCH 04/30] Addressed PR comments --- sauron/.gitignore | 1 + sauron/sauron.py | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) create mode 100644 sauron/.gitignore diff --git a/sauron/.gitignore b/sauron/.gitignore new file mode 100644 index 000000000..21d0b898f --- /dev/null +++ b/sauron/.gitignore @@ -0,0 +1 @@ +.venv/ diff --git a/sauron/sauron.py b/sauron/sauron.py index f58208e7a..65fb99124 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -47,22 +47,21 @@ def init(plugin, options, **kwargs): # Test for Esplora or mempoolspace try: - # MutinyNet API - feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) + # Esplora API + feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 - plugin.is_mempoolspace = True + plugin.is_mempoolspace = False except AssertionError as e0: try: - # Blockstream 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 - plugin.is_mempoolspace = False + plugin.is_mempoolspace = True except AssertionError as e1: raise Exception("Sauron API cannot be reached") from e1 - if options["sauron-tor-proxy"]: address, port = options["sauron-tor-proxy"].split(":") socks5_proxy = "socks5h://{}:{}".format(address, port) @@ -298,8 +297,7 @@ def estimatefees(plugin, **kwargs): plugin.add_option( "sauron-api-endpoint", "", - "The URL of the esplora instance to hit (including '/api').", - "The URL of the mutinynet instance to hit (including '/api').", + "The URL of the esplora or mempool.space instance to hit (including '/api').", ) plugin.add_option( From 7402d7d07565f2868dc0cdbf9cf8138fbb68e524 Mon Sep 17 00:00:00 2001 From: Carlos Ruz Date: Mon, 30 Sep 2024 17:21:00 -0600 Subject: [PATCH 05/30] Update pyln-client requirements, fix bug and clean code. --- sauron/requirements.txt | 2 +- sauron/sauron.py | 124 +++++++++++++--------------------------- 2 files changed, 40 insertions(+), 86 deletions(-) diff --git a/sauron/requirements.txt b/sauron/requirements.txt index f8b0c253f..7fd22f35e 100644 --- a/sauron/requirements.txt +++ b/sauron/requirements.txt @@ -1,2 +1,2 @@ -pyln-client==24.5 +pyln-client>=23.2,<=24.5 requests[socks]>=2.23.0 diff --git a/sauron/sauron.py b/sauron/sauron.py index 65fb99124..0ac6b9375 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -2,22 +2,24 @@ import requests import sys import time -from pprint import pprint -from urllib3.util.retry import Retry +from requests.packages.urllib3.util.retry import Retry from requests.adapters import HTTPAdapter from art import sauron_eye from pyln.client import Plugin + plugin = Plugin(dynamic=False) plugin.sauron_socks_proxies = None plugin.sauron_network = "test" + class SauronError(Exception): pass + def fetch(url): - """Fetch the given {url}, maybe through a pre-defined proxy.""" + """Fetch this {url}, maybe through a pre-defined proxy.""" # FIXME: Maybe try to be smart and renew circuit to broadcast different # transactions ? Hint: lightningd will agressively send us the same # transaction a certain amount of times. @@ -36,11 +38,10 @@ def fetch(url): return session.get(url) + @plugin.init() def init(plugin, options, **kwargs): plugin.api_endpoint = options.get("sauron-api-endpoint", None) - plugin.log("plugin.api_endpoint = %s" % plugin.api_endpoint) - if not plugin.api_endpoint: raise SauronError("You need to specify the sauron-api-endpoint option.") sys.exit(1) @@ -74,11 +75,11 @@ def init(plugin, options, **kwargs): plugin.log("Sauron plugin initialized") plugin.log(sauron_eye) + @plugin.method("getchaininfo") def getchaininfo(plugin, **kwargs): blockhash_url = "{}/block-height/0".format(plugin.api_endpoint) blockcount_url = "{}/blocks/tip/height".format(plugin.api_endpoint) - chains = { "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f": "main", "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943": "test", @@ -115,11 +116,10 @@ def getchaininfo(plugin, **kwargs): "ibd": False, } + @plugin.method("getrawblockbyheight") def getrawblock(plugin, height, **kwargs): - # Step 1: Get the block hash by height blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) - blockhash_req = fetch(blockhash_url) if blockhash_req.status_code != 200: return { @@ -127,11 +127,7 @@ def getrawblock(plugin, height, **kwargs): "block": None, } - block_hash = blockhash_req.text.strip() # Ensure no extra spaces or newlines - - # Step 2: Determine the block URL and fetch the block data - block_url = "{}/block/{}/raw".format(plugin.api_endpoint, block_hash) - + block_url = "{}/block/{}/raw".format(plugin.api_endpoint, blockhash_req.text) while True: block_req = fetch(block_url) if block_req.status_code != 200: @@ -151,22 +147,16 @@ def getrawblock(plugin, height, **kwargs): plugin.log("Esplora gave us an incomplete block, retrying in 2s", level="error") time.sleep(2) - plugin.log("block_req = %s" % pprint(vars(block_req))) - - # Step 3: Process the block data - # Blockstream and Mutinynet returns raw binary data - block_data = block_req.content.hex() - plugin.log("block_data = %s" % block_data) - return { - "blockhash": block_hash, - "block": block_data, + "blockhash": blockhash_req.text, + "block": block_req.content.hex(), } @plugin.method("sendrawtransaction") def sendrawtx(plugin, tx, **kwargs): sendtx_url = "{}/tx".format(plugin.api_endpoint) + sendtx_req = requests.post(sendtx_url, data=tx) if sendtx_req.status_code != 200: return { @@ -181,68 +171,40 @@ def sendrawtx(plugin, tx, **kwargs): @plugin.method("getutxout") -def getutxout(plugin, address, txid, vout, **kwargs): - # Determine the API endpoint type based on the URL structure - if plugin.is_mempoolspace: - # MutinyNet API - gettx_url = "{}/address/{}/utxo".format(plugin.api_endpoint, address) - else: - # Blockstream API - gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) - status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) +def getutxout(plugin, txid, vout, **kwargs): + gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) + status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) - # Fetch the list of UTXOs for the given address gettx_req = fetch(gettx_url) if not gettx_req.status_code == 200: raise SauronError( - "Endpoint at {} returned {} ({}) when trying to get transaction.".format( + "Endpoint at {} returned {} ({}) when trying to " "get transaction.".format( gettx_url, gettx_req.status_code, gettx_req.text ) ) - if plugin.is_mempoolspace: - # Building response from MutinyNet API - # Parse the UTXO data - utxos = gettx_req.json() - # Find the UTXO with the given txid and vout - for utxo in utxos: - if utxo['txid'] == txid: - return { - "amount": utxo["value"], - "script": None # MutinyNet API does not provide script information - } - - # If the specific UTXO is not found - return { - "amount": None, - "script": None - } - else: - # Building response from Blockstream API - status_req = fetch(status_url) - if not status_req.status_code == 200: - raise SauronError( - "Endpoint at {} returned {} ({}) when trying to get UTXO status.".format( - status_url, status_req.status_code, status_req.text - ) + status_req = fetch(status_url) + if not status_req.status_code == 200: + raise SauronError( + "Endpoint at {} returned {} ({}) when trying to " "get utxo status.".format( + status_url, status_req.status_code, status_req.text ) + ) - if status_req.json()["spent"]: - return { - "amount": None, - "script": None - } - - txo = gettx_req.json()["vout"][vout] + if status_req.json()["spent"]: return { - "amount": txo["value"], - "script": txo["scriptpubkey"] + "amount": None, + "script": None, } + txo = gettx_req.json()["vout"][vout] + return { + "amount": txo["value"], + "script": txo["scriptpubkey"], + } @plugin.method("estimatefees") def estimatefees(plugin, **kwargs): - # Define the URL based on the selected API if plugin.is_mempoolspace: # MutinyNet API feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) @@ -250,29 +212,21 @@ def estimatefees(plugin, **kwargs): # Blockstream API feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) - plugin.log("estimatefees: plugin.api_endpoint = %s" % plugin.api_endpoint) - plugin.log("estimatefees: feerate_url = %s" % feerate_url) feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() - plugin.log("estimatefees: feerates = %s" % feerates) - - # Define the multiply factor for sat/vB to sat/kVB conversion - multiply_factor = 10**3 - - if plugin.sauron_network in ["test", "signet"]: - # Apply the fallback for test/signet networks + if plugin.sauron_network == "test" or plugin.sauron_network == "signet": + # FIXME: remove the hack if the test API is "fixed" feerate = feerates.get("144", 1) - slow = normal = urgent = very_urgent = int(feerate * multiply_factor) + slow = normal = urgent = very_urgent = int(feerate * 10**3) else: - # Adjust fee rates based on the specific API # It returns sat/vB, we want sat/kVB, so multiply everything by 10**3 - slow = int(feerates["144"] * multiply_factor) - normal = int(feerates["12"] * multiply_factor) - urgent = int(feerates["6"] * multiply_factor) - very_urgent = int(feerates["2"] * multiply_factor) + slow = int(feerates["144"] * 10**3) + normal = int(feerates["12"] * 10**3) + urgent = int(feerates["6"] * 10**3) + very_urgent = int(feerates["2"] * 10**3) - feerate_floor = int(feerates.get("1008", slow) * multiply_factor) + feerate_floor = int(feerates.get("1008", slow) * 10**3) feerates = [ {"blocks": 2, "feerate": very_urgent}, {"blocks": 6, "feerate": urgent}, @@ -280,7 +234,6 @@ def estimatefees(plugin, **kwargs): {"blocks": 144, "feerate": slow} ] - # Return the estimated fees return { "opening": normal, "mutual_close": normal, @@ -294,6 +247,7 @@ def estimatefees(plugin, **kwargs): "feerates": feerates } + plugin.add_option( "sauron-api-endpoint", "", From 98f5faebd109b748be3b91a1f47425329828ed36 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sat, 28 Sep 2024 02:13:27 -0600 Subject: [PATCH 06/30] Add sauron tests --- .ci/test.py | 3 +- poncho | 2 +- sauron/sauron.py | 11 +- sauron/tests/test_sauron_esplora.py | 152 +++++++++++++++++ sauron/tests/test_sauron_esplora_tor_proxy.py | 45 ++++++ sauron/tests/test_sauron_mempoolspace.py | 153 ++++++++++++++++++ sauron/tests/util.py | 34 ++++ 7 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 sauron/tests/test_sauron_esplora.py create mode 100644 sauron/tests/test_sauron_esplora_tor_proxy.py create mode 100644 sauron/tests/test_sauron_mempoolspace.py create mode 100644 sauron/tests/util.py diff --git a/.ci/test.py b/.ci/test.py index a4f5ee742..7239d4594 100644 --- a/.ci/test.py +++ b/.ci/test.py @@ -241,13 +241,14 @@ def run_one(p: Plugin, workflow: str) -> bool: logging.info(f"Virtualenv at {vpath}") + num_workers = 1 if p.name == "sauron" else 5 cmd = [ str(pytest_path), "-vvv", "--timeout=600", "--timeout-method=thread", "--color=yes", - "-n=5", + f"-n={num_workers}", ] logging.info(f"Running `{' '.join(cmd)}` in directory {p.path.resolve()}") diff --git a/poncho b/poncho index e7795d763..a94da96ff 160000 --- a/poncho +++ b/poncho @@ -1 +1 @@ -Subproject commit e7795d763168d81435e7430105a7ef4c6985c45a +Subproject commit a94da96ff257cd10edda74ac897fc15a4344a08e diff --git a/sauron/sauron.py b/sauron/sauron.py index 0ac6b9375..4165ef100 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -51,9 +51,9 @@ def init(plugin, options, **kwargs): # Esplora API feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) feerate_req = fetch(feerate_url) - assert feerate_req.status_code == 200 + assert feerate_req.status_code == 200 and feerate_req.content != b'{}' plugin.is_mempoolspace = False - except AssertionError as e0: + except AssertionError: try: # MutinyNet API feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) @@ -72,7 +72,8 @@ def init(plugin, options, **kwargs): } plugin.log("Using proxy {} for requests".format(socks5_proxy)) - plugin.log("Sauron plugin initialized") + api = "mempool.space" if plugin.is_mempoolspace else "Esplora" + plugin.log(f"Sauron plugin initialized using {api} API") plugin.log(sauron_eye) @@ -215,7 +216,7 @@ def estimatefees(plugin, **kwargs): feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() - if plugin.sauron_network == "test" or plugin.sauron_network == "signet": + if plugin.sauron_network in ["test", "signet"]: # FIXME: remove the hack if the test API is "fixed" feerate = feerates.get("144", 1) slow = normal = urgent = very_urgent = int(feerate * 10**3) @@ -259,7 +260,7 @@ def estimatefees(plugin, **kwargs): "", "Tor's SocksPort address in the form address:port, don't specify the" " protocol. If you didn't modify your torrc you want to put" - "'localhost:9050' here.", + " 'localhost:9050' here.", ) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py new file mode 100644 index 000000000..233f00965 --- /dev/null +++ b/sauron/tests/test_sauron_esplora.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "bitcoin" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "bitcoin", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_rpc_getchaininfo(ln_node): + """ + Test getchaininfo + """ + + 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"] == "main" + assert not response["ibd"] + + +def test_rpc_getrawblockbyheight(ln_node): + """ + Test getrawblockbyheight + """ + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(ln_node): + """ + Test sendrawtransaction + """ + + 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(ln_node): + """ + Test getutxout + """ + + expected_response = { + "amount": 1000000000, + "script": "4104b5abd412d4341b45056d3e376cd446eca43fa871b51961330deebd84423e740daa520690e1d9e074654c59ff87b408db903649623e86f1ca5412786f61ade2bfac", + } + response = ln_node.rpc.call( + "getutxout", + { + # block 181 + "txid": "a16f3ce4dd5deb92d98ef5cf8afeaf0775ebca408f708b2146c4fb42b41e14be", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(ln_node): + """ + Test estimatefees + """ + + # 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 new file mode 100644 index 000000000..434335a9c --- /dev/null +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "bitcoin" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "bitcoin", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/api", + "sauron-tor-proxy": "localhost:9050", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_tor_proxy(ln_node): + """ + Test for tor proxy + """ + + 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.py new file mode 100644 index 000000000..c07b970b4 --- /dev/null +++ b/sauron/tests/test_sauron_mempoolspace.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "signet" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + 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://mutinynet.com/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_rpc_getchaininfo(ln_node): + """ + Test getchaininfo + """ + + response = ln_node.rpc.call("getchaininfo") + + assert ln_node.daemon.is_in_log("Sauron plugin initialized using mempool.space 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(ln_node): + """ + Test getrawblockbyheight + """ + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad222030101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(ln_node): + """ + Test sendrawtransaction + """ + + 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(ln_node): + """ + Test getutxout + """ + + expected_response = { + "amount": 5000000000, + "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", + } + response = ln_node.rpc.call( + "getutxout", + { + # coinbase tx block 0 + "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(ln_node): + """ + Test estimatefees + """ + + # Sample response + # { + # "opening": 1000, + # "mutual_close": 1000, + # "unilateral_close": 1000, + # "delayed_to_us": 1000, + # "htlc_resolution": 1000, + # "penalty": 1000, + # "min_acceptable": 500, + # "max_acceptable": 10000, + # "feerate_floor": 1000000, + # "feerates": [ + # {"blocks": 2, "feerate": 1000}, + # {"blocks": 6, "feerate": 1000}, + # {"blocks": 12, "feerate": 1000}, + # {"blocks": 144, "feerate": 1000} + # ] + # } + + 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/util.py b/sauron/tests/util.py new file mode 100644 index 000000000..c0e01d2d1 --- /dev/null +++ b/sauron/tests/util.py @@ -0,0 +1,34 @@ +import logging + +import pytest +from pyln.testing import utils +from pyln.testing.fixtures import * # noqa: F403 + + +class LightningD(utils.LightningD): + def __init__(self, lightning_dir, *args, **kwargs): + super().__init__(lightning_dir, *args, **kwargs) + + opts_to_disable = [ + "bitcoin-datadir", + "bitcoin-rpcpassword", + "bitcoin-rpcuser", + "dev-bitcoind-poll", + ] + for opt in opts_to_disable: + self.opts.pop(opt) + + # Monkey patch + def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): + utils.TailableProc.start( + self, stdin, stdout_redir=False, stderr_redir=stderr_redir + ) + + 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() From 18f6e23c3a34ef0feac709fd251114e3a0ef5300 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sat, 28 Sep 2024 02:13:27 -0600 Subject: [PATCH 07/30] Add sauron tests --- .ci/test.py | 3 +- poncho | 2 +- sauron/sauron.py | 11 +- sauron/tests/test_sauron_esplora.py | 152 +++++++++++++++++ sauron/tests/test_sauron_esplora_tor_proxy.py | 45 ++++++ sauron/tests/test_sauron_mempoolspace.py | 153 ++++++++++++++++++ sauron/tests/util.py | 34 ++++ 7 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 sauron/tests/test_sauron_esplora.py create mode 100644 sauron/tests/test_sauron_esplora_tor_proxy.py create mode 100644 sauron/tests/test_sauron_mempoolspace.py create mode 100644 sauron/tests/util.py diff --git a/.ci/test.py b/.ci/test.py index a4f5ee742..7239d4594 100644 --- a/.ci/test.py +++ b/.ci/test.py @@ -241,13 +241,14 @@ def run_one(p: Plugin, workflow: str) -> bool: logging.info(f"Virtualenv at {vpath}") + num_workers = 1 if p.name == "sauron" else 5 cmd = [ str(pytest_path), "-vvv", "--timeout=600", "--timeout-method=thread", "--color=yes", - "-n=5", + f"-n={num_workers}", ] logging.info(f"Running `{' '.join(cmd)}` in directory {p.path.resolve()}") diff --git a/poncho b/poncho index e7795d763..a94da96ff 160000 --- a/poncho +++ b/poncho @@ -1 +1 @@ -Subproject commit e7795d763168d81435e7430105a7ef4c6985c45a +Subproject commit a94da96ff257cd10edda74ac897fc15a4344a08e diff --git a/sauron/sauron.py b/sauron/sauron.py index 0ac6b9375..4165ef100 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -51,9 +51,9 @@ def init(plugin, options, **kwargs): # Esplora API feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) feerate_req = fetch(feerate_url) - assert feerate_req.status_code == 200 + assert feerate_req.status_code == 200 and feerate_req.content != b'{}' plugin.is_mempoolspace = False - except AssertionError as e0: + except AssertionError: try: # MutinyNet API feerate_url = "{}/v1/fees/recommended".format(plugin.api_endpoint) @@ -72,7 +72,8 @@ def init(plugin, options, **kwargs): } plugin.log("Using proxy {} for requests".format(socks5_proxy)) - plugin.log("Sauron plugin initialized") + api = "mempool.space" if plugin.is_mempoolspace else "Esplora" + plugin.log(f"Sauron plugin initialized using {api} API") plugin.log(sauron_eye) @@ -215,7 +216,7 @@ def estimatefees(plugin, **kwargs): feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() - if plugin.sauron_network == "test" or plugin.sauron_network == "signet": + if plugin.sauron_network in ["test", "signet"]: # FIXME: remove the hack if the test API is "fixed" feerate = feerates.get("144", 1) slow = normal = urgent = very_urgent = int(feerate * 10**3) @@ -259,7 +260,7 @@ def estimatefees(plugin, **kwargs): "", "Tor's SocksPort address in the form address:port, don't specify the" " protocol. If you didn't modify your torrc you want to put" - "'localhost:9050' here.", + " 'localhost:9050' here.", ) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py new file mode 100644 index 000000000..233f00965 --- /dev/null +++ b/sauron/tests/test_sauron_esplora.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "bitcoin" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "bitcoin", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_rpc_getchaininfo(ln_node): + """ + Test getchaininfo + """ + + 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"] == "main" + assert not response["ibd"] + + +def test_rpc_getrawblockbyheight(ln_node): + """ + Test getrawblockbyheight + """ + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(ln_node): + """ + Test sendrawtransaction + """ + + 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(ln_node): + """ + Test getutxout + """ + + expected_response = { + "amount": 1000000000, + "script": "4104b5abd412d4341b45056d3e376cd446eca43fa871b51961330deebd84423e740daa520690e1d9e074654c59ff87b408db903649623e86f1ca5412786f61ade2bfac", + } + response = ln_node.rpc.call( + "getutxout", + { + # block 181 + "txid": "a16f3ce4dd5deb92d98ef5cf8afeaf0775ebca408f708b2146c4fb42b41e14be", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(ln_node): + """ + Test estimatefees + """ + + # 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 new file mode 100644 index 000000000..434335a9c --- /dev/null +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -0,0 +1,45 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "bitcoin" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["bitcoin"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + utils.LightningNode.__init__(self, *args, **kwargs) + lightning_dir = args[1] + + self.daemon = LightningD(lightning_dir, None) # noqa: F405 + options = { + "disable-plugin": "bcli", + "network": "bitcoin", + "plugin": os.path.join(os.path.dirname(__file__), "../sauron.py"), + "sauron-api-endpoint": "https://blockstream.info/api", + "sauron-tor-proxy": "localhost:9050", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_tor_proxy(ln_node): + """ + Test for tor proxy + """ + + 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.py new file mode 100644 index 000000000..c07b970b4 --- /dev/null +++ b/sauron/tests/test_sauron_mempoolspace.py @@ -0,0 +1,153 @@ +#!/usr/bin/python + +import os + +os.environ["TEST_NETWORK"] = "signet" +import pyln +import pytest +from pyln.testing import utils +from util import * # noqa: F403 + +pyln.testing.fixtures.network_daemons["signet"] = utils.BitcoinD + + +class LightningNode(utils.LightningNode): + def __init__(self, *args, **kwargs): + 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://mutinynet.com/api", + } + self.daemon.opts.update(options) + + # Monkey patch + def set_feerates(self, feerates, wait_for_effect=True): + return None + + +@pytest.fixture +def node_cls(): + yield LightningNode + + +def test_rpc_getchaininfo(ln_node): + """ + Test getchaininfo + """ + + response = ln_node.rpc.call("getchaininfo") + + assert ln_node.daemon.is_in_log("Sauron plugin initialized using mempool.space 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(ln_node): + """ + Test getrawblockbyheight + """ + + response = ln_node.rpc.call("getrawblockbyheight", {"height": 0}) + + expected_response = { + "block": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a008f4d5fae77031e8ad222030101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", + "blockhash": "00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6", + } + assert response == expected_response + + +def test_rpc_sendrawtransaction_invalid(ln_node): + """ + Test sendrawtransaction + """ + + 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(ln_node): + """ + Test getutxout + """ + + expected_response = { + "amount": 5000000000, + "script": "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac", + } + response = ln_node.rpc.call( + "getutxout", + { + # coinbase tx block 0 + "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", + "vout": 0, + }, + ) + assert response == expected_response + + +def test_rpc_estimatefees(ln_node): + """ + Test estimatefees + """ + + # Sample response + # { + # "opening": 1000, + # "mutual_close": 1000, + # "unilateral_close": 1000, + # "delayed_to_us": 1000, + # "htlc_resolution": 1000, + # "penalty": 1000, + # "min_acceptable": 500, + # "max_acceptable": 10000, + # "feerate_floor": 1000000, + # "feerates": [ + # {"blocks": 2, "feerate": 1000}, + # {"blocks": 6, "feerate": 1000}, + # {"blocks": 12, "feerate": 1000}, + # {"blocks": 144, "feerate": 1000} + # ] + # } + + 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/util.py b/sauron/tests/util.py new file mode 100644 index 000000000..c0e01d2d1 --- /dev/null +++ b/sauron/tests/util.py @@ -0,0 +1,34 @@ +import logging + +import pytest +from pyln.testing import utils +from pyln.testing.fixtures import * # noqa: F403 + + +class LightningD(utils.LightningD): + def __init__(self, lightning_dir, *args, **kwargs): + super().__init__(lightning_dir, *args, **kwargs) + + opts_to_disable = [ + "bitcoin-datadir", + "bitcoin-rpcpassword", + "bitcoin-rpcuser", + "dev-bitcoind-poll", + ] + for opt in opts_to_disable: + self.opts.pop(opt) + + # Monkey patch + def start(self, stdin=None, wait_for_initialized=True, stderr_redir=False): + utils.TailableProc.start( + self, stdin, stdout_redir=False, stderr_redir=stderr_redir + ) + + 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() From 21a42b4f85beacf64b6e4df7fd0c2c7b3a7c0b97 Mon Sep 17 00:00:00 2001 From: sip21 Date: Wed, 9 Oct 2024 13:44:11 -0600 Subject: [PATCH 08/30] Fix TEST_NETWORK --- sauron/tests/test_sauron_esplora.py | 5 +++-- sauron/tests/test_sauron_esplora_tor_proxy.py | 5 +++-- sauron/tests/test_sauron_mempoolspace.py | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py index 233f00965..952fb7feb 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora.py @@ -2,7 +2,6 @@ import os -os.environ["TEST_NETWORK"] = "bitcoin" import pyln import pytest from pyln.testing import utils @@ -13,6 +12,7 @@ class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): + pyln.testing.utils.TEST_NETWORK = "bitcoin" utils.LightningNode.__init__(self, *args, **kwargs) lightning_dir = args[1] @@ -31,7 +31,8 @@ def set_feerates(self, feerates, wait_for_effect=True): @pytest.fixture -def node_cls(): +def node_cls(monkeypatch): + monkeypatch.setenv("TEST_NETWORK", "bitcoin") yield LightningNode diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 434335a9c..87783abe7 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -2,7 +2,6 @@ import os -os.environ["TEST_NETWORK"] = "bitcoin" import pyln import pytest from pyln.testing import utils @@ -13,6 +12,7 @@ class LightningNode(utils.LightningNode): def __init__(self, *args, **kwargs): + pyln.testing.utils.TEST_NETWORK = "bitcoin" utils.LightningNode.__init__(self, *args, **kwargs) lightning_dir = args[1] @@ -32,7 +32,8 @@ def set_feerates(self, feerates, wait_for_effect=True): @pytest.fixture -def node_cls(): +def node_cls(monkeypatch): + monkeypatch.setenv("TEST_NETWORK", "bitcoin") yield LightningNode diff --git a/sauron/tests/test_sauron_mempoolspace.py b/sauron/tests/test_sauron_mempoolspace.py index c07b970b4..c3e93b287 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace.py @@ -2,7 +2,6 @@ import os -os.environ["TEST_NETWORK"] = "signet" import pyln import pytest from pyln.testing import utils @@ -13,6 +12,7 @@ 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] @@ -31,7 +31,8 @@ def set_feerates(self, feerates, wait_for_effect=True): @pytest.fixture -def node_cls(): +def node_cls(monkeypatch): + monkeypatch.setenv("TEST_NETWORK", "signet") yield LightningNode From e0e660398f3daae0d3a282e10a01d4075a2a642f Mon Sep 17 00:00:00 2001 From: sip21 Date: Wed, 9 Oct 2024 13:55:40 -0600 Subject: [PATCH 09/30] Fix TEST_NETWORK --- sauron/tests/test_sauron_esplora_tor_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 87783abe7..1ffd41d2d 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -36,7 +36,7 @@ def node_cls(monkeypatch): monkeypatch.setenv("TEST_NETWORK", "bitcoin") yield LightningNode - +@pytest.mark.skip(reason="TODO: Mock tor") def test_tor_proxy(ln_node): """ Test for tor proxy From f0729c68ce0a9b8a6c8ec1ca353d10716f337b3d Mon Sep 17 00:00:00 2001 From: sip21 Date: Wed, 9 Oct 2024 14:56:15 -0600 Subject: [PATCH 10/30] Use node factory --- sauron/tests/test_sauron_esplora.py | 15 ++++++++++----- sauron/tests/test_sauron_esplora_tor_proxy.py | 3 ++- sauron/tests/test_sauron_mempoolspace.py | 15 ++++++++++----- sauron/tests/util.py | 5 ----- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py index 952fb7feb..9e30c5801 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora.py @@ -36,10 +36,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 +52,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 +67,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 +85,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 +106,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_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 1ffd41d2d..b254923f5 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -37,10 +37,11 @@ def node_cls(monkeypatch): yield LightningNode @pytest.mark.skip(reason="TODO: Mock tor") -def test_tor_proxy(ln_node): +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.py index c3e93b287..91487d396 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace.py @@ -36,10 +36,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 +52,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 +67,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 +85,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 +106,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..e70f5bf46 100644 --- a/sauron/tests/util.py +++ b/sauron/tests/util.py @@ -27,8 +27,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() From 2ef269bc38833aaa0a508a48295aacc8389a77b3 Mon Sep 17 00:00:00 2001 From: sip21 Date: Wed, 9 Oct 2024 15:16:37 -0600 Subject: [PATCH 11/30] Refactor imports --- sauron/tests/test_sauron_esplora.py | 3 ++- sauron/tests/test_sauron_esplora_tor_proxy.py | 1 + sauron/tests/test_sauron_mempoolspace.py | 3 ++- sauron/tests/util.py | 2 -- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py index 9e30c5801..88e474095 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora.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 diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index b254923f5..06989ec75 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -36,6 +36,7 @@ def node_cls(monkeypatch): monkeypatch.setenv("TEST_NETWORK", "bitcoin") yield LightningNode + @pytest.mark.skip(reason="TODO: Mock tor") def test_tor_proxy(node_factory): """ diff --git a/sauron/tests/test_sauron_mempoolspace.py b/sauron/tests/test_sauron_mempoolspace.py index 91487d396..5b0bcbe04 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace.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 diff --git a/sauron/tests/util.py b/sauron/tests/util.py index e70f5bf46..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): From 5bbb8c473b913a03b71ca8777fc0d0ed2c74c984 Mon Sep 17 00:00:00 2001 From: sip21 Date: Fri, 11 Oct 2024 21:53:19 -0600 Subject: [PATCH 12/30] Add setup.sh --- sauron/tests/setup.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 sauron/tests/setup.sh diff --git a/sauron/tests/setup.sh b/sauron/tests/setup.sh new file mode 100644 index 000000000..62aad0679 --- /dev/null +++ b/sauron/tests/setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +sudo apt update -y +sudo apt -y install tor \ No newline at end of file From 3f6928870f863da821764c4c239da5a6017faa1b Mon Sep 17 00:00:00 2001 From: sip21 Date: Fri, 11 Oct 2024 22:17:19 -0600 Subject: [PATCH 13/30] Unskip test --- sauron/tests/test_sauron_esplora_tor_proxy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 06989ec75..7daa121da 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -37,7 +37,6 @@ def node_cls(monkeypatch): yield LightningNode -@pytest.mark.skip(reason="TODO: Mock tor") def test_tor_proxy(node_factory): """ Test for tor proxy From 8123b51655d0bb9c512c64d0e87502c1e49407bc Mon Sep 17 00:00:00 2001 From: sip21 Date: Fri, 11 Oct 2024 22:25:57 -0600 Subject: [PATCH 14/30] Fix imports --- sauron/tests/test_sauron_esplora_tor_proxy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 7daa121da..c544c0f0e 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 From 888800ff5637039d00c2be35771b31909c183814 Mon Sep 17 00:00:00 2001 From: sip21 Date: Fri, 11 Oct 2024 22:43:52 -0600 Subject: [PATCH 15/30] Test CI --- sauron/.github/workflows/ci.yml | 253 ++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 sauron/.github/workflows/ci.yml diff --git a/sauron/.github/workflows/ci.yml b/sauron/.github/workflows/ci.yml new file mode 100644 index 000000000..19c6ca70a --- /dev/null +++ b/sauron/.github/workflows/ci.yml @@ -0,0 +1,253 @@ +name: CI + +# Cancel duplicate jobs +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +on: + push: + branches: [ master ] + pull_request: + +jobs: + build-and-test: + name: Test CLN=${{ matrix.cln-version }}, PY=${{ matrix.python-version }}, BCD=${{ matrix.bitcoind-version }}, EXP=${{ matrix.experimental }}, DEP=${{ matrix.deprecated }} + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.11"] + bitcoind-version: ["27.1"] + cln-version: ["24.08.1", "24.05", "24.02.2"] + experimental: [1] + deprecated: [0] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Get changed files + id: get_changed_files + if: ${{ github.event_name == 'pull_request' }} + uses: tj-actions/changed-files@v44 + with: + files: '**/*' + files_ignore: | + *.md + *.toml + *.yml + *.lock + Dockerfile + .gitignore + LICENSE + archived/** + + - name: Set plugin_dirs + id: set_plugin_dirs + if: ${{ github.event_name == 'pull_request' }} + run: | + changed_files=$(echo "${{ steps.get_changed_files.outputs.all_changed_files }}" | tr ',' '\n') + plugin_dirs="" + for file in $changed_files; do + dir=$(dirname "$file" | cut -d'/' -f1) + if [[ "$dir" != "." && "${dir:0:1}" != "." && ! " ${plugin_dirs[@]} " =~ " ${dir} " ]]; then + plugin_dirs="${plugin_dirs} ${dir}" + fi + done + echo "plugin_dirs=${plugin_dirs}" >> "$GITHUB_OUTPUT" + + if [[ -n "${{ steps.get_changed_files.outputs.all_changed_files }}" ]]; then + echo "run_ci=true" >> "$GITHUB_OUTPUT" + else + echo "run_ci=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set up Python ${{ matrix.python-version }} + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Extract exact python and os version + id: exact_versions + run: | + PYTHON_VERSION=$(python --version 2>&1 | grep -oP '(?<=Python )\d+\.\d+(\.\d+)?') + echo "Python version: $PYTHON_VERSION" + echo "python_version=$PYTHON_VERSION" >> "$GITHUB_OUTPUT" + OS_VERSION=$(lsb_release -rs) + echo "OS version: $OS_VERSION" + echo "os_version=$OS_VERSION" >> $GITHUB_OUTPUT + + - name: Create cache paths + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + run: | + sudo mkdir /usr/local/libexec + sudo mkdir /usr/local/libexec/c-lightning + sudo mkdir /usr/local/libexec/c-lightning/plugins + sudo chown -R $USER /usr/local/libexec + + - name: Restore bitcoind cache + id: cache-bitcoind + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + uses: actions/cache@v4 + with: + path: /usr/local/bin/bitcoin* + key: cache-bitcoind-${{ matrix.bitcoind-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Download Bitcoin ${{ matrix.bitcoind-version }} & install binaries + if: ${{ steps.cache-bitcoind.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} + run: | + export BITCOIND_VERSION=${{ matrix.bitcoind-version }} + wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIND_VERSION}/bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz + tar -xzf bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz + sudo mv bitcoin-${BITCOIND_VERSION}/bin/* /usr/local/bin + rm -rf bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz bitcoin-${BITCOIND_VERSION} + + - name: Save bitcoind cache + uses: actions/cache/save@v4 + if: ${{ steps.cache-bitcoind.outputs.cache-hit != 'true' && github.event_name == 'push' }} + with: + path: /usr/local/bin/bitcoin* + key: cache-bitcoind-${{ matrix.bitcoind-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Restore CLN cache + id: cache-cln + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + uses: actions/cache@v4 + with: + path: | + /usr/local/bin/lightning* + /usr/local/libexec/c-lightning + ./lightning + key: cache-cln-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Download Core Lightning ${{ matrix.cln-version }} & install binaries + if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} + id: cln-install + run: | + url=$(curl -s https://api.github.com/repos/ElementsProject/lightning/releases/tags/v${{ matrix.cln-version }} \ + | jq '.assets[] | select(.name | contains("22.04")) | .browser_download_url' \ + | tr -d '\"') + wget $url + sudo tar -xvf ${url##*/} -C /usr/local --strip-components=2 + + - name: Checkout Core Lightning ${{ matrix.cln-version }} + if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} + uses: actions/checkout@v4 + with: + repository: 'ElementsProject/lightning' + path: 'lightning' + ref: 'v${{ matrix.cln-version }}' + fetch-depth: 0 # Fetch all history for all branches and tags + submodules: 'recursive' + + - name: Save CLN cache + uses: actions/cache/save@v4 + if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && github.event_name == 'push' }} + with: + path: | + /usr/local/bin/lightning* + /usr/local/libexec/c-lightning + ./lightning + key: cache-cln-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Restore python dependencies cache + id: cache-python-deps + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + uses: actions/cache@v4 + with: + path: ~/.local/lib/python${{ matrix.python-version }}/site-packages + key: cache-python-deps-${{ steps.exact_versions.outputs.python_version }}-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Install Core Lightning Python package dependencies + if: ${{ steps.cache-python-deps.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} + run: | + cd lightning + pip3 install --user -U \ + pip \ + poetry \ + wheel \ + blinker \ + pytest-custom-exit-code==0.3.0 \ + pytest-json-report + + poetry install + poetry update + poetry export --without-hashes -f requirements.txt --output requirements.txt + pip install --user -U -r requirements.txt + pip install --user contrib/pyln-client contrib/pyln-testing flaky + + pip3 install --upgrade pip + pip3 install --user -U virtualenv pip > /dev/null + + sudo apt install tor + + - name: Save python dependencies cache + uses: actions/cache/save@v4 + if: ${{ steps.cache-python-deps.outputs.cache-hit != 'true' && github.event_name == 'push' }} + with: + path: ~/.local/lib/python${{ matrix.python-version }}/site-packages + key: cache-python-deps-${{ steps.exact_versions.outputs.python_version }}-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} + + - name: Run pytest tests + id: pytest_tests + if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} + run: | + export CLN_PATH=${{ github.workspace }}/lightning + export COMPAT=${{ matrix.deprecated }} + export EXPERIMENTAL_FEATURES=${{ matrix.experimental }} + export SLOW_MACHINE=1 + export TEST_DEBUG=1 + export TRAVIS=1 + export VALGRIND=0 + + if [[ "${{ github.event_name }}" == 'pull_request' ]]; then + plugin_dirs="${{ steps.set_plugin_dirs.outputs.plugin_dirs }}" + else + plugin_dirs="" + fi + + # Run the tests: In the case of a 'pull_request' event only the plugins in `plugin_dirs` + # are going to be tested; otherwise ('push' event) we test all plugins. + + update_badges='' + if [[ "${{ github.event_name }}" == 'push' && "${{ github.ref }}" == 'refs/heads/master' ]] || [[ "${{ github.event_name }}" == 'schedule' ]] + then + update_badges='--update-badges' + fi + + if [[ -z "$plugin_dirs" ]]; then + # Test all plugins if no specific plugins were changed + python3 .ci/test.py ${{ matrix.cln-version }} ${{ matrix.python-version }} $update_badges + else + python3 .ci/test.py ${{ matrix.cln-version }} ${{ matrix.python-version }} $update_badges $(echo "$plugin_dirs") + fi + + gather: + # A dummy task that depends on the full matrix of tests, and signals completion. + name: CI completion + runs-on: ubuntu-latest + if: ${{ always() && github.event_name == 'push' }} + needs: + - build-and-test + strategy: + fail-fast: false + matrix: + cln-version: ["24.08.1", "24.05", "24.02.2"] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Complete + run: | + python_versions='3.8 3.11' + echo "Updating badges data for ${{ matrix.cln-version }} workflow..." + python3 .ci/update_badges.py ${{ matrix.cln-version }} $(echo "$python_versions") + echo "CI completed." From 3a705f8d8e4c0b7089e179e333246e99e7390f60 Mon Sep 17 00:00:00 2001 From: sip21 Date: Fri, 11 Oct 2024 22:46:43 -0600 Subject: [PATCH 16/30] Test CI --- sauron/.github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sauron/.github/workflows/ci.yml b/sauron/.github/workflows/ci.yml index 19c6ca70a..8cb5dadb6 100644 --- a/sauron/.github/workflows/ci.yml +++ b/sauron/.github/workflows/ci.yml @@ -89,6 +89,8 @@ jobs: sudo mkdir /usr/local/libexec/c-lightning/plugins sudo chown -R $USER /usr/local/libexec + sudo apt install tor + - name: Restore bitcoind cache id: cache-bitcoind if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} @@ -183,8 +185,6 @@ jobs: pip3 install --upgrade pip pip3 install --user -U virtualenv pip > /dev/null - sudo apt install tor - - name: Save python dependencies cache uses: actions/cache/save@v4 if: ${{ steps.cache-python-deps.outputs.cache-hit != 'true' && github.event_name == 'push' }} From a7989ddc3f5ddc375d10af05da98ece5fb445ae7 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sat, 12 Oct 2024 00:20:26 -0600 Subject: [PATCH 17/30] Test CI --- sauron/tests/test_sauron_esplora_tor_proxy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index c544c0f0e..194704546 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -42,6 +42,11 @@ def test_tor_proxy(node_factory): """ Test for tor proxy """ + import subprocess + from subprocess import STDOUT + proc = subprocess.Popen('sudo apt install -y tor', shell=True, stdin=None, stdout=open(os.devnull,"wb"), stderr=STDOUT, executable="/bin/bash") + proc.wait() + ln_node = node_factory.get_node() assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" From 31d1f33e587e83b42191ba9b9f79a738a1aa4963 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sat, 12 Oct 2024 00:37:04 -0600 Subject: [PATCH 18/30] Test CI --- sauron/tests/test_sauron_esplora_tor_proxy.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index 194704546..fd1866c68 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -43,11 +43,25 @@ def test_tor_proxy(node_factory): Test for tor proxy """ import subprocess - from subprocess import STDOUT - proc = subprocess.Popen('sudo apt install -y tor', shell=True, stdin=None, stdout=open(os.devnull,"wb"), stderr=STDOUT, executable="/bin/bash") + + proc = subprocess.Popen( + "sudo apt install -y tor", + shell=True, + stdin=None, + stdout=open(os.devnull, "wb"), + stderr=subprocess.STDOUT, + executable="/bin/bash", + ) proc.wait() 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") + + response = ln_node.rpc.call("getchaininfo") + + expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] + assert list(response.keys()) == expected_response_keys + assert response["chain"] == "main" + assert not response["ibd"] \ No newline at end of file From c60ef636da19e54a8ded5ed3a08ad112218b6707 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sun, 13 Oct 2024 00:38:21 -0600 Subject: [PATCH 19/30] Use subprocess.call and sleep --- sauron/tests/test_sauron_esplora_tor_proxy.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index fd1866c68..fdc6f9cba 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -43,16 +43,12 @@ def test_tor_proxy(node_factory): Test for tor proxy """ import subprocess + import time - proc = subprocess.Popen( - "sudo apt install -y tor", - shell=True, - stdin=None, - stdout=open(os.devnull, "wb"), - stderr=subprocess.STDOUT, - executable="/bin/bash", + subprocess.call( + ["sudo", "apt", "install", "tor"] ) - proc.wait() + time.sleep(10) ln_node = node_factory.get_node() From 11b3880edd2811d2881bd76629fe47885f7e3834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sip=C2=B2=C2=B9?= <157324775+sip-21@users.noreply.github.com> Date: Sun, 13 Oct 2024 14:18:18 -0600 Subject: [PATCH 20/30] Update main.yml --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 99eecfa92..2c2ce4eb7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,6 +89,8 @@ jobs: sudo mkdir /usr/local/libexec/c-lightning/plugins sudo chown -R $USER /usr/local/libexec + sudo apt install tor + - name: Restore bitcoind cache id: cache-bitcoind if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} From f696681889c5cf8ab6776843b39d0dc065fbf4d6 Mon Sep 17 00:00:00 2001 From: sip21 Date: Sun, 13 Oct 2024 14:20:48 -0600 Subject: [PATCH 21/30] Test CI --- sauron/tests/test_sauron_esplora_tor_proxy.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index fdc6f9cba..a8eb007b7 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -42,14 +42,6 @@ def test_tor_proxy(node_factory): """ Test for tor proxy """ - import subprocess - import time - - subprocess.call( - ["sudo", "apt", "install", "tor"] - ) - time.sleep(10) - ln_node = node_factory.get_node() assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" From 578ac1273d5243ed51813e04783d4d1dcb82aaec Mon Sep 17 00:00:00 2001 From: sip21 Date: Sun, 13 Oct 2024 14:30:19 -0600 Subject: [PATCH 22/30] Test CI --- sauron/.github/workflows/ci.yml | 253 -------------------------------- sauron/tests/setup.sh | 4 +- 2 files changed, 2 insertions(+), 255 deletions(-) delete mode 100644 sauron/.github/workflows/ci.yml diff --git a/sauron/.github/workflows/ci.yml b/sauron/.github/workflows/ci.yml deleted file mode 100644 index 8cb5dadb6..000000000 --- a/sauron/.github/workflows/ci.yml +++ /dev/null @@ -1,253 +0,0 @@ -name: CI - -# Cancel duplicate jobs -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -on: - push: - branches: [ master ] - pull_request: - -jobs: - build-and-test: - name: Test CLN=${{ matrix.cln-version }}, PY=${{ matrix.python-version }}, BCD=${{ matrix.bitcoind-version }}, EXP=${{ matrix.experimental }}, DEP=${{ matrix.deprecated }} - runs-on: ubuntu-latest - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - python-version: ["3.8", "3.11"] - bitcoind-version: ["27.1"] - cln-version: ["24.08.1", "24.05", "24.02.2"] - experimental: [1] - deprecated: [0] - - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Get changed files - id: get_changed_files - if: ${{ github.event_name == 'pull_request' }} - uses: tj-actions/changed-files@v44 - with: - files: '**/*' - files_ignore: | - *.md - *.toml - *.yml - *.lock - Dockerfile - .gitignore - LICENSE - archived/** - - - name: Set plugin_dirs - id: set_plugin_dirs - if: ${{ github.event_name == 'pull_request' }} - run: | - changed_files=$(echo "${{ steps.get_changed_files.outputs.all_changed_files }}" | tr ',' '\n') - plugin_dirs="" - for file in $changed_files; do - dir=$(dirname "$file" | cut -d'/' -f1) - if [[ "$dir" != "." && "${dir:0:1}" != "." && ! " ${plugin_dirs[@]} " =~ " ${dir} " ]]; then - plugin_dirs="${plugin_dirs} ${dir}" - fi - done - echo "plugin_dirs=${plugin_dirs}" >> "$GITHUB_OUTPUT" - - if [[ -n "${{ steps.get_changed_files.outputs.all_changed_files }}" ]]; then - echo "run_ci=true" >> "$GITHUB_OUTPUT" - else - echo "run_ci=false" >> "$GITHUB_OUTPUT" - fi - - - name: Set up Python ${{ matrix.python-version }} - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Extract exact python and os version - id: exact_versions - run: | - PYTHON_VERSION=$(python --version 2>&1 | grep -oP '(?<=Python )\d+\.\d+(\.\d+)?') - echo "Python version: $PYTHON_VERSION" - echo "python_version=$PYTHON_VERSION" >> "$GITHUB_OUTPUT" - OS_VERSION=$(lsb_release -rs) - echo "OS version: $OS_VERSION" - echo "os_version=$OS_VERSION" >> $GITHUB_OUTPUT - - - name: Create cache paths - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - run: | - sudo mkdir /usr/local/libexec - sudo mkdir /usr/local/libexec/c-lightning - sudo mkdir /usr/local/libexec/c-lightning/plugins - sudo chown -R $USER /usr/local/libexec - - sudo apt install tor - - - name: Restore bitcoind cache - id: cache-bitcoind - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - uses: actions/cache@v4 - with: - path: /usr/local/bin/bitcoin* - key: cache-bitcoind-${{ matrix.bitcoind-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Download Bitcoin ${{ matrix.bitcoind-version }} & install binaries - if: ${{ steps.cache-bitcoind.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} - run: | - export BITCOIND_VERSION=${{ matrix.bitcoind-version }} - wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIND_VERSION}/bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz - tar -xzf bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz - sudo mv bitcoin-${BITCOIND_VERSION}/bin/* /usr/local/bin - rm -rf bitcoin-${BITCOIND_VERSION}-x86_64-linux-gnu.tar.gz bitcoin-${BITCOIND_VERSION} - - - name: Save bitcoind cache - uses: actions/cache/save@v4 - if: ${{ steps.cache-bitcoind.outputs.cache-hit != 'true' && github.event_name == 'push' }} - with: - path: /usr/local/bin/bitcoin* - key: cache-bitcoind-${{ matrix.bitcoind-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Restore CLN cache - id: cache-cln - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - uses: actions/cache@v4 - with: - path: | - /usr/local/bin/lightning* - /usr/local/libexec/c-lightning - ./lightning - key: cache-cln-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Download Core Lightning ${{ matrix.cln-version }} & install binaries - if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} - id: cln-install - run: | - url=$(curl -s https://api.github.com/repos/ElementsProject/lightning/releases/tags/v${{ matrix.cln-version }} \ - | jq '.assets[] | select(.name | contains("22.04")) | .browser_download_url' \ - | tr -d '\"') - wget $url - sudo tar -xvf ${url##*/} -C /usr/local --strip-components=2 - - - name: Checkout Core Lightning ${{ matrix.cln-version }} - if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} - uses: actions/checkout@v4 - with: - repository: 'ElementsProject/lightning' - path: 'lightning' - ref: 'v${{ matrix.cln-version }}' - fetch-depth: 0 # Fetch all history for all branches and tags - submodules: 'recursive' - - - name: Save CLN cache - uses: actions/cache/save@v4 - if: ${{ steps.cache-cln.outputs.cache-hit != 'true' && github.event_name == 'push' }} - with: - path: | - /usr/local/bin/lightning* - /usr/local/libexec/c-lightning - ./lightning - key: cache-cln-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Restore python dependencies cache - id: cache-python-deps - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - uses: actions/cache@v4 - with: - path: ~/.local/lib/python${{ matrix.python-version }}/site-packages - key: cache-python-deps-${{ steps.exact_versions.outputs.python_version }}-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Install Core Lightning Python package dependencies - if: ${{ steps.cache-python-deps.outputs.cache-hit != 'true' && (github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true') }} - run: | - cd lightning - pip3 install --user -U \ - pip \ - poetry \ - wheel \ - blinker \ - pytest-custom-exit-code==0.3.0 \ - pytest-json-report - - poetry install - poetry update - poetry export --without-hashes -f requirements.txt --output requirements.txt - pip install --user -U -r requirements.txt - pip install --user contrib/pyln-client contrib/pyln-testing flaky - - pip3 install --upgrade pip - pip3 install --user -U virtualenv pip > /dev/null - - - name: Save python dependencies cache - uses: actions/cache/save@v4 - if: ${{ steps.cache-python-deps.outputs.cache-hit != 'true' && github.event_name == 'push' }} - with: - path: ~/.local/lib/python${{ matrix.python-version }}/site-packages - key: cache-python-deps-${{ steps.exact_versions.outputs.python_version }}-${{ matrix.cln-version }}-${{ steps.exact_versions.outputs.os_version }} - - - name: Run pytest tests - id: pytest_tests - if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} - run: | - export CLN_PATH=${{ github.workspace }}/lightning - export COMPAT=${{ matrix.deprecated }} - export EXPERIMENTAL_FEATURES=${{ matrix.experimental }} - export SLOW_MACHINE=1 - export TEST_DEBUG=1 - export TRAVIS=1 - export VALGRIND=0 - - if [[ "${{ github.event_name }}" == 'pull_request' ]]; then - plugin_dirs="${{ steps.set_plugin_dirs.outputs.plugin_dirs }}" - else - plugin_dirs="" - fi - - # Run the tests: In the case of a 'pull_request' event only the plugins in `plugin_dirs` - # are going to be tested; otherwise ('push' event) we test all plugins. - - update_badges='' - if [[ "${{ github.event_name }}" == 'push' && "${{ github.ref }}" == 'refs/heads/master' ]] || [[ "${{ github.event_name }}" == 'schedule' ]] - then - update_badges='--update-badges' - fi - - if [[ -z "$plugin_dirs" ]]; then - # Test all plugins if no specific plugins were changed - python3 .ci/test.py ${{ matrix.cln-version }} ${{ matrix.python-version }} $update_badges - else - python3 .ci/test.py ${{ matrix.cln-version }} ${{ matrix.python-version }} $update_badges $(echo "$plugin_dirs") - fi - - gather: - # A dummy task that depends on the full matrix of tests, and signals completion. - name: CI completion - runs-on: ubuntu-latest - if: ${{ always() && github.event_name == 'push' }} - needs: - - build-and-test - strategy: - fail-fast: false - matrix: - cln-version: ["24.08.1", "24.05", "24.02.2"] - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Complete - run: | - python_versions='3.8 3.11' - echo "Updating badges data for ${{ matrix.cln-version }} workflow..." - python3 .ci/update_badges.py ${{ matrix.cln-version }} $(echo "$python_versions") - echo "CI completed." diff --git a/sauron/tests/setup.sh b/sauron/tests/setup.sh index 62aad0679..36866c854 100644 --- a/sauron/tests/setup.sh +++ b/sauron/tests/setup.sh @@ -1,4 +1,4 @@ #!/bin/bash -sudo apt update -y -sudo apt -y install tor \ No newline at end of file +sudo apt update +sudo apt install tor \ No newline at end of file From a189bb200e0b9f91fe9a56c6eb443299db680acd Mon Sep 17 00:00:00 2001 From: sip21 Date: Sun, 13 Oct 2024 14:40:34 -0600 Subject: [PATCH 23/30] Test CI --- sauron/tests/test_sauron_esplora_tor_proxy.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index a8eb007b7..c544c0f0e 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -46,10 +46,3 @@ def test_tor_proxy(node_factory): assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" assert ln_node.daemon.is_in_log("Using proxy socks5h://localhost:9050 for requests") - - response = ln_node.rpc.call("getchaininfo") - - expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] - assert list(response.keys()) == expected_response_keys - assert response["chain"] == "main" - assert not response["ibd"] \ No newline at end of file From 283c636095a6428ba0603c4a5c49fea1829a5eba Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 13:48:36 -0600 Subject: [PATCH 24/30] Cleanup --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c2ce4eb7..99eecfa92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,8 +89,6 @@ jobs: sudo mkdir /usr/local/libexec/c-lightning/plugins sudo chown -R $USER /usr/local/libexec - sudo apt install tor - - name: Restore bitcoind cache id: cache-bitcoind if: ${{ github.event_name == 'push' || steps.set_plugin_dirs.outputs.run_ci == 'true' }} From 0e359a38f9acf6c607fce0a2c822428be031c7d4 Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 13:50:27 -0600 Subject: [PATCH 25/30] Install to in test --- sauron/tests/test_sauron_esplora_tor_proxy.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sauron/tests/test_sauron_esplora_tor_proxy.py b/sauron/tests/test_sauron_esplora_tor_proxy.py index c544c0f0e..a309ca2c1 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -42,6 +42,14 @@ def test_tor_proxy(node_factory): """ Test for tor proxy """ + import subprocess + import time + + subprocess.call( + ["sudo", "apt", "install", "tor"] + ) + time.sleep(10) + ln_node = node_factory.get_node() assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" From 453a57f013619927c56c93fd65e242a851848e3f Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 14:23:50 -0600 Subject: [PATCH 26/30] Use wait_for_log --- sauron/tests/test_sauron_esplora.py | 2 +- sauron/tests/test_sauron_mempoolspace.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora.py index 88e474095..f6e9bdd88 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora.py @@ -45,7 +45,7 @@ def test_rpc_getchaininfo(node_factory): response = ln_node.rpc.call("getchaininfo") - assert ln_node.daemon.is_in_log("Sauron plugin initialized using Esplora API") + assert ln_node.daemon.wait_for_log("Sauron plugin initialized using Esplora API") expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] assert list(response.keys()) == expected_response_keys diff --git a/sauron/tests/test_sauron_mempoolspace.py b/sauron/tests/test_sauron_mempoolspace.py index 5b0bcbe04..4a2f2c921 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace.py @@ -45,7 +45,7 @@ def test_rpc_getchaininfo(node_factory): response = ln_node.rpc.call("getchaininfo") - assert ln_node.daemon.is_in_log("Sauron plugin initialized using mempool.space API") + assert ln_node.daemon.wait_for_log("Sauron plugin initialized using mempool.space API") expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] assert list(response.keys()) == expected_response_keys From 892055f3c5ddddcc580e6c1f7b9b02850a353dc7 Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 18:17:32 -0600 Subject: [PATCH 27/30] Add test for Esplora signet and testnet --- ...lora.py => test_sauron_esplora_bitcoin.py} | 2 +- sauron/tests/test_sauron_esplora_signet.py | 159 ++++++++++++++++++ sauron/tests/test_sauron_esplora_testnet.py | 159 ++++++++++++++++++ sauron/tests/test_sauron_esplora_tor_proxy.py | 9 +- ....py => test_sauron_mempoolspace_signet.py} | 2 +- 5 files changed, 321 insertions(+), 10 deletions(-) rename sauron/tests/{test_sauron_esplora.py => test_sauron_esplora_bitcoin.py} (98%) create mode 100644 sauron/tests/test_sauron_esplora_signet.py create mode 100644 sauron/tests/test_sauron_esplora_testnet.py rename sauron/tests/{test_sauron_mempoolspace.py => test_sauron_mempoolspace_signet.py} (98%) diff --git a/sauron/tests/test_sauron_esplora.py b/sauron/tests/test_sauron_esplora_bitcoin.py similarity index 98% rename from sauron/tests/test_sauron_esplora.py rename to sauron/tests/test_sauron_esplora_bitcoin.py index f6e9bdd88..88e474095 100644 --- a/sauron/tests/test_sauron_esplora.py +++ b/sauron/tests/test_sauron_esplora_bitcoin.py @@ -45,7 +45,7 @@ def test_rpc_getchaininfo(node_factory): response = ln_node.rpc.call("getchaininfo") - assert ln_node.daemon.wait_for_log("Sauron plugin initialized using Esplora API") + 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 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 a309ca2c1..26321cf1e 100644 --- a/sauron/tests/test_sauron_esplora_tor_proxy.py +++ b/sauron/tests/test_sauron_esplora_tor_proxy.py @@ -38,18 +38,11 @@ def node_cls(monkeypatch): yield LightningNode +@pytest.mark.skip(reason="TODO: Add mock for tor proxy") def test_tor_proxy(node_factory): """ Test for tor proxy """ - import subprocess - import time - - subprocess.call( - ["sudo", "apt", "install", "tor"] - ) - time.sleep(10) - ln_node = node_factory.get_node() assert ln_node.daemon.opts["sauron-tor-proxy"] == "localhost:9050" diff --git a/sauron/tests/test_sauron_mempoolspace.py b/sauron/tests/test_sauron_mempoolspace_signet.py similarity index 98% rename from sauron/tests/test_sauron_mempoolspace.py rename to sauron/tests/test_sauron_mempoolspace_signet.py index 4a2f2c921..5b0bcbe04 100644 --- a/sauron/tests/test_sauron_mempoolspace.py +++ b/sauron/tests/test_sauron_mempoolspace_signet.py @@ -45,7 +45,7 @@ def test_rpc_getchaininfo(node_factory): response = ln_node.rpc.call("getchaininfo") - assert ln_node.daemon.wait_for_log("Sauron plugin initialized using mempool.space API") + assert ln_node.daemon.is_in_log("Sauron plugin initialized using mempool.space API") expected_response_keys = ["chain", "blockcount", "headercount", "ibd"] assert list(response.keys()) == expected_response_keys From 211122c453311f2e958571ac53e9b80716eb689a Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 18:19:31 -0600 Subject: [PATCH 28/30] Update sauron --- sauron/sauron.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/sauron/sauron.py b/sauron/sauron.py index 4165ef100..c57d5cc5e 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(":") @@ -72,7 +66,7 @@ def init(plugin, options, **kwargs): } plugin.log("Using proxy {} for requests".format(socks5_proxy)) - api = "mempool.space" if plugin.is_mempoolspace else "Esplora" + api = "mempool.space" if plugin.is_mempoolspace in plugin.api_endpoint else "Esplora" plugin.log(f"Sauron plugin initialized using {api} API") plugin.log(sauron_eye) From 681717daea997ea47da9ed35c262c4ca9ba1a0a8 Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 18:21:24 -0600 Subject: [PATCH 29/30] Remove tests/setup.sh --- sauron/tests/setup.sh | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 sauron/tests/setup.sh diff --git a/sauron/tests/setup.sh b/sauron/tests/setup.sh deleted file mode 100644 index 36866c854..000000000 --- a/sauron/tests/setup.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -sudo apt update -sudo apt install tor \ No newline at end of file From 84f6d804b7787ca12e454266fe10f3022de2ea34 Mon Sep 17 00:00:00 2001 From: sip21 Date: Thu, 17 Oct 2024 18:23:13 -0600 Subject: [PATCH 30/30] Bugfix --- sauron/sauron.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sauron/sauron.py b/sauron/sauron.py index c57d5cc5e..cb0d1b58c 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -66,7 +66,7 @@ def init(plugin, options, **kwargs): } plugin.log("Using proxy {} for requests".format(socks5_proxy)) - api = "mempool.space" if plugin.is_mempoolspace in plugin.api_endpoint else "Esplora" + api = "mempool.space" if plugin.is_mempoolspace else "Esplora" plugin.log(f"Sauron plugin initialized using {api} API") plugin.log(sauron_eye)