summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpcblockchainexplorer/api.py152
-rw-r--r--rpcblockchainexplorer/config.py20
-rw-r--r--rpcblockchainexplorer/flaskapp.py82
-rw-r--r--rpcblockchainexplorer/rpc.py18
-rw-r--r--run.py3
5 files changed, 275 insertions, 0 deletions
diff --git a/rpcblockchainexplorer/api.py b/rpcblockchainexplorer/api.py
new file mode 100644
index 0000000..d7a6d32
--- /dev/null
+++ b/rpcblockchainexplorer/api.py
@@ -0,0 +1,152 @@
+"""
+Autogenerate API endpoints by inspecting python-bitcoinlib's RPC client because
+maximum lazy.
+"""
+
+from copy import copy
+
+# HACK: view_func is weird
+import types
+
+# For using python-bitcoinlib for details about the Bitcoin Core RPC protocol.
+import inspect
+
+# For some even more devious trickery involving partilaly-pre-specified
+# function calls.
+import functools
+
+# Proxy class is used to get information about accepted parameters. Yeah,
+# pretty lame but it works.
+from bitcoin.rpc import Proxy
+
+# because of the interwebs...
+from flask import (
+ Blueprint,
+ request,
+ g,
+)
+
+api = Blueprint("api", __name__, url_prefix="")
+
+ALLOWED_COMMANDS = [
+ # blockchain
+ "getbestblockhash",
+ "getblock",
+ "getblockchaininfo",
+ "getblockcount",
+ "getblockhash",
+ "getchaintips",
+ "getdifficulty",
+ "getmempoolinfo",
+ "gettxout",
+
+ # network
+ "getconnectioncount",
+ "getnettotals",
+ "getnetworkinfo",
+ "getpeerinfo",
+
+ # transactions
+ "getrawtransaction",
+ "decoderawtransaction",
+]
+
+# All API endpoints conform to the following template, which can also be used
+# to construct valid urls.
+API_ENDPOINT_TEMPLATE = "/{command_name}"
+
+def create_api_endpoints(commands=ALLOWED_COMMANDS):
+ """
+ Automatically generate all API endpoints by (ab)using python-bitcoinlib.
+ """
+
+ # Only generate API endpoints for the explicitly whitelisted commands.
+ for command in commands:
+
+ # store any additional arguments here
+ keyword_arguments = {}
+
+ if command in Proxy.__dict__.keys():
+ # get a reference to the function
+ rpcfunction = Proxy.__dict__[command]
+
+ # investigate the function signature
+ argument_spec = inspect.getargspec(rpcfunction)
+
+ # only look at the arguments
+ arguments = argument_spec.args
+
+ # nobody cares about the self
+ arguments.remove("self")
+
+ # "self" is never a default value
+ defaults = argument_spec.defaults
+
+ # Preserve this information for later when constructing the API
+ # endpoints.
+ for (argument_index, argument) in enumerate(arguments):
+ # Perhaps there are not always default values? Who knows.
+ if defaults and len(defaults) > argument_index:
+ some_default = defaults[argument_index]
+ else:
+ some_default = None
+
+ keyword_arguments[argument] = some_default
+
+ # Construct an API endpoint that accepts the computed arguments. Begin
+ # by constructing the API endpoint uri from the template.
+
+ # endpoint is always based on the command name
+ api_uri = API_ENDPOINT_TEMPLATE.format(command_name=command)
+
+ def some_command_endpoint():
+ """
+ Autogenerated API endpoint.
+ """
+
+ # Allow the defaults or user-passed values to be used.
+ params = {}
+
+ # Use request.args to get the user-passed value, otherwise use the
+ # default as defined in python-bitcoinlib.
+ for (argument_name, default_value) in keyword_arguments.items():
+ params[argument_name] = request.args.get(argument_name, default_value)
+
+ # Get a reference to the command, if any.
+ if command in Proxy.__dict__.keys():
+ callable_function = g.bitcoin_rpc_client.__dict__[command]
+ rpc_function = functools.partial(callable_function)
+ else:
+ callable_function = g.bitcoin_rpc_client._call
+ _self = g.bitcoin_rpc_client
+ rpc_function = functools.partial(callable_function, command)
+
+ # Call the RPC service command and pass in all of the given
+ # parameters.
+ results = rpc_function(**params)
+
+ # That's all, folks.
+ return repr(results)
+
+ #temporary_function = types.FunctionType(
+ # # TODO: use func_name in python2
+ # copy(some_command_endpoint.__code__),
+ #
+ # # use func_globals in python2
+ # copy(some_command_endpoint.__globals__),
+ #
+ # name=command,
+ #
+ # # use func_defaults in python2
+ # argdefs=copy(some_command_endpoint.__defaults__),
+ #
+ # # use just "closure" in python2
+ # closure=copy(some_command_endpoint.__closure__),
+ #)
+
+ api.add_url_rule(api_uri, endpoint=command, view_func=some_command_endpoint, methods=["GET"])
+
+# Always create all the endpoints when importing this module. This will attach
+# the endpoints to the blueprint, which can then be attached to flask
+# application instances.
+create_api_endpoints()
diff --git a/rpcblockchainexplorer/config.py b/rpcblockchainexplorer/config.py
new file mode 100644
index 0000000..020f36b
--- /dev/null
+++ b/rpcblockchainexplorer/config.py
@@ -0,0 +1,20 @@
+"""
+Application configuration.
+"""
+
+import os
+
+# use python-bitcoinlib
+import bitcoin
+
+DEFAULT_BITCOIN_MODE = "regtest"
+
+# Set python-bitcoinlib mode to the same as bitcoind's mode, based on
+# environment variable and a default in its absence.
+BITCOIN_MODE = os.environ.get("BITCOIN_MODE", DEFAULT_BITCOIN_MODE)
+
+# python-bitcoinlib has a mode :-(
+bitcoin.SelectParams(BITCOIN_MODE)
+
+# flask application name
+DEFAULT_APP_NAME = "rpcblockchainexplorer"
diff --git a/rpcblockchainexplorer/flaskapp.py b/rpcblockchainexplorer/flaskapp.py
new file mode 100644
index 0000000..afee252
--- /dev/null
+++ b/rpcblockchainexplorer/flaskapp.py
@@ -0,0 +1,82 @@
+"""
+Web application gears.
+"""
+
+# use default logging framework
+import logging
+
+from flask import (
+ # need to make a flask application
+ Flask,
+
+ # request handler thing
+ g,
+)
+
+# and the application must have a name
+from .config import DEFAULT_APP_NAME
+
+# used to create an rpc client on every request
+from .rpc import get_bitcoin_rpc_client
+
+# get the only blueprint that matters in this app
+from .api import api
+
+DEFAULT_BLUEPRINTS = [
+ api,
+]
+
+def create_app(config=None, app_name=DEFAULT_APP_NAME, blueprints=DEFAULT_BLUEPRINTS):
+ """
+ Construct a flask application object.
+ """
+
+ app = Flask(app_name)
+
+ configure_app(app, config)
+ configure_logging(app)
+ configure_hook(app)
+ configure_blueprints(app, blueprints)
+
+ return app
+
+def configure_app(app, config=None):
+ """
+ Really simple configuration for flask applications.
+ """
+
+ if config:
+ app.config.from_object(config)
+ elif not app.config.get("testing"):
+ app.debug = True
+
+def configure_logging(app):
+ """
+ Use python logging.
+ """
+
+ # no reason to hide messages yet
+ app.logger.setLevel(logging.DEBUG)
+
+def configure_hook(app):
+ """
+ Setup the before_request hook.
+ """
+
+ # http://flask.pocoo.org/docs/0.10/api/#flask.Blueprint.before_request
+ @app.before_request
+ def before_request():
+ """
+ Make a new bitcoin RPC client instance for every request.
+ """
+
+ # store client on request context object
+ g.bitcoin_rpc_client = get_bitcoin_rpc_client()
+
+def configure_blueprints(app, blueprints):
+ """
+ Setup flask blueprints on the flask application.
+ """
+
+ for blueprint in blueprints:
+ app.register_blueprint(blueprint)
diff --git a/rpcblockchainexplorer/rpc.py b/rpcblockchainexplorer/rpc.py
new file mode 100644
index 0000000..75a33e9
--- /dev/null
+++ b/rpcblockchainexplorer/rpc.py
@@ -0,0 +1,18 @@
+"""
+RPC connection details.
+"""
+
+# use python-bitcoinlib for RPC
+import bitcoin
+
+from .config import BITCOIN_MODE
+
+def get_bitcoin_rpc_client():
+ """
+ Create a new RPC connection to bitcoind.
+ """
+
+ # use local bitcoind config from ~/.bitcoin/bitcoin.conf
+ bitcoin_rpc_client = bitcoin.rpc.Proxy()
+
+ return bitcoin_rpc_client
diff --git a/run.py b/run.py
new file mode 100644
index 0000000..2d9c07e
--- /dev/null
+++ b/run.py
@@ -0,0 +1,3 @@
+from rpcblockchainexplorer.flaskapp import create_app
+app = create_app()
+app.run(debug=True)