From 2bc8680c62fe9857812aaa886702fee3bf5b418d Mon Sep 17 00:00:00 2001 From: darosior Date: Thu, 6 Feb 2020 15:29:43 +0100 Subject: [PATCH] Initial version of an esplora backend plugin --- sauron/art.py | 26 ++++++++ sauron/requirements.txt | 2 + sauron/sauron.py | 135 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 sauron/art.py create mode 100644 sauron/requirements.txt create mode 100755 sauron/sauron.py diff --git a/sauron/art.py b/sauron/art.py new file mode 100644 index 000000000..215845c39 --- /dev/null +++ b/sauron/art.py @@ -0,0 +1,26 @@ +# Art source: https://textart.io/art/kF4RP1GLcmBNgF2zVV3_JQeF/lord-of-the-rings-eye-of-the-sauron +sauron_eye = """ + + Three::rings + for:::the::Elven-Kings + under:the:sky,:Seven:for:the + Dwarf-Lords::in::their::halls:of + stone,:Nine for:Mortal + :::Men::: ________ doomed::to + die.:One _,-'...:... `-. for:::the + ::Dark:: ,- .:::::::::::. `. Lord::on + his:dark ,' .:::::zzz:::::. `. :throne: + In:::the/ ::::dMMMMMb:::: \ Land::of + :Mordor:\ ::::dMMmgJP:::: / :where:: + ::the::: '. '::::YMMMP::::' ,' Shadows: + lie.::One `. ``:::::::::'' ,' Ring::to + ::rule:: `-._```:'''_,-' ::them:: + all,::One `-----' ring::to + ::find::: them,:One + Ring:::::to bring::them + all::and::in:the:darkness:bind + them:In:the:Land:of:Mordor + where:::the::Shadows + :::lie.::: + +""" diff --git a/sauron/requirements.txt b/sauron/requirements.txt new file mode 100644 index 000000000..719916d46 --- /dev/null +++ b/sauron/requirements.txt @@ -0,0 +1,2 @@ +pyln-client>=0.7.3 +requests>=2.0.0 diff --git a/sauron/sauron.py b/sauron/sauron.py new file mode 100755 index 000000000..087b25c9e --- /dev/null +++ b/sauron/sauron.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +import json +import requests + +from art import sauron_eye +from pyln.client import Plugin + + +plugin = Plugin() + + +@plugin.init() +def init(plugin, options, configuration, **kwargs): + plugin.api_endpoint = options.get("sauron-api-endpoint") + if not plugin.api_endpoint: + raise Exception("You need to specify the sauron-api-endpoint option.") + + 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", + "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206": + "regtest" + } + + genesis_req = requests.get(blockhash_url) + blockcount_req = requests.get(blockcount_url) + assert genesis_req.status_code == 200 and blockcount_req.status_code == 200 + if genesis_req.text not in chains.keys(): + raise Exception("Unsupported network") + + # We wouldn't be able to hit it if its bitcoind wasn't synced, so + # ibd = false and headercount = blockcount + return { + "chain": chains[genesis_req.text], + "blockcount": blockcount_req.text, + "headercount": blockcount_req.text, + "ibd": False, + } + + +@plugin.method("getrawblockbyheight") +def getrawblock(plugin, height, **kwargs): + blockhash_url = "{}/block-height/{}".format(plugin.api_endpoint, height) + + blockhash_req = requests.get(blockhash_url) + # FIXME: Esplora doesn't serve raw blocks ! + # https://github.com/Blockstream/esplora/issues/171 + block_url = "https://blockchain.info/block/{}?format=hex" + block_req = requests.get(block_url.format(blockhash_req.text)) + if blockhash_req.status_code != 200 or block_req.status_code != 200: + return { + "blockhash": None, + "block": None, + } + + return { + "blockhash": blockhash_req.text, + "block": block_req.text, + } + + +@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 { + "success": False, + "errmsg": sendtx_req.text, + } + + return { + "success": True, + "errmsg": "", + } + + +@plugin.method("gettxout") +def gettxout(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) + status_req = requests.get(status_url) + assert gettx_req.status_code == status_req.status_code == 200 + if json.loads(status_req.text)["spent"]: + return { + "amount": None, + "script": None, + } + + txo = json.loads(gettx_req.text)["vout"][vout] + return { + "amount": txo["value"], + "script": txo["scriptpubkey"], + } + + +@plugin.method("getfeerate") +def getfeerate(plugin, blocks, mode, **kwargs): + feerate_url = "{}/fee-estimates".format(plugin.api_endpoint) + # Use an estimation provided by esplora + if blocks == 100: + blocks = 144 + + feerate_req = requests.get(feerate_url) + assert feerate_req.status_code == 200 + feerates = json.loads(feerate_req.text) + # It renders sat/vB, so * 10**8 / 10**3 for BTC/kB + feerate = feerates[str(blocks)] * 10**5 + + # FIXME mode ? + return { + "feerate": int(feerate), + } + + +plugin.add_option( + "sauron-api-endpoint", + "", + "The URL of the esplora instance to hit (including '/api')." +) + +plugin.run()