Browse Source
This is a simple reverse proxy that `bitcoin-cli` can talk to when invoked by `lightningd`. It allows us to trace `bitcoin-cli` calls, and intercept calls to mock the replies, better than the current bash-script based method.json-streaming
5 changed files with 91 additions and 7 deletions
@ -0,0 +1,81 @@ |
|||||
|
""" A bitcoind proxy that allows instrumentation and canned responses |
||||
|
""" |
||||
|
from flask import Flask, request |
||||
|
from bitcoin.rpc import JSONRPCError |
||||
|
from bitcoin.rpc import RawProxy as BitcoinProxy |
||||
|
from utils import BitcoinD |
||||
|
from cheroot.wsgi import Server |
||||
|
from cheroot.wsgi import PathInfoDispatcher |
||||
|
|
||||
|
import decimal |
||||
|
import json |
||||
|
import logging |
||||
|
import os |
||||
|
import threading |
||||
|
|
||||
|
|
||||
|
class DecimalEncoder(json.JSONEncoder): |
||||
|
"""By default json.dumps does not handle Decimals correctly, so we override it's handling |
||||
|
""" |
||||
|
def default(self, o): |
||||
|
if isinstance(o, decimal.Decimal): |
||||
|
return str(o) |
||||
|
return super(DecimalEncoder, self).default(o) |
||||
|
|
||||
|
|
||||
|
class ProxiedBitcoinD(BitcoinD): |
||||
|
def __init__(self, bitcoin_dir, proxyport=0): |
||||
|
BitcoinD.__init__(self, bitcoin_dir, rpcport=None) |
||||
|
self.app = Flask("BitcoindProxy") |
||||
|
self.app.add_url_rule("/", "API entrypoint", self.proxy, methods=['POST']) |
||||
|
self.proxyport = proxyport |
||||
|
|
||||
|
def proxy(self): |
||||
|
r = json.loads(request.data.decode('ASCII')) |
||||
|
conf_file = os.path.join(self.bitcoin_dir, 'bitcoin.conf') |
||||
|
brpc = BitcoinProxy(btc_conf_file=conf_file) |
||||
|
|
||||
|
try: |
||||
|
reply = { |
||||
|
"result": brpc._call(r['method'], *r['params']), |
||||
|
"error": None, |
||||
|
"id": r['id'] |
||||
|
} |
||||
|
except JSONRPCError as e: |
||||
|
reply = { |
||||
|
"error": e.error, |
||||
|
"id": r['id'] |
||||
|
} |
||||
|
return json.dumps(reply, cls=DecimalEncoder) |
||||
|
|
||||
|
def start(self): |
||||
|
d = PathInfoDispatcher({'/': self.app}) |
||||
|
self.server = Server(('0.0.0.0', self.proxyport), d) |
||||
|
self.proxy_thread = threading.Thread(target=self.server.start) |
||||
|
self.proxy_thread.daemon = True |
||||
|
self.proxy_thread.start() |
||||
|
BitcoinD.start(self) |
||||
|
|
||||
|
# Now that bitcoind is running on the real rpcport, let's tell all |
||||
|
# future callers to talk to the proxyport. We use the bind_addr as a |
||||
|
# signal that the port is bound and accepting connections. |
||||
|
while self.server.bind_addr[1] == 0: |
||||
|
pass |
||||
|
self.proxiedport = self.rpcport |
||||
|
self.rpcport = self.server.bind_addr[1] |
||||
|
logging.debug("bitcoind reverse proxy listening on {}, forwarding to {}".format( |
||||
|
self.rpcport, self.proxiedport |
||||
|
)) |
||||
|
|
||||
|
def stop(self): |
||||
|
BitcoinD.stop(self) |
||||
|
self.server.stop() |
||||
|
self.proxy_thread.join() |
||||
|
|
||||
|
|
||||
|
# The main entrypoint is mainly used to test the proxy. It is not used during |
||||
|
# lightningd testing. |
||||
|
if __name__ == "__main__": |
||||
|
p = ProxiedBitcoinD(bitcoin_dir='/tmp/bitcoind-test/', proxyport=5000) |
||||
|
p.start() |
||||
|
p.proxy_thread.join() |
Loading…
Reference in new issue