From 314e27175444bcc2dc80f08d945bbb88a65cba1c Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Wed, 14 Aug 2019 13:51:48 +0100 Subject: [PATCH 1/4] Adds basic functionality for both zmq_pub and bitcoin_rpc simulators --- tests/bitcoin_rpc_sim.py | 35 +++++++++++++++++++++++++++++++++++ tests/zmq_pub_sim.py | 25 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 tests/bitcoin_rpc_sim.py create mode 100644 tests/zmq_pub_sim.py diff --git a/tests/bitcoin_rpc_sim.py b/tests/bitcoin_rpc_sim.py new file mode 100644 index 0000000..7956f11 --- /dev/null +++ b/tests/bitcoin_rpc_sim.py @@ -0,0 +1,35 @@ +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/zmq_pub_sim.py b/tests/zmq_pub_sim.py new file mode 100644 index 0000000..f897689 --- /dev/null +++ b/tests/zmq_pub_sim.py @@ -0,0 +1,25 @@ +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) From 3ac2d446a3906e73c1174791cdbe5c27e9ee0867 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Thu, 15 Aug 2019 19:13:55 +0100 Subject: [PATCH 2/4] 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]) From 51bafa323accbcd1268537d0ef6c950b4cb16edc Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Fri, 16 Aug 2019 17:01:16 +0100 Subject: [PATCH 3/4] Improves the simulator and adds some tests The simulator now return the proper error messages when incorrect data is pased on RPC calls. Functionality for forks is missing. --- pisa/tools.py | 5 ++ tests/bitcoin_sim_tests.py | 109 +++++++++++++++++++++++++++++++++++++ tests/bitcoind_sim.py | 97 +++++++++++++++++++++++---------- 3 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 tests/bitcoin_sim_tests.py diff --git a/pisa/tools.py b/pisa/tools.py index 0155509..42ad73f 100644 --- a/pisa/tools.py +++ b/pisa/tools.py @@ -1,3 +1,4 @@ +import re from pisa.utils.authproxy import JSONRPCException from pisa.rpc_errors import RPC_INVALID_ADDRESS_OR_KEY from http.client import HTTPException @@ -55,3 +56,7 @@ def in_correct_network(bitcoin_cli, network): return correct_network + +def check_txid_format(txid): + # TODO: #12-check-txid-regexp + return isinstance(txid, str) and re.search(r'^[0-9A-Fa-f]{64}$', txid) is not None diff --git a/tests/bitcoin_sim_tests.py b/tests/bitcoin_sim_tests.py new file mode 100644 index 0000000..dd77baa --- /dev/null +++ b/tests/bitcoin_sim_tests.py @@ -0,0 +1,109 @@ +import os +import binascii +from pisa.utils.authproxy import AuthServiceProxy, JSONRPCException +from pisa.conf import BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT +from pisa.tools import check_txid_format + + +bitcoin_cli = AuthServiceProxy("http://%s:%s@%s:%d" % (BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT)) + +# Help should always return 0 +assert(bitcoin_cli.help() == 0) + +# getblockhash should return a blockid (which matches the txid format) +block_hash = bitcoin_cli.getblockhash(0) +assert(check_txid_format(block_hash)) + +# Check that the values are within range and of the proper format (all should fail) +values = [-1, 500, None, '', '111', [], 1.1] +print("getblockhash fails ({}):".format(len(values))) + +for v in values: + try: + block_hash = bitcoin_cli.getblockhash(v) + assert False + except JSONRPCException as e: + print('\t{}'.format(e)) + +# getblock should return a list of transactions and the height +block = bitcoin_cli.getblock(block_hash) +assert(isinstance(block.get('tx'), list)) +assert(len(block.get('tx')) != 0) +assert(isinstance(block.get('height'), int)) + +# Some fails +values += ["a"*64, binascii.hexlify(os.urandom(32)).decode()] +print("\ngetblock fails ({}):".format(len(values))) + +for v in values: + try: + block = bitcoin_cli.getblock(v) + assert False + except JSONRPCException as e: + print('\t{}'.format(e)) + +# decoderawtransaction should only return if the given transaction matches a txid format +coinbase_tx = block.get('tx')[0] +tx = bitcoin_cli.decoderawtransaction(coinbase_tx) +assert(isinstance(tx, dict)) +assert(isinstance(tx.get('txid'), str)) +assert(check_txid_format(tx.get('txid'))) + +# Therefore should also work for a random formatted 32-byte hex in our simulation +random_tx = binascii.hexlify(os.urandom(32)).decode() +tx = bitcoin_cli.decoderawtransaction(random_tx) +assert(isinstance(tx, dict)) +assert(isinstance(tx.get('txid'), str)) +assert(check_txid_format(tx.get('txid'))) + +# But it should fail for not proper formatted one +values = [1, None, '', "a"*63, "b"*65, [], binascii.hexlify(os.urandom(31)).hex()] +print("\ndecoderawtransaction fails ({}):".format(len(values))) + +for v in values: + try: + block = bitcoin_cli.decoderawtransaction(v) + assert False + except JSONRPCException as e: + print('\t{}'.format(e)) + +# sendrawtransaction should only allow txids that the simulator has not mined yet +bitcoin_cli.sendrawtransaction(binascii.hexlify(os.urandom(32)).decode()) + +# Any data not matching the txid format or that matches with an already mined transaction should fail +values += [coinbase_tx] + +print("\nsendrawtransaction fails ({}):".format(len(values))) + +for v in values: + try: + block = bitcoin_cli.sendrawtransaction(v) + assert False + except JSONRPCException as e: + print('\t{}'.format(e)) + +# getrawtransaction should work for existing transactions, and fail for non-existing ones +tx = bitcoin_cli.getrawtransaction(coinbase_tx) + +assert(isinstance(tx, dict)) +assert(isinstance(tx.get('confirmations'), int)) + +print("\nsendrawtransaction fails ({}):".format(len(values))) + +for v in values: + try: + block = bitcoin_cli.sendrawtransaction(v) + assert False + except JSONRPCException as e: + print('\t{}'.format(e)) + +# getblockcount should always return a positive integer +bc = bitcoin_cli.getblockcount() +assert (isinstance(bc, int)) +assert (bc >= 0) + +print("\nAll tests passed!") + + + + diff --git a/tests/bitcoind_sim.py b/tests/bitcoind_sim.py index e2b8b39..01ce712 100644 --- a/tests/bitcoind_sim.py +++ b/tests/bitcoind_sim.py @@ -2,6 +2,8 @@ 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 +from pisa.rpc_errors import * +from pisa.tools import check_txid_format import binascii import json import os @@ -41,70 +43,107 @@ def process_request(): 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. + running on. 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 + global mempool request_data = request.get_json() method = request_data.get('method') response = {"id": 0, "result": 0, "error": None} + no_param_err = {"code": RPC_MISC_ERROR, "message": "JSON value is not a {} as expected"} if method == "decoderawtransaction": txid = get_param(request_data) - if txid: - response["result"] = {"txid": txid} + if isinstance(txid, str): + if check_txid_format(txid): + response["result"] = {"txid": txid} + + else: + response["error"] = {"code": RPC_DESERIALIZATION_ERROR, "message": "TX decode failed"} + + else: + response["error"] = no_param_err + response["error"]["message"] = response["error"]["message"].format("string") elif method == "sendrawtransaction": + # TODO: A way of rejecting transactions should be added to test edge cases. txid = get_param(request_data) - if txid: - sent_transactions.append(txid) + if isinstance(txid, str): + if check_txid_format(txid): + if txid not in mempool and txid not in list(mined_transactions.keys()): + mempool.append(txid) - # FIXME: If the same transaction is sent twice it should return an error informing that the transaction is - # already known + else: + response["error"] = {"code": RPC_VERIFY_ALREADY_IN_CHAIN, + "message": "Transaction already in block chain"} + + else: + response["error"] = {"code": RPC_DESERIALIZATION_ERROR, "message": "TX decode failed"} + + else: + response["error"] = no_param_err + response["error"]["message"] = response["error"]["message"].format("string") elif method == "getrawtransaction": txid = get_param(request_data) - if txid: + if isinstance(txid, str): block = blocks.get(mined_transactions.get(txid)) if block: - response["result"] = {"confirmations": block_count - block.get('height')} + response["result"] = {"confirmations": len(blockchain) - block.get('height')} else: - # FIXME: if the transaction cannot be found it should return an error. Check bitcoind - return abort(500) + response["error"] = {'code': RPC_INVALID_ADDRESS_OR_KEY, + 'message': 'No such mempool or blockchain transaction. Use gettransaction for ' + 'wallet transactions.'} + else: + response["error"] = no_param_err + response["error"]["message"] = response["error"]["message"].format("string") elif method == "getblockcount": - response["result"] = block_count + response["result"] = len(blockchain) elif method == "getblock": blockid = get_param(request_data) - if blockid: - response["result"] = blocks.get(blockid) + if isinstance(blockid, str): + block = blocks.get(blockid) + + if block: + response["result"] = block + + else: + response["error"] = {"code": RPC_INVALID_ADDRESS_OR_KEY, "message": "Block not found"} + + else: + response["error"] = no_param_err + response["error"]["message"] = response["error"]["message"].format("string") elif method == "getblockhash": height = get_param(request_data) - if height == 0: - # testnet3 genesis block hash - response["result"] = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + if isinstance(height, int): + if 0 <= height <= len(blockchain): + response["result"] = blockchain[height] + + else: + response["error"] = {"code": RPC_INVALID_PARAMETER, "message": "Block height out of range"} else: - return abort(500, "Unsupported method") + response["error"] = no_param_err + response["error"]["message"] = response["error"]["message"].format("integer") elif method == "help": pass else: - return abort(500, "Unsupported method") + return abort(404, "Method not found") return Response(json.dumps(response), status=200, mimetype='application/json') @@ -124,26 +163,26 @@ def load_data(): def simulate_mining(): - global sent_transactions, mined_transactions, blocks, block_count + global mempool, mined_transactions, blocks, blockchain 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: + if len(mempool) != 0: # We'll mine up to 100 txs per block - txs_to_mine += sent_transactions[:99] - sent_transactions = sent_transactions[99:] + txs_to_mine += mempool[:99] + mempool = mempool[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} + blocks[block_hash] = {"tx": txs_to_mine, "height": len(blockchain)} mining_simulator.publish_data(binascii.unhexlify(block_hash)) + blockchain.append(block_hash) - block_count += 1 time.sleep(10) @@ -151,10 +190,10 @@ if __name__ == '__main__': mining_simulator = ZMQPublisher(topic=b'hashblock', feed_protocol=FEED_PROTOCOL, feed_addr=FEED_ADDR, feed_port=FEED_PORT) - sent_transactions = [] + mempool = [] mined_transactions = {} blocks = {} - block_count = 0 + blockchain = [] mining_thread = Thread(target=simulate_mining) mining_thread.start() From 157456f16471bfd60538ad6e5bcfe09b1b5df5d2 Mon Sep 17 00:00:00 2001 From: Sergi Delgado Segura Date: Mon, 19 Aug 2019 18:18:10 +0100 Subject: [PATCH 4/4] Improves simulator and adds basic appointment test The getblock rpc call was missing the block and previousblock hashes. --- tests/simulator/__init__.py | 0 tests/simulator/appointment_test.py | 52 ++++++++++++++++++++++ tests/{ => simulator}/bitcoin_sim_tests.py | 0 tests/{ => simulator}/bitcoind_sim.py | 20 ++++++--- tests/{ => simulator}/zmq_publisher.py | 0 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 tests/simulator/__init__.py create mode 100644 tests/simulator/appointment_test.py rename tests/{ => simulator}/bitcoin_sim_tests.py (100%) rename tests/{ => simulator}/bitcoind_sim.py (90%) rename tests/{ => simulator}/zmq_publisher.py (100%) diff --git a/tests/simulator/__init__.py b/tests/simulator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/simulator/appointment_test.py b/tests/simulator/appointment_test.py new file mode 100644 index 0000000..2af8496 --- /dev/null +++ b/tests/simulator/appointment_test.py @@ -0,0 +1,52 @@ +import os +import json +import requests +import time +from hashlib import sha256 +from binascii import hexlify, unhexlify +from apps.cli.blob import Blob +from pisa import HOST, PORT +from pisa.utils.authproxy import AuthServiceProxy +from pisa.conf import BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT + +PISA_API = "http://{}:{}".format(HOST, PORT) + + +def generate_dummy_appointment(dispute_txid): + r = requests.get(url=PISA_API+'/get_block_count', timeout=5) + + current_height = r.json().get("block_count") + + dummy_appointment_data = {"tx": hexlify(os.urandom(32)).decode('utf-8'), + "tx_id": dispute_txid, "start_time": current_height + 5, + "end_time": current_height + 10, "dispute_delta": 20} + + cipher = "AES-GCM-128" + hash_function = "SHA256" + + locator = sha256(unhexlify(dummy_appointment_data.get("tx_id"))).hexdigest() + blob = Blob(dummy_appointment_data.get("tx"), cipher, hash_function) + + encrypted_blob = blob.encrypt((dummy_appointment_data.get("tx_id")), debug=False, logging=False) + + appointment = {"locator": locator, "start_time": dummy_appointment_data.get("start_time"), + "end_time": dummy_appointment_data.get("end_time"), + "dispute_delta": dummy_appointment_data.get("dispute_delta"), + "encrypted_blob": encrypted_blob, "cipher": cipher, "hash_function": hash_function} + + return appointment + + +dispute_txid = hexlify(os.urandom(32)).decode('utf-8') +appointment = generate_dummy_appointment(dispute_txid) + +print("Sending appointment (locator: {}) to PISA".format(appointment.get("locator"))) +r = requests.post(url=PISA_API, json=json.dumps(appointment), timeout=5) +print(r, r.reason) + +print("Sleeping 10 sec") +time.sleep(10) +bitcoin_cli = AuthServiceProxy("http://%s:%s@%s:%d" % (BTC_RPC_USER, BTC_RPC_PASSWD, BTC_RPC_HOST, BTC_RPC_PORT)) + +print("Triggering PISA with dispute tx") +bitcoin_cli.sendrawtransaction(dispute_txid) \ No newline at end of file diff --git a/tests/bitcoin_sim_tests.py b/tests/simulator/bitcoin_sim_tests.py similarity index 100% rename from tests/bitcoin_sim_tests.py rename to tests/simulator/bitcoin_sim_tests.py diff --git a/tests/bitcoind_sim.py b/tests/simulator/bitcoind_sim.py similarity index 90% rename from tests/bitcoind_sim.py rename to tests/simulator/bitcoind_sim.py index 01ce712..06cfeae 100644 --- a/tests/bitcoind_sim.py +++ b/tests/simulator/bitcoind_sim.py @@ -1,9 +1,10 @@ from pisa.conf import FEED_PROTOCOL, FEED_ADDR, FEED_PORT from flask import Flask, request, Response, abort -from tests.zmq_publisher import ZMQPublisher +from tests.simulator.zmq_publisher import ZMQPublisher from threading import Thread from pisa.rpc_errors import * from pisa.tools import check_txid_format +import logging import binascii import json import os @@ -38,9 +39,9 @@ def process_request(): 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. + getblock: querying for a block will return a dictionary with a three fields: "tx" representing a list + of transactions, "height" representing the block height and "hash" representing the block + hash. 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. @@ -117,6 +118,7 @@ def process_request(): block = blocks.get(blockid) if block: + block["hash"] = blockid response["result"] = block else: @@ -164,6 +166,7 @@ def load_data(): def simulate_mining(): global mempool, mined_transactions, blocks, blockchain + prev_block_hash = None while True: block_hash = binascii.hexlify(os.urandom(32)).decode('utf-8') @@ -179,9 +182,13 @@ def simulate_mining(): for tx in txs_to_mine: mined_transactions[tx] = block_hash - blocks[block_hash] = {"tx": txs_to_mine, "height": len(blockchain)} + blocks[block_hash] = {"tx": txs_to_mine, "height": len(blockchain), "previousblockhash": prev_block_hash} mining_simulator.publish_data(binascii.unhexlify(block_hash)) blockchain.append(block_hash) + prev_block_hash = block_hash + + print("New block mined: {}".format(block_hash)) + print("\tTransactions: {}".format(txs_to_mine)) time.sleep(10) @@ -198,4 +205,7 @@ if __name__ == '__main__': mining_thread = Thread(target=simulate_mining) mining_thread.start() + # Setting Flask log to ERROR only so it does not mess with out logging + logging.getLogger('werkzeug').setLevel(logging.ERROR) + app.run(host=HOST, port=PORT) diff --git a/tests/zmq_publisher.py b/tests/simulator/zmq_publisher.py similarity index 100% rename from tests/zmq_publisher.py rename to tests/simulator/zmq_publisher.py