diff --git a/sauron/README.md b/sauron/README.md index 977b711..d02dbe1 100644 --- a/sauron/README.md +++ b/sauron/README.md @@ -23,3 +23,12 @@ 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/ ``` + +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`. + +Hidden services are also supported : +``` +lightningd --testnet --disable-plugin bcli --plugin $PWD/sauron.py --sauron-tor-proxy localhost:9050 --sauron-api-endpoint http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/testnet/api/ +``` diff --git a/sauron/requirements.txt b/sauron/requirements.txt index 719916d..a0c901b 100644 --- a/sauron/requirements.txt +++ b/sauron/requirements.txt @@ -1,2 +1,3 @@ pyln-client>=0.7.3 -requests>=2.0.0 +requests>=2.23.0 +requests[socks]>=2.23.0 diff --git a/sauron/sauron.py b/sauron/sauron.py index 209f358..9998486 100755 --- a/sauron/sauron.py +++ b/sauron/sauron.py @@ -6,20 +6,40 @@ from art import sauron_eye from pyln.client import Plugin -plugin = Plugin() +plugin = Plugin(dynamic=False) +plugin.sauron_socks_proxies = None 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 + # transactions ? Hint: lightningd will agressively send us the same + # transaction a certain amount of times. + session = requests.session() + session.proxies = plugin.sauron_socks_proxies + return session.get(url) + + @plugin.init() def init(plugin, options, configuration, **kwargs): - plugin.api_endpoint = options.get("sauron-api-endpoint") + plugin.api_endpoint = options["sauron-api-endpoint"] if not plugin.api_endpoint: raise SauronError("You need to specify the sauron-api-endpoint option.") sys.exit(1) + if options["sauron-tor-proxy"]: + address, port = options["sauron-tor-proxy"].split(":") + socks5_proxy = "socks5h://{}:{}".format(address, port) + plugin.sauron_socks_proxies = { + "http": socks5_proxy, + "https": socks5_proxy, + } + plugin.log("Using proxy {} for requests".format(socks5_proxy)) + plugin.log("Sauron plugin initialized") plugin.log(sauron_eye) @@ -37,14 +57,14 @@ def getchaininfo(plugin, **kwargs): "regtest" } - genesis_req = requests.get(blockhash_url) + genesis_req = fetch(blockhash_url) if not genesis_req.status_code == 200: raise SauronError("Endpoint at {} returned {} ({}) when trying to " "get genesis block hash." .format(blockhash_url, genesis_req.status_code, genesis_req.text)) - blockcount_req = requests.get(blockcount_url) + blockcount_req = fetch(blockcount_url) if not blockcount_req.status_code == 200: raise SauronError("Endpoint at {} returned {} ({}) when trying to " "get blockcount.".format(blockcount_url, @@ -67,9 +87,9 @@ def getchaininfo(plugin, **kwargs): def getrawblock(plugin, height, **kwargs): blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) - blockhash_req = requests.get(blockhash_url) - block_req = requests.get("{}/block/{}/raw".format(plugin.api_endpoint, - blockhash_req.text)) + blockhash_req = fetch(blockhash_url) + block_req = fetch("{}/block/{}/raw".format(plugin.api_endpoint, + blockhash_req.text)) if blockhash_req.status_code != 200 or block_req.status_code != 200: return { "blockhash": None, @@ -104,13 +124,13 @@ def getutxout(plugin, txid, vout, **kwargs): gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) - gettx_req = requests.get(gettx_url) + 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 = requests.get(status_url) + 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, @@ -134,7 +154,7 @@ def getutxout(plugin, txid, vout, **kwargs): def getfeerate(plugin, **kwargs): feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) - feerate_req = requests.get(feerate_url) + feerate_req = fetch(feerate_url) assert feerate_req.status_code == 200 feerates = feerate_req.json() # It renders sat/vB, we want sat/kVB, so multiply everything by 10**3 @@ -161,4 +181,13 @@ plugin.add_option( "The URL of the esplora instance to hit (including '/api')." ) +plugin.add_option( + "sauron-tor-proxy", + "", + "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." +) + + plugin.run()