From 39a9a92bdb434fe0e7affa30a9d119b218e12843 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Date: Fri, 29 Mar 2019 18:54:31 +0000 Subject: [PATCH] pisa-btc initial commit Contains basic structure, zmq so subscribe to blockid messages, api and so on. Still WIP --- .gitignore | 18 +++++++++++ pisa-btc/apps/__init__.py | 2 ++ pisa-btc/apps/messages.py | 1 + pisa-btc/apps/pisa-cli.py | 53 +++++++++++++++++++++++++++++++++ pisa-btc/pisa/__init__.py | 2 ++ pisa-btc/pisa/api.py | 34 +++++++++++++++++++++ pisa-btc/pisa/pisad.py | 25 ++++++++++++++++ pisa-btc/pisa/shared.py | 8 +++++ pisa-btc/pisa/tx_watcher.py | 23 ++++++++++++++ pisa-btc/pisa/zmq_subscriber.py | 37 +++++++++++++++++++++++ pisa-btc/sample_conf.py | 11 +++++++ 11 files changed, 214 insertions(+) create mode 100644 .gitignore create mode 100644 pisa-btc/apps/__init__.py create mode 100644 pisa-btc/apps/messages.py create mode 100644 pisa-btc/apps/pisa-cli.py create mode 100644 pisa-btc/pisa/__init__.py create mode 100644 pisa-btc/pisa/api.py create mode 100644 pisa-btc/pisa/pisad.py create mode 100644 pisa-btc/pisa/shared.py create mode 100644 pisa-btc/pisa/tx_watcher.py create mode 100644 pisa-btc/pisa/zmq_subscriber.py create mode 100644 pisa-btc/sample_conf.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..493f51b --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*~ +\#*\# +.\#* +build/ +dist/ +node_modules/ +.vscode/ +*.log +.nyc_output +logs/ + +.DS_Store +.idea +conf.py +bitcoin.conf +__pycache__/* +.pending* + diff --git a/pisa-btc/apps/__init__.py b/pisa-btc/apps/__init__.py new file mode 100644 index 0000000..41def5a --- /dev/null +++ b/pisa-btc/apps/__init__.py @@ -0,0 +1,2 @@ +PISA_API_SERVER = 'localhost' +PISA_API_PORT = 2222 diff --git a/pisa-btc/apps/messages.py b/pisa-btc/apps/messages.py new file mode 100644 index 0000000..39c83d9 --- /dev/null +++ b/pisa-btc/apps/messages.py @@ -0,0 +1 @@ +wrong_txid = "You should provide the 16 MSB (in base58 hex) of the txid you'd like to be monitored." \ No newline at end of file diff --git a/pisa-btc/apps/pisa-cli.py b/pisa-btc/apps/pisa-cli.py new file mode 100644 index 0000000..0782c8b --- /dev/null +++ b/pisa-btc/apps/pisa-cli.py @@ -0,0 +1,53 @@ +from multiprocessing.connection import Client +from getopt import getopt +from sys import argv +from apps import PISA_API_SERVER, PISA_API_PORT +import apps.messages as msg +from base58 import b58decode + + +commands = ['register_tx'] + + +def check_txid_format(txid): + if len(txid) != 32: + raise Exception("txid does not matches the expected size (16-byte / 32 hex chars). " + msg.wrong_txid) + try: + b58decode(txid) + except ValueError: + raise Exception("The provided txid is not in base58. " + msg.wrong_txid) + + +def show_usage(): + print("usage: python pisa-cli.py argument [additional_arguments]." + "\nArguments:" + "\nregister_tx half_txid: \tregisters a txid to be monitored by PISA using the 16 MSB of the txid (in hex)." + "\nhelp: \t\tshows this message.") + + +if __name__ == '__main__': + opts, args = getopt(argv[1:], '', commands) + + # Get args + if len(args) > 0: + command = args[0] + else: + raise Exception("Argument missing. Use help for usage information.") + + if command in commands: + + if command == 'register_tx': + if len(args) != 2: + raise Exception("txid missing. " + msg.wrong_txid) + + arg = args[1] + check_txid_format(arg) + + conn = Client((PISA_API_SERVER, PISA_API_PORT)) + + # Argv could be undefined, but we only have one command for now so safe + conn.send((command, arg)) + + else: + show_usage() + diff --git a/pisa-btc/pisa/__init__.py b/pisa-btc/pisa/__init__.py new file mode 100644 index 0000000..46e14d4 --- /dev/null +++ b/pisa-btc/pisa/__init__.py @@ -0,0 +1,2 @@ +HOST = 'localhost' +PORT = 2222 \ No newline at end of file diff --git a/pisa-btc/pisa/api.py b/pisa-btc/pisa/api.py new file mode 100644 index 0000000..1b4da59 --- /dev/null +++ b/pisa-btc/pisa/api.py @@ -0,0 +1,34 @@ +import threading +from multiprocessing.connection import Listener +from pisa import * + + +def manage_api(debug, host=HOST, port=PORT): + listener = Listener((host, port)) + while True: + conn = listener.accept() + + if debug: + print('Connection accepted from', listener.last_accepted) + + # Maintain metadata up to date. + t_serve = threading.Thread(target=serve_data, args=[debug, conn, listener.last_accepted]) + t_serve.start() + + +def serve_data(debug, conn, remote_addr): + while not conn.closed: + try: + msg = conn.recv() + + if type(msg) == tuple: + if len(msg) is 2: + command, arg = msg + + print(command, arg) + + except (IOError, EOFError): + if debug: + print('Disconnecting from', remote_addr) + + conn.close() diff --git a/pisa-btc/pisa/pisad.py b/pisa-btc/pisa/pisad.py new file mode 100644 index 0000000..2e57e19 --- /dev/null +++ b/pisa-btc/pisa/pisad.py @@ -0,0 +1,25 @@ +from getopt import getopt +from sys import argv +from threading import Thread +from pisa import shared +from pisa.zmq_subscriber import run_subscribe +from pisa.tx_watcher import watch_txs +from pisa.api import manage_api + + +if __name__ == '__main__': + debug = False + opts, _ = getopt(argv[1:], 'd', ['debug']) + for opt, arg in opts: + if opt in ['-d', '--debug']: + debug = True + + shared.init() + + zmq_thread = Thread(target=run_subscribe, args=[debug]) + tx_watcher_thread = Thread(target=watch_txs, args=[debug]) + api_thread = Thread(target=manage_api, args=[debug]) + + zmq_thread.start() + tx_watcher_thread.start() + api_thread.start() diff --git a/pisa-btc/pisa/shared.py b/pisa-btc/pisa/shared.py new file mode 100644 index 0000000..191ce8a --- /dev/null +++ b/pisa-btc/pisa/shared.py @@ -0,0 +1,8 @@ +from queue import Queue + + +def init(): + global block_queue, registered_txs + + block_queue = Queue() + registered_txs = dict() diff --git a/pisa-btc/pisa/tx_watcher.py b/pisa-btc/pisa/tx_watcher.py new file mode 100644 index 0000000..ed55d6a --- /dev/null +++ b/pisa-btc/pisa/tx_watcher.py @@ -0,0 +1,23 @@ +from pisa import shared +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from conf import BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT + + +def watch_txs(debug): + bitcoin_cli = AuthServiceProxy("http://%s:%s@%s:%d" % (BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT)) + + while True: + block_hash = shared.block_queue.get() + + try: + block = bitcoin_cli.getblock(block_hash) + + prev_tx_id = block.get('previousblockhash') + txs = block.get('tx') + + if debug: + # Log shit + print(prev_tx_id, txs) + + except JSONRPCException as e: + print(e) diff --git a/pisa-btc/pisa/zmq_subscriber.py b/pisa-btc/pisa/zmq_subscriber.py new file mode 100644 index 0000000..c572f3f --- /dev/null +++ b/pisa-btc/pisa/zmq_subscriber.py @@ -0,0 +1,37 @@ +""" +Adapted from https://github.com/bitcoin/bitcoin/blob/master/contrib/zmq/zmq_sub.py +""" + +import binascii +import zmq +import conf +from pisa import shared + + +class ZMQHandler: + def __init__(self): + self.zmqContext = zmq.Context() + + self.zmqSubSocket = self.zmqContext.socket(zmq.SUB) + self.zmqSubSocket.setsockopt(zmq.RCVHWM, 0) + self.zmqSubSocket.setsockopt_string(zmq.SUBSCRIBE, "hashblock") + + self.zmqSubSocket.connect("%s://%s:%s" % (conf.FEED_PROTOCOL, conf.FEED_ADDR, conf.FEED_PORT)) + + def handle(self, debug): + msg = self.zmqSubSocket.recv_multipart() + topic = msg[0] + body = msg[1] + + if topic == b"hashblock": + block_hash = binascii.hexlify(body).decode('UTF-8') + shared.block_queue.put(block_hash) + + if debug: + # Log shit + pass + + +def run_subscribe(debug): + daemon = ZMQHandler() + daemon.handle(debug) diff --git a/pisa-btc/sample_conf.py b/pisa-btc/sample_conf.py new file mode 100644 index 0000000..46ec5d3 --- /dev/null +++ b/pisa-btc/sample_conf.py @@ -0,0 +1,11 @@ +# bitcoind +BTC_RPC_USER = None +BTC_RPC_PASSWD = None +BTC_RPC_HOST = None +BTC_RPC_PORT = None + + +# ZMQ +FEED_PROTOCOL = None +FEED_ADDR = None +FEED_PORT = None