sauron: add Tor proxy support

Signed-off-by: Antoine Poinsot <darosior@protonmail.com>
This commit is contained in:
Antoine Poinsot
2020-05-17 22:59:16 +02:00
committed by Christian Decker
parent 04662336fa
commit bd1459ab4e
3 changed files with 50 additions and 11 deletions

View File

@@ -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/ 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/
```

View File

@@ -1,2 +1,3 @@
pyln-client>=0.7.3 pyln-client>=0.7.3
requests>=2.0.0 requests>=2.23.0
requests[socks]>=2.23.0

View File

@@ -6,20 +6,40 @@ from art import sauron_eye
from pyln.client import Plugin from pyln.client import Plugin
plugin = Plugin() plugin = Plugin(dynamic=False)
plugin.sauron_socks_proxies = None
class SauronError(Exception): class SauronError(Exception):
pass 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() @plugin.init()
def init(plugin, options, configuration, **kwargs): 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: if not plugin.api_endpoint:
raise SauronError("You need to specify the sauron-api-endpoint option.") raise SauronError("You need to specify the sauron-api-endpoint option.")
sys.exit(1) 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 plugin initialized")
plugin.log(sauron_eye) plugin.log(sauron_eye)
@@ -37,14 +57,14 @@ def getchaininfo(plugin, **kwargs):
"regtest" "regtest"
} }
genesis_req = requests.get(blockhash_url) genesis_req = fetch(blockhash_url)
if not genesis_req.status_code == 200: if not genesis_req.status_code == 200:
raise SauronError("Endpoint at {} returned {} ({}) when trying to " raise SauronError("Endpoint at {} returned {} ({}) when trying to "
"get genesis block hash." "get genesis block hash."
.format(blockhash_url, genesis_req.status_code, .format(blockhash_url, genesis_req.status_code,
genesis_req.text)) genesis_req.text))
blockcount_req = requests.get(blockcount_url) blockcount_req = fetch(blockcount_url)
if not blockcount_req.status_code == 200: if not blockcount_req.status_code == 200:
raise SauronError("Endpoint at {} returned {} ({}) when trying to " raise SauronError("Endpoint at {} returned {} ({}) when trying to "
"get blockcount.".format(blockcount_url, "get blockcount.".format(blockcount_url,
@@ -67,9 +87,9 @@ def getchaininfo(plugin, **kwargs):
def getrawblock(plugin, height, **kwargs): def getrawblock(plugin, height, **kwargs):
blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height)
blockhash_req = requests.get(blockhash_url) blockhash_req = fetch(blockhash_url)
block_req = requests.get("{}/block/{}/raw".format(plugin.api_endpoint, block_req = fetch("{}/block/{}/raw".format(plugin.api_endpoint,
blockhash_req.text)) blockhash_req.text))
if blockhash_req.status_code != 200 or block_req.status_code != 200: if blockhash_req.status_code != 200 or block_req.status_code != 200:
return { return {
"blockhash": None, "blockhash": None,
@@ -104,13 +124,13 @@ def getutxout(plugin, txid, vout, **kwargs):
gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid) gettx_url = "{}/tx/{}".format(plugin.api_endpoint, txid)
status_url = "{}/tx/{}/outspend/{}".format(plugin.api_endpoint, txid, vout) 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: if not gettx_req.status_code == 200:
raise SauronError("Endpoint at {} returned {} ({}) when trying to " raise SauronError("Endpoint at {} returned {} ({}) when trying to "
"get transaction.".format(gettx_url, "get transaction.".format(gettx_url,
gettx_req.status_code, gettx_req.status_code,
gettx_req.text)) gettx_req.text))
status_req = requests.get(status_url) status_req = fetch(status_url)
if not status_req.status_code == 200: if not status_req.status_code == 200:
raise SauronError("Endpoint at {} returned {} ({}) when trying to " raise SauronError("Endpoint at {} returned {} ({}) when trying to "
"get utxo status.".format(status_url, "get utxo status.".format(status_url,
@@ -134,7 +154,7 @@ def getutxout(plugin, txid, vout, **kwargs):
def getfeerate(plugin, **kwargs): def getfeerate(plugin, **kwargs):
feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) 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 assert feerate_req.status_code == 200
feerates = feerate_req.json() feerates = feerate_req.json()
# It renders sat/vB, we want sat/kVB, so multiply everything by 10**3 # 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')." "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() plugin.run()