From 3ac2d446a3906e73c1174791cdbe5c27e9ee0867 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 15 Aug 2019 19:13:55 +0100 Subject: [PATCH] Defines a better bitcoind_simulator. Basic functionallity is already implemented --- tests/bitcoin_rpc_sim.py | 35 --------- tests/bitcoind_sim.py | 162 +++++++++++++++++++++++++++++++++++++++ tests/zmq_pub_sim.py | 25 ------ tests/zmq_publisher.py | 12 +++ 4 files changed, 174 insertions(+), 60 deletions(-) delete mode 100644 tests/bitcoin_rpc_sim.py create mode 100644 tests/bitcoind_sim.py delete mode 100644 tests/zmq_pub_sim.py create mode 100644 tests/zmq_publisher.py diff --git a/tests/bitcoin_rpc_sim.py b/tests/bitcoin_rpc_sim.py deleted file mode 100644 index 7956f11..0000000 --- a/tests/bitcoin_rpc_sim.py +++ /dev/null @@ -1,35 +0,0 @@ -from flask import Flask, request, Response, abort -import json - -app = Flask(__name__) -HOST = 'localhost' -PORT = '18443' - - -@app.route('/', methods=['POST']) -def process_request(): - request_data = request.get_json() - method = request_data.get('method') - - if method == "help": - pass - elif method == "getblockcount": - pass - elif method == "getblock": - pass - elif method == "getblockhash": - pass - elif method == "getrawtransaction": - pass - elif method == "decoderawtransaction": - pass - else: - return abort(500, "Unsupported method") - - response = {"id": 0, "result": 0, "error": None} - - return Response(json.dumps(response), status=200, mimetype='application/json') - - -if __name__ == '__main__': - app.run(host=HOST, port=PORT) \ No newline at end of file diff --git a/tests/bitcoind_sim.py b/tests/bitcoind_sim.py new file mode 100644 index 0000000..e2b8b39 --- /dev/null +++ b/tests/bitcoind_sim.py @@ -0,0 +1,162 @@ +from pisa.conf import FEED_PROTOCOL, FEED_ADDR, FEED_PORT +from flask import Flask, request, Response, abort +from tests.zmq_publisher import ZMQPublisher +from threading import Thread +import binascii +import json +import os +import time + + +app = Flask(__name__) +HOST = 'localhost' +PORT = '18443' + + +@app.route('/', methods=['POST']) +def process_request(): + """ + process_requests simulates the bitcoin-rpc server run by bitcoind. The available commands are limited to the ones + we'll need to use in pisa. The model we will be using is pretty simplified to reduce the complexity of simulating + bitcoind: + + Raw transactions: raw transactions will actually be transaction ids (txids). Pisa will, therefore, receive + encrypted blobs that encrypt ids instead of real transactions. + + decoderawtransaction: querying for the decoding of a raw transaction will return a dictionary with a single + field: "txid", which will match with the txid provided in the request + + sendrawtransaction: sending a rawtransaction will notify our mining simulator to include such transaction in a + subsequent block. + + getrawtransaction: requesting a rawtransaction from a txid will return a dictionary containing a single field: + "confirmations", since rawtransactions are only queried to check whether a transaction has + made it to a block or not. + + getblockcount: the block count will be get from the mining simulator by querying how many blocks have been + emited so far. + + getblock: querying for a block will return a dictionary with a two fields: "tx" representing a list of + transactions, and "height" representing the block height. Both will be got from the mining + simulator. + + getblockhash: a block hash is only queried by pisad on bootstrapping to check the network bitcoind is + running on. It always asks for the genesis block. Since this is ment to be for testing we + will return the testnet3 genesis block hash. + + help: help is only used as a sample command to test if bitcoind is running when bootstrapping + pisad. It will return a 200/OK with no data. + """ + + global sent_transactions + request_data = request.get_json() + method = request_data.get('method') + + response = {"id": 0, "result": 0, "error": None} + + if method == "decoderawtransaction": + txid = get_param(request_data) + + if txid: + response["result"] = {"txid": txid} + + elif method == "sendrawtransaction": + txid = get_param(request_data) + + if txid: + sent_transactions.append(txid) + + # FIXME: If the same transaction is sent twice it should return an error informing that the transaction is + # already known + + elif method == "getrawtransaction": + txid = get_param(request_data) + + if txid: + block = blocks.get(mined_transactions.get(txid)) + + if block: + response["result"] = {"confirmations": block_count - block.get('height')} + + else: + # FIXME: if the transaction cannot be found it should return an error. Check bitcoind + return abort(500) + + elif method == "getblockcount": + response["result"] = block_count + + elif method == "getblock": + blockid = get_param(request_data) + + if blockid: + response["result"] = blocks.get(blockid) + + elif method == "getblockhash": + height = get_param(request_data) + if height == 0: + # testnet3 genesis block hash + response["result"] = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + + else: + return abort(500, "Unsupported method") + + elif method == "help": + pass + + else: + return abort(500, "Unsupported method") + + return Response(json.dumps(response), status=200, mimetype='application/json') + + +def get_param(request_data): + param = None + + params = request_data.get("params") + if isinstance(params, list) and len(params) > 0: + param = params[0] + + return param + + +def load_data(): + pass + + +def simulate_mining(): + global sent_transactions, mined_transactions, blocks, block_count + + while True: + block_hash = binascii.hexlify(os.urandom(32)).decode('utf-8') + coinbase_tx_hash = binascii.hexlify(os.urandom(32)).decode('utf-8') + txs_to_mine = [coinbase_tx_hash] + + if len(sent_transactions) != 0: + # We'll mine up to 100 txs per block + txs_to_mine += sent_transactions[:99] + sent_transactions = sent_transactions[99:] + + # Keep track of the mined transaction (to respond to getrawtransaction) + for tx in txs_to_mine: + mined_transactions[tx] = block_hash + + blocks[block_hash] = {"tx": txs_to_mine, "height": block_count} + mining_simulator.publish_data(binascii.unhexlify(block_hash)) + + block_count += 1 + time.sleep(10) + + +if __name__ == '__main__': + mining_simulator = ZMQPublisher(topic=b'hashblock', feed_protocol=FEED_PROTOCOL, feed_addr=FEED_ADDR, + feed_port=FEED_PORT) + + sent_transactions = [] + mined_transactions = {} + blocks = {} + block_count = 0 + + mining_thread = Thread(target=simulate_mining) + mining_thread.start() + + app.run(host=HOST, port=PORT) diff --git a/tests/zmq_pub_sim.py b/tests/zmq_pub_sim.py deleted file mode 100644 index f897689..0000000 --- a/tests/zmq_pub_sim.py +++ /dev/null @@ -1,25 +0,0 @@ -import zmq -import time -from binascii import unhexlify -from pisa.conf import FEED_PROTOCOL, FEED_ADDR, FEED_PORT - - -class ZMQServerSimulator: - def __init__(self, topic=b'hashblock'): - self.topic = topic - self.context = zmq.Context() - self.socket = self.context.socket(zmq.PUB) - self.socket.bind("%s://%s:%s" % (FEED_PROTOCOL, FEED_ADDR, FEED_PORT)) - - def mine_block(self, block_hash): - self.socket.send_multipart([self.topic, block_hash]) - time.sleep(1) - - -if __name__ == '__main__': - simulator = ZMQServerSimulator() - - block_hash = unhexlify('0000000000000000000873e8ebc0bb1e61e560a773ec2319457b71f1b4030be0') - - while True: - simulator.mine_block(block_hash) diff --git a/tests/zmq_publisher.py b/tests/zmq_publisher.py new file mode 100644 index 0000000..1cd069e --- /dev/null +++ b/tests/zmq_publisher.py @@ -0,0 +1,12 @@ +import zmq + + +class ZMQPublisher: + def __init__(self, topic, feed_protocol, feed_addr, feed_port): + self.topic = topic + self.context = zmq.Context() + self.socket = self.context.socket(zmq.PUB) + self.socket.bind("%s://%s:%s" % (feed_protocol, feed_addr, feed_port)) + + def publish_data(self, data): + self.socket.send_multipart([self.topic, data])