From 05c24172054b61de0dd1a4840dfeddba1c76e5dc Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Wed, 16 Sep 2020 14:03:32 +0200 Subject: [PATCH 1/2] Replaced AuthServiceProxy with bitcoin.rpc.Proxy from python-bitcoinlib. --- requirements.txt | 1 + teos/block_processor.py | 73 ++++++++++++-------- teos/carrier.py | 115 +++++++++++++++++++------------ teos/teosd.py | 4 ++ teos/tools.py | 11 +-- test/teos/conftest.py | 94 +++++++++++++++---------- test/teos/unit/conftest.py | 20 +++--- test/teos/unit/test_api.py | 11 +-- test/teos/unit/test_carrier.py | 20 ++++-- test/teos/unit/test_responder.py | 17 +++-- test/teos/unit/test_watcher.py | 13 ++-- 11 files changed, 232 insertions(+), 147 deletions(-) diff --git a/requirements.txt b/requirements.txt index f790e750..d0f1e87c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ structlog python-daemon waitress gunicorn; platform_system != "Windows" +python-bitcoinlib diff --git a/teos/block_processor.py b/teos/block_processor.py index e4a10188..b9e086f7 100644 --- a/teos/block_processor.py +++ b/teos/block_processor.py @@ -1,8 +1,10 @@ from teos.logger import get_logger +import teos.utils.rpc_errors as rpc_errors from common.exceptions import BasicException -from teos.tools import bitcoin_cli -from teos.utils.auth_proxy import JSONRPCException +import bitcoin.rpc +from bitcoin.rpc import JSONRPCError +from bitcoin.core import b2x, b2lx, lx class InvalidTransactionFormat(BasicException): @@ -26,6 +28,24 @@ def __init__(self, btc_connect_params): self.logger = get_logger(component=BlockProcessor.__name__) self.btc_connect_params = btc_connect_params + def proxy(self): + """ + Returns a new ``http`` connection with ``bitcoind`` using the ``json-rpc`` interface, using + ``btc_connect_params`` for the connectio parameters. + + Returns: + :obj:`Proxy `: An authenticated service proxy to ``bitcoind`` + that can be used to send ``json-rpc`` commands. + """ + + service_url = "http://%s:%s@%s:%d" % ( + self.btc_connect_params.get("BTC_RPC_USER"), + self.btc_connect_params.get("BTC_RPC_PASSWORD"), + self.btc_connect_params.get("BTC_RPC_CONNECT"), + self.btc_connect_params.get("BTC_RPC_PORT"), + ) + return bitcoin.rpc.Proxy(service_url) + def get_block(self, block_hash): """ Gets a block given a block hash by querying ``bitcoind``. @@ -38,15 +58,12 @@ def get_block(self, block_hash): Returns :obj:`None` otherwise. """ - try: - block = bitcoin_cli(self.btc_connect_params).getblock(block_hash) - - except JSONRPCException as e: - block = None + # by using "call" we obtain a dict, rather than a CBlock that we obtain calling .getblock(). + return self.proxy().call("getblock", block_hash) + except JSONRPCError as e: self.logger.error("Couldn't get block from bitcoind", error=e.error) - - return block + return None def get_best_block_hash(self): """ @@ -57,15 +74,11 @@ def get_best_block_hash(self): Returns :obj:`None` otherwise (not even sure this can actually happen). """ - try: - block_hash = bitcoin_cli(self.btc_connect_params).getbestblockhash() - - except JSONRPCException as e: - block_hash = None + return b2lx(self.proxy().getbestblockhash()) + except JSONRPCError as e: self.logger.error("Couldn't get block hash", error=e.error) - - return block_hash + return None def get_block_count(self): """ @@ -78,13 +91,10 @@ def get_block_count(self): """ try: - block_count = bitcoin_cli(self.btc_connect_params).getblockcount() - - except JSONRPCException as e: - block_count = None + return self.proxy().getblockcount() + except JSONRPCError as e: self.logger.error("Couldn't get block count", error=e.error) - - return block_count + return None def decode_raw_transaction(self, raw_tx): """ @@ -99,17 +109,20 @@ def decode_raw_transaction(self, raw_tx): Raises: :obj:`InvalidTransactionFormat`: If the `provided ``raw_tx`` has invalid format. + :obj:`JSONRPCError`: on any other error from the rpc call. """ try: - tx = bitcoin_cli(self.btc_connect_params).decoderawtransaction(raw_tx) - - except JSONRPCException as e: - msg = "Cannot build transaction from decoded data" - self.logger.error(msg, error=e.error) - raise InvalidTransactionFormat(msg) - - return tx + return self.proxy().call("decoderawtransaction", raw_tx) + except JSONRPCError as e: + errno = e.error.get("code") + if errno == rpc_errors.RPC_DESERIALIZATION_ERROR: + msg = "Cannot build transaction from decoded data" + self.logger.error(msg, error=e.error) + raise InvalidTransactionFormat(msg) + else: + self.logger.error(e.error.get("message"), error=e.error) + raise e def get_distance_to_tip(self, target_block_hash): """ diff --git a/teos/carrier.py b/teos/carrier.py index 16eef2b5..96fd7222 100644 --- a/teos/carrier.py +++ b/teos/carrier.py @@ -1,7 +1,10 @@ from teos.logger import get_logger from teos.tools import bitcoin_cli import teos.utils.rpc_errors as rpc_errors -from teos.utils.auth_proxy import JSONRPCException +import bitcoin.rpc +from bitcoin.rpc import JSONRPCError, VerifyRejectedError, VerifyError, VerifyAlreadyInChainError +from bitcoin.core import x, lx, b2lx +from bitcoin.core.serialize import SerializationError, SerializationTruncationError from common.errors import UNKNOWN_JSON_RPC_EXCEPTION, RPC_TX_REORGED_AFTER_BROADCAST # FIXME: This class is not fully covered by unit tests @@ -52,6 +55,25 @@ def __init__(self, btc_connect_params): self.btc_connect_params = btc_connect_params self.issued_receipts = {} + def proxy(self): + """ + Returns a new ``http`` connection with ``bitcoind`` using the ``json-rpc`` interface, using + ``btc_connect_params`` for the connectio parameters. + + Returns: + :obj:`Proxy `: An authenticated service proxy to ``bitcoind`` + that can be used to send ``json-rpc`` commands. + """ + + service_url = "http://%s:%s@%s:%d" % ( + self.btc_connect_params.get("BTC_RPC_USER"), + self.btc_connect_params.get("BTC_RPC_PASSWORD"), + self.btc_connect_params.get("BTC_RPC_CONNECT"), + self.btc_connect_params.get("BTC_RPC_PORT"), + ) + return bitcoin.rpc.Proxy(service_url) + + # NOTCOVERED def send_transaction(self, rawtx, txid): """ @@ -73,43 +95,50 @@ def send_transaction(self, rawtx, txid): try: self.logger.info("Pushing transaction to the network", txid=txid, rawtx=rawtx) - bitcoin_cli(self.btc_connect_params).sendrawtransaction(rawtx) + tx = bitcoin.core.CTransaction.deserialize(x(rawtx)) + self.proxy().sendrawtransaction(tx) receipt = Receipt(delivered=True) - except JSONRPCException as e: + except (SerializationError, SerializationTruncationError) as e: + receipt = Receipt(delivered=False, reason=rpc_errors.RPC_DESERIALIZATION_ERROR) + self.logger.error("Transaction couldn't be broadcasted", error=e) + + # Since we're pushing a raw transaction to the network we can face several rejections + except VerifyRejectedError as e: + # DISCUSS: 37-transaction-rejection + receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_REJECTED) + self.logger.error("Transaction couldn't be broadcasted", error=e) + + except VerifyError as e: + # DISCUSS: 37-transaction-rejection + receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_ERROR) + self.logger.error("Transaction couldn't be broadcasted", error=e) + + except VerifyAlreadyInChainError as e: + self.logger.info("Transaction is already in the blockchain. Getting confirmation count", txid=txid) + + # If the transaction is already in the chain, we get the number of confirmations and watch the tracker + # until the end of the appointment + tx_info = self.get_transaction(txid) + + if tx_info is not None: + confirmations = int(tx_info.get("confirmations")) + receipt = Receipt( + delivered=True, confirmations=confirmations, reason=rpc_errors.RPC_VERIFY_ALREADY_IN_CHAIN + ) + + else: + # There's a really unlikely edge case where a transaction can be reorged between receiving the + # notification and querying the data. Notice that this implies the tx being also kicked off the + # mempool, which again is really unlikely. + receipt = Receipt(delivered=False, reason=RPC_TX_REORGED_AFTER_BROADCAST) + + except JSONRPCError as e: + # Other errors that don't have a class in python-bitcoinlib + errno = e.error.get("code") - # Since we're pushing a raw transaction to the network we can face several rejections - if errno == rpc_errors.RPC_VERIFY_REJECTED: - # DISCUSS: 37-transaction-rejection - receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_REJECTED) - self.logger.error("Transaction couldn't be broadcast", error=e.error) - - elif errno == rpc_errors.RPC_VERIFY_ERROR: - # DISCUSS: 37-transaction-rejection - receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_ERROR) - self.logger.error("Transaction couldn't be broadcast", error=e.error) - - elif errno == rpc_errors.RPC_VERIFY_ALREADY_IN_CHAIN: - self.logger.info("Transaction is already in the blockchain. Getting confirmation count", txid=txid) - - # If the transaction is already in the chain, we get the number of confirmations and watch the tracker - # until the end of the appointment - tx_info = self.get_transaction(txid) - - if tx_info is not None: - confirmations = int(tx_info.get("confirmations")) - receipt = Receipt( - delivered=True, confirmations=confirmations, reason=rpc_errors.RPC_VERIFY_ALREADY_IN_CHAIN - ) - - else: - # There's a really unlikely edge case where a transaction can be reorged between receiving the - # notification and querying the data. Notice that this implies the tx being also kicked off the - # mempool, which again is really unlikely. - receipt = Receipt(delivered=False, reason=RPC_TX_REORGED_AFTER_BROADCAST) - - elif errno == rpc_errors.RPC_DESERIALIZATION_ERROR: + if errno == rpc_errors.RPC_DESERIALIZATION_ERROR: # Adding this here just for completeness. We should never end up here. The Carrier only sends txs # handed by the Responder, who receives them from the Watcher, who checks that the tx can be properly # deserialized @@ -118,7 +147,7 @@ def send_transaction(self, rawtx, txid): else: # If something else happens (unlikely but possible) log it so we can treat it in future releases - self.logger.error("JSONRPCException", method="Carrier.send_transaction", error=e.error) + self.logger.error("JSONRPCError", method="Carrier.send_transaction", error=e.error) receipt = Receipt(delivered=False, reason=UNKNOWN_JSON_RPC_EXCEPTION) self.issued_receipts[txid] = receipt @@ -138,18 +167,18 @@ def get_transaction(self, txid): """ try: - tx_info = bitcoin_cli(self.btc_connect_params).getrawtransaction(txid, 1) - return tx_info + return self.proxy().getrawtransaction(lx(txid), verbose=True) - except JSONRPCException as e: + except IndexError as e: # While it's quite unlikely, the transaction that was already in the blockchain could have been # reorged while we were querying bitcoind to get the confirmation count. In that case we just restart # the tracker - if e.error.get("code") == rpc_errors.RPC_INVALID_ADDRESS_OR_KEY: - self.logger.info("Transaction not found in mempool nor blockchain", txid=txid) - else: - # If something else happens (unlikely but possible) log it so we can treat it in future releases - self.logger.error("JSONRPCException", method="Carrier.get_transaction", error=e.error) + self.logger.info("Transaction not found in mempool nor blockchain", txid=txid) + return None + + except JSONRPCError as e: + # If something else happens (unlikely but possible) log it so we can treat it in future releases + self.logger.error("JSONRPCError", method="Carrier.get_transaction", error=e.error) return None diff --git a/teos/teosd.py b/teos/teosd.py index 19fbffe2..41a514d2 100755 --- a/teos/teosd.py +++ b/teos/teosd.py @@ -8,6 +8,8 @@ from getopt import getopt, GetoptError from signal import signal, SIGINT, SIGQUIT, SIGTERM +import bitcoin + from common.config_loader import ConfigLoader from common.cryptographer import Cryptographer from common.tools import setup_data_folder @@ -469,6 +471,8 @@ def run(): config = get_config(command_line_conf, data_dir) + bitcoin.SelectParams(config.get("BTC_NETWORK")) + if config.get("DAEMON"): print("Starting TEOS") with daemon.DaemonContext(): diff --git a/teos/tools.py b/teos/tools.py index 81eebac1..03d51dea 100644 --- a/teos/tools.py +++ b/teos/tools.py @@ -1,7 +1,8 @@ from socket import timeout from http.client import HTTPException -from teos.utils.auth_proxy import AuthServiceProxy, JSONRPCException +import bitcoin.rpc +from bitcoin.rpc import JSONRPCError from common.constants import MAINNET_RPC_PORT, TESTNET_RPC_PORT, REGTEST_RPC_PORT @@ -20,11 +21,11 @@ def bitcoin_cli(btc_connect_params): (``rpc user, rpc password, host and port``) Returns: - :obj:`AuthServiceProxy `: An authenticated service proxy to ``bitcoind`` + :obj:`Proxy `: An authenticated service proxy to ``bitcoind`` that can be used to send ``json-rpc`` commands. """ - return AuthServiceProxy( + return bitcoin.rpc.Proxy( "http://%s:%s@%s:%d" % ( btc_connect_params.get("BTC_RPC_USER"), @@ -50,8 +51,8 @@ def can_connect_to_bitcoind(btc_connect_params): can_connect = True try: - bitcoin_cli(btc_connect_params).help() - except (timeout, ConnectionRefusedError, JSONRPCException, HTTPException, OSError): + bitcoin_cli(btc_connect_params).getbestblockhash() + except (timeout, ConnectionRefusedError, JSONRPCError, HTTPException, OSError) as e: can_connect = False return can_connect diff --git a/test/teos/conftest.py b/test/teos/conftest.py index 90e47080..8add0659 100644 --- a/test/teos/conftest.py +++ b/test/teos/conftest.py @@ -8,8 +8,12 @@ from decimal import Decimal, getcontext from teos.teosd import get_config -from teos.utils.auth_proxy import AuthServiceProxy, JSONRPCException +import bitcoin +import bitcoin.rpc +from bitcoin.core import b2lx, b2x, x + +bitcoin.SelectParams("regtest") getcontext().prec = 10 utxos = list() @@ -19,7 +23,7 @@ cmd_args = {"BTC_NETWORK": "regtest"} config = get_config(cmd_args, ".teos") -bitcoin_cli = AuthServiceProxy( +bitcoin_cli = bitcoin.rpc.Proxy( "http://%s:%s@%s:%d" % ( config.get("BTC_RPC_USER"), @@ -37,20 +41,22 @@ def prng_seed(): @pytest.fixture(scope="session") def run_bitcoind(dirname=".test_bitcoin"): - # Run bitcoind in a separate folder - makedirs(dirname, exist_ok=True) + try: + # Run bitcoind in a separate folder + makedirs(dirname, exist_ok=True) - bitcoind = os.getenv("BITCOIND", "bitcoind") + bitcoind = os.getenv("BITCOIND", "bitcoind") - copy(os.path.join(os.path.dirname(__file__), "bitcoin.conf"), dirname) - subprocess.Popen([bitcoind, f"--datadir={dirname}"]) + copy(os.path.join(os.path.dirname(__file__), "bitcoin.conf"), dirname) + subprocess.Popen([bitcoind, f"--datadir={dirname}"]) - # Generate some initial blocks - setup_node() - yield + # Generate some initial blocks + setup_node() + yield - bitcoin_cli.stop() - rmtree(dirname) + finally: + bitcoin_cli.call("stop") + rmtree(dirname) def setup_node(): @@ -60,7 +66,7 @@ def setup_node(): while True: # FIXME: Not creating a new bitcoin_cli here creates one of those Request-Sent errors I don't know how to fix # Not a big deal, but it would be nicer not having to. - bitcoin_cli = AuthServiceProxy( + bitcoin_cli = bitcoin.rpc.Proxy( "http://%s:%s@%s:%d" % ( config.get("BTC_RPC_USER"), @@ -73,11 +79,10 @@ def setup_node(): btc_addr = bitcoin_cli.getnewaddress() break - except ConnectionError: + except (ConnectionError, bitcoin.rpc.InWarmupError): sleep(1) - except JSONRPCException as e: - if "Loading wallet..." in str(e): - sleep(1) + + print("Address:", btc_addr) # Mine enough blocks so coinbases are mature and we have enough funds to run everything bitcoin_cli.generatetoaddress(105, btc_addr) @@ -86,17 +91,20 @@ def setup_node(): def create_initial_transactions(fee=Decimal("0.00005")): utxos = bitcoin_cli.listunspent() - btc_addresses = [bitcoin_cli.getnewaddress() for _ in range(100)] + btc_addresses = [str(bitcoin_cli.getnewaddress()) for _ in range(100)] for utxo in utxos: # Create 100 outputs per utxo and mine a new block. - tx_ins = {"txid": utxo.get("txid"), "vout": utxo.get("vout")} + outpoint = utxo.get("outpoint") + tx_ins = {"txid": b2lx(outpoint.hash), "vout": outpoint.n} - tx_outs = {btc_address: utxo.get("amount") / 100 for btc_address in btc_addresses[:-1]} - tx_outs[btc_addresses[-1]] = (utxo.get("amount") / 100) - fee + amount = Decimal(utxo.get("amount") / 100_000_000) + tx_outs = {btc_address: str(amount / 100) for btc_address in btc_addresses[:-1]} + tx_outs[btc_addresses[-1]] = str((amount / 100) - fee) - raw_tx = bitcoin_cli.createrawtransaction([tx_ins], tx_outs) - signed_tx = bitcoin_cli.signrawtransactionwithwallet(raw_tx) - bitcoin_cli.sendrawtransaction(signed_tx.get("hex")) + raw_tx_hex = bitcoin_cli.call("createrawtransaction", [tx_ins], tx_outs) + tx = bitcoin.rpc.CTransaction.deserialize(bytes.fromhex(raw_tx_hex)) + signed_tx = bitcoin_cli.signrawtransactionwithwallet(tx) + bitcoin_cli.sendrawtransaction(signed_tx.get("tx")) bitcoin_cli.generatetoaddress(1, btc_addr) @@ -123,7 +131,7 @@ def get_utxo(): def generate_blocks(n): - return bitcoin_cli.generatetoaddress(n, btc_addr) + return list(map(b2lx, bitcoin_cli.generatetoaddress(n, btc_addr))) def generate_blocks_with_delay(n, delay=0.2): @@ -135,13 +143,19 @@ def generate_blocks_with_delay(n, delay=0.2): return block_ids +def makeCTransaction(rawtx): + return bitcoin.core.CTransaction.deserialize(x(rawtx)) + + def generate_block_with_transactions(commitment_txs): # If a list of transactions is passed, send them all if isinstance(commitment_txs, list): for tx in commitment_txs: - bitcoin_cli.sendrawtransaction(tx) + bitcoin_cli.sendrawtransaction(makeCTransaction(tx)) elif isinstance(commitment_txs, str): - bitcoin_cli.sendrawtransaction(commitment_txs) + bitcoin_cli.sendrawtransaction(makeCTransaction(commitment_txs)) + else: + raise TypeError(f"Expected a string or a list of strings, not a {type(commitment_txs).__name__}.") return generate_blocks(1) @@ -154,16 +168,19 @@ def create_commitment_tx(utxo=None, destination=None, fee=Decimal("0.00001")): if destination is None: destination = utxo.get("address") - commitment_tx_ins = {"txid": utxo.get("txid"), "vout": utxo.get("vout")} - commitment_tx_outs = {destination: utxo.get("amount") - fee} + outpoint = utxo.get("outpoint") + amount = Decimal(utxo.get("amount") / 100_000_000) + commitment_tx_ins = {"txid": b2lx(outpoint.hash), "vout": outpoint.n} + commitment_tx_outs = {str(destination): str(amount - fee)} - raw_commitment_tx = bitcoin_cli.createrawtransaction([commitment_tx_ins], commitment_tx_outs) - signed_commitment_tx = bitcoin_cli.signrawtransactionwithwallet(raw_commitment_tx) + raw_commitment_tx = bitcoin_cli.call("createrawtransaction", [commitment_tx_ins], commitment_tx_outs) + tx = bitcoin.rpc.CTransaction.deserialize(bytes.fromhex(raw_commitment_tx)) + signed_commitment_tx = bitcoin_cli.signrawtransactionwithwallet(tx) if not signed_commitment_tx.get("complete"): raise ValueError("Couldn't sign transaction. {}".format(signed_commitment_tx)) - return signed_commitment_tx.get("hex") + return signed_commitment_tx.get("tx") def create_penalty_tx(decoded_commitment_tx, destination=None, fee=Decimal("0.00001")): @@ -172,27 +189,28 @@ def create_penalty_tx(decoded_commitment_tx, destination=None, fee=Decimal("0.00 destination = decoded_commitment_tx.get("vout")[0].get("scriptPubKey").get("addresses")[0] penalty_tx_ins = {"txid": decoded_commitment_tx.get("txid"), "vout": 0} - penalty_tx_outs = {destination: decoded_commitment_tx.get("vout")[0].get("value") - fee} + penalty_tx_outs = {str(destination): str(decoded_commitment_tx.get("vout")[0].get("value") - fee)} orphan_info = { "txid": decoded_commitment_tx.get("txid"), "scriptPubKey": decoded_commitment_tx.get("vout")[0].get("scriptPubKey").get("hex"), "vout": 0, - "amount": decoded_commitment_tx.get("vout")[0].get("value"), + "amount": str(decoded_commitment_tx.get("vout")[0].get("value")), } - raw_penalty_tx = bitcoin_cli.createrawtransaction([penalty_tx_ins], penalty_tx_outs) - signed_penalty_tx = bitcoin_cli.signrawtransactionwithwallet(raw_penalty_tx, [orphan_info]) + raw_penalty_tx = bitcoin_cli.call("createrawtransaction", [penalty_tx_ins], penalty_tx_outs) + tx = bitcoin.rpc.CTransaction.deserialize(bytes.fromhex(raw_penalty_tx)) + signed_penalty_tx = bitcoin_cli.signrawtransactionwithwallet(tx, [orphan_info]) if not signed_penalty_tx.get("complete"): raise ValueError("Couldn't sign orphan transaction. {}".format(signed_penalty_tx)) - return signed_penalty_tx.get("hex") + return signed_penalty_tx.get("tx") def create_txs(): signed_commitment_tx = create_commitment_tx() - decoded_commitment_tx = bitcoin_cli.decoderawtransaction(signed_commitment_tx) + decoded_commitment_tx = bitcoin_cli.call("decoderawtransaction", b2x(signed_commitment_tx.serialize())) signed_penalty_tx = create_penalty_tx(decoded_commitment_tx) diff --git a/test/teos/unit/conftest.py b/test/teos/unit/conftest.py index 4f25e551..987eee17 100644 --- a/test/teos/unit/conftest.py +++ b/test/teos/unit/conftest.py @@ -2,6 +2,8 @@ from shutil import rmtree from coincurve import PrivateKey +from bitcoin.core import b2x, b2lx + from teos.carrier import Carrier from teos.users_dbm import UsersDBM from teos.gatekeeper import Gatekeeper @@ -79,7 +81,7 @@ def generate_keypair(): def fork(block_hash, blocks): - bitcoin_cli.invalidateblock(block_hash) + bitcoin_cli.call("invalidateblock", block_hash) bitcoin_cli.generatetoaddress(blocks, bitcoin_cli.getnewaddress()) @@ -88,18 +90,20 @@ def generate_dummy_appointment(run_bitcoind): def _generate_dummy_appointment(): commitment_tx, commitment_txid, penalty_tx = create_txs() - dummy_appointment_data = {"tx": penalty_tx, "tx_id": commitment_txid, "to_self_delay": 20} + penalty_tx_hex = b2x(penalty_tx.serialize()) + + dummy_appointment_data = {"tx": penalty_tx_hex, "tx_id": commitment_txid, "to_self_delay": 20} appointment_data = { "locator": compute_locator(commitment_txid), "to_self_delay": dummy_appointment_data.get("to_self_delay"), - "encrypted_blob": Cryptographer.encrypt(penalty_tx, commitment_txid), + "encrypted_blob": Cryptographer.encrypt(penalty_tx_hex, commitment_txid), "user_id": get_random_value_hex(16), "user_signature": get_random_value_hex(50), "start_block": 200, } - return ExtendedAppointment.from_dict(appointment_data), commitment_tx + return ExtendedAppointment.from_dict(appointment_data), b2x(commitment_tx.serialize()) return _generate_dummy_appointment @@ -109,15 +113,15 @@ def generate_dummy_tracker(run_bitcoind): def _generate_dummy_tracker(commitment_tx=None): if not commitment_tx: commitment_tx = create_commitment_tx() - decoded_commitment_tx = bitcoin_cli.decoderawtransaction(commitment_tx) + decoded_commitment_tx = bitcoin_cli.call("decoderawtransaction", b2x(commitment_tx.serialize())) penalty_tx = create_penalty_tx(decoded_commitment_tx) locator = decoded_commitment_tx.get("txid")[:LOCATOR_LEN_HEX] tracker_data = dict( locator=locator, - dispute_txid=bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"), - penalty_txid=bitcoin_cli.decoderawtransaction(penalty_tx).get("txid"), - penalty_rawtx=penalty_tx, + dispute_txid=b2lx(commitment_tx.GetTxid()), + penalty_txid=b2lx(penalty_tx.GetTxid()), + penalty_rawtx=b2x(penalty_tx.serialize()), user_id="02" + get_random_value_hex(32), ) diff --git a/test/teos/unit/test_api.py b/test/teos/unit/test_api.py index 02a3f3a5..56a87712 100644 --- a/test/teos/unit/test_api.py +++ b/test/teos/unit/test_api.py @@ -2,6 +2,8 @@ from multiprocessing import Event from shutil import rmtree +from bitcoin.core import b2x + from teos.api import API import common.errors as errors from teos.watcher import Watcher @@ -403,7 +405,7 @@ def test_add_appointment_update_smaller(internal_api, client, appointment, block ) -def test_add_appointment_in_cache_invalid_transaction(internal_api, client, block_processor): +def test_add_appointment_in_cache_invalid_transaction(internal_api, client, block_processor, monkeypatch): internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo( available_slots=1, subscription_expiry=block_processor.get_block_count() + 1 ) @@ -411,9 +413,9 @@ def test_add_appointment_in_cache_invalid_transaction(internal_api, client, bloc # We need to create the appointment manually commitment_tx, commitment_txid, penalty_tx = create_txs() - locator = compute_locator(commitment_tx) - dummy_appointment_data = {"tx": penalty_tx, "tx_id": commitment_txid, "to_self_delay": 20} - encrypted_blob = Cryptographer.encrypt(penalty_tx[::-1], commitment_txid) + locator = compute_locator(commitment_txid) + dummy_appointment_data = {"tx": b2x(penalty_tx.serialize()), "tx_id": commitment_txid, "to_self_delay": 20} + encrypted_blob = Cryptographer.encrypt(b2x(penalty_tx.serialize()), commitment_txid) appointment_data = { "locator": locator, @@ -536,6 +538,7 @@ def test_get_appointment_in_responder(internal_api, client, generate_dummy_track # mock the gatekeeper (user won't be registered if the previous tests weren't ran) monkeypatch.setattr(internal_api.watcher.gatekeeper, "authenticate_user", mock_authenticate_user) + monkeypatch.setattr(internal_api.watcher.gatekeeper, "has_subscription_expired", lambda *args: False) # Request back the data message = "get appointment {}".format(tx_tracker.locator) diff --git a/test/teos/unit/test_carrier.py b/test/teos/unit/test_carrier.py index 276fdc51..2bb65e7a 100644 --- a/test/teos/unit/test_carrier.py +++ b/test/teos/unit/test_carrier.py @@ -1,3 +1,5 @@ +from bitcoin.core import b2x + from test.teos.conftest import generate_blocks from test.teos.unit.conftest import get_random_value_hex from teos.utils.rpc_errors import RPC_VERIFY_ALREADY_IN_CHAIN, RPC_DESERIALIZATION_ERROR @@ -16,9 +18,9 @@ def test_send_transaction(carrier): tx = create_commitment_tx() - txid = bitcoin_cli.decoderawtransaction(tx).get("txid") - - receipt = carrier.send_transaction(tx, txid) + tx_hex = b2x(tx.serialize()) + rawtx = bitcoin_cli.call("decoderawtransaction", tx_hex) + receipt = carrier.send_transaction(tx_hex, rawtx.get("txid")) assert receipt.delivered is True @@ -26,9 +28,11 @@ def test_send_transaction(carrier): def test_send_double_spending_transaction(carrier): # We can test what happens if the same transaction is sent twice tx = create_commitment_tx() - txid = bitcoin_cli.decoderawtransaction(tx).get("txid") + tx_hex = b2x(tx.serialize()) + rawtx = bitcoin_cli.call("decoderawtransaction", tx_hex) + txid = rawtx.get("txid") - receipt = carrier.send_transaction(tx, txid) + receipt = carrier.send_transaction(tx_hex, txid) sent_txs.append(txid) # Wait for a block to be mined. Issued receipts is reset from the Responder every block, so we should do it too. @@ -36,7 +40,7 @@ def test_send_double_spending_transaction(carrier): carrier.issued_receipts = {} # Try to send it again - receipt2 = carrier.send_transaction(tx, txid) + receipt2 = carrier.send_transaction(tx_hex, txid) # The carrier should report delivered True for both, but in the second case the transaction was already delivered # (either by himself or someone else) @@ -46,7 +50,9 @@ def test_send_double_spending_transaction(carrier): def test_send_transaction_invalid_format(carrier): # Test sending a transaction that does not fits the format - txid = create_commitment_tx()[::-1] + tx = create_commitment_tx() + tx_hex = b2x(tx.serialize()) + txid = tx_hex[::-1] receipt = carrier.send_transaction(txid, txid) assert receipt.delivered is False and receipt.reason == RPC_DESERIALIZATION_ERROR diff --git a/test/teos/unit/test_responder.py b/test/teos/unit/test_responder.py index 05cf7c7d..8e7e9bc7 100644 --- a/test/teos/unit/test_responder.py +++ b/test/teos/unit/test_responder.py @@ -6,6 +6,8 @@ from copy import deepcopy from threading import Thread +from bitcoin.core import b2x + from teos.carrier import Carrier from teos.chain_monitor import ChainMonitor from teos.block_processor import BlockProcessor @@ -21,6 +23,7 @@ generate_blocks_with_delay, create_commitment_tx, generate_block_with_transactions, + makeCTransaction, ) from test.teos.unit.conftest import get_random_value_hex, bitcoind_feed_params @@ -147,7 +150,7 @@ def test_handle_breach(db_manager, gatekeeper, carrier, responder, block_process uuid = uuid4().hex commitment_tx = create_commitment_tx() tracker = generate_dummy_tracker(commitment_tx) - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # The block_hash passed to add_response does not matter much now. It will in the future to deal with errors receipt = responder.handle_breach( @@ -296,7 +299,7 @@ def test_do_watch(temp_db_manager, gatekeeper, carrier, block_processor, generat subscription_expiry = block_processor.get_block_count() + 110 # Broadcast all commitment transactions - generate_block_with_transactions(commitment_txs) + generate_block_with_transactions([*map(lambda tx: b2x(tx.serialize()), commitment_txs)]) # Create a fresh responder to simplify the test responder = Responder(temp_db_manager, gatekeeper, carrier, block_processor) @@ -331,7 +334,7 @@ def test_do_watch(temp_db_manager, gatekeeper, carrier, block_processor, generat # And broadcast some of the penalties broadcast_txs = [] for tracker in trackers[:5]: - bitcoin_cli.sendrawtransaction(tracker.penalty_rawtx) + bitcoin_cli.sendrawtransaction(makeCTransaction(tracker.penalty_rawtx)) broadcast_txs.append(tracker.penalty_txid) # Mine a block @@ -416,7 +419,7 @@ def test_get_txs_to_rebroadcast(responder): def test_get_completed_trackers(db_manager, gatekeeper, carrier, responder, block_processor, generate_dummy_tracker): commitment_txs = [create_commitment_tx() for _ in range(30)] - generate_block_with_transactions(commitment_txs) + generate_block_with_transactions([*map(lambda tx: b2x(tx.serialize()), commitment_txs)]) # A complete tracker is a tracker whose penalty transaction has been irrevocably resolved (i.e. has reached 100 # confirmations) # We'll create 3 type of txs: irrevocably resolved, confirmed but not irrevocably resolved, and unconfirmed @@ -440,12 +443,12 @@ def test_get_completed_trackers(db_manager, gatekeeper, carrier, responder, bloc responder.trackers[uuid] = tracker.get_summary() for uuid, tracker in trackers_ir_resolved.items(): - bitcoin_cli.sendrawtransaction(tracker.penalty_rawtx) + bitcoin_cli.sendrawtransaction(makeCTransaction(tracker.penalty_rawtx)) generate_blocks_with_delay(1) for uuid, tracker in trackers_confirmed.items(): - bitcoin_cli.sendrawtransaction(tracker.penalty_rawtx) + bitcoin_cli.sendrawtransaction(makeCTransaction(tracker.penalty_rawtx)) # ir_resolved have 100 confirmations and confirmed have 99 generate_blocks_with_delay(99) @@ -534,7 +537,7 @@ def test_get_outdated_trackers(responder, generate_dummy_tracker): def test_rebroadcast(db_manager, gatekeeper, carrier, responder, block_processor, generate_dummy_tracker): # Include the commitment txs in a block commitment_txs = [create_commitment_tx() for _ in range(20)] - generate_block_with_transactions(commitment_txs) + generate_block_with_transactions([*map(lambda tx: b2x(tx.serialize()), commitment_txs)]) txs_to_rebroadcast = [] # Rebroadcast calls add_response with retry=True. The tracker data is already in trackers. diff --git a/test/teos/unit/test_watcher.py b/test/teos/unit/test_watcher.py index 6e70d4db..97106f40 100644 --- a/test/teos/unit/test_watcher.py +++ b/test/teos/unit/test_watcher.py @@ -5,6 +5,8 @@ from threading import Thread from coincurve import PrivateKey +from bitcoin.core import b2x, b2lx + from teos.carrier import Carrier from teos.responder import Responder from teos.gatekeeper import UserInfo @@ -33,6 +35,7 @@ create_txs, bitcoin_cli, generate_block_with_transactions, + makeCTransaction, ) from test.teos.unit.conftest import ( get_random_value_hex, @@ -123,7 +126,7 @@ def test_locator_cache_init_not_enough_blocks(block_processor): generate_blocks(3 - block_count) # Simulate there are only 3 blocks - third_block_hash = bitcoin_cli.getblockhash(2) + third_block_hash = b2lx(bitcoin_cli.getblockhash(2)) locator_cache.init(third_block_hash, block_processor) assert len(locator_cache.blocks) == 3 for k, v in locator_cache.blocks.items(): @@ -300,7 +303,7 @@ def test_fix_cache(block_processor): # The data in the new cache corresponds to the last ``cache_size`` blocks. block_count = block_processor.get_block_count() for i in range(block_count, block_count - locator_cache.cache_size, -1): - block_hash = bitcoin_cli.getblockhash(i) + block_hash = b2lx(bitcoin_cli.getblockhash(i)) assert block_hash in locator_cache.blocks for locator in locator_cache.blocks[block_hash]: assert locator in locator_cache.cache @@ -469,9 +472,9 @@ def test_add_appointment_in_cache_invalid_blob(watcher): # We need to create the appointment manually commitment_tx, commitment_txid, penalty_tx = create_txs() - locator = compute_locator(commitment_tx) + locator = compute_locator(commitment_txid) dummy_appointment_data = {"tx": penalty_tx, "tx_id": commitment_txid, "to_self_delay": 20} - encrypted_blob = Cryptographer.encrypt(penalty_tx[::-1], commitment_txid) + encrypted_blob = Cryptographer.encrypt(b2x(penalty_tx.serialize())[::-1], commitment_txid) appointment_data = { "locator": locator, @@ -591,7 +594,7 @@ def test_do_watch(watcher, temp_db_manager, generate_dummy_appointment): # Broadcast the first two for dispute_tx in dispute_txs[:2]: - bitcoin_cli.sendrawtransaction(dispute_tx) + bitcoin_cli.sendrawtransaction(makeCTransaction(dispute_tx)) # After generating a block, the appointment count should have been reduced by 2 (two breaches) generate_blocks_with_delay(1) From ba9426ddc0ac30478356e4a759aba4029858eaee Mon Sep 17 00:00:00 2001 From: Salvatore Ingala <6681844+bigspider@users.noreply.github.com> Date: Wed, 7 Oct 2020 10:43:31 +0200 Subject: [PATCH 2/2] Finished removing AuthServiceProxy; adapted e2e tests --- .coveragerc | 1 - teos/carrier.py | 5 +- teos/utils/auth_proxy.py | 225 -------------------------------- test/teos/e2e/conftest.py | 4 +- test/teos/e2e/test_basic_e2e.py | 65 +++++---- 5 files changed, 43 insertions(+), 257 deletions(-) delete mode 100644 teos/utils/auth_proxy.py diff --git a/.coveragerc b/.coveragerc index ac585fbb..453a2d1e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -4,4 +4,3 @@ omit = teos/teosd.py teos/logger.py teos/sample_conf.py - teos/utils/auth_proxy.py \ No newline at end of file diff --git a/teos/carrier.py b/teos/carrier.py index 96fd7222..b524b4e0 100644 --- a/teos/carrier.py +++ b/teos/carrier.py @@ -73,7 +73,6 @@ def proxy(self): ) return bitcoin.rpc.Proxy(service_url) - # NOTCOVERED def send_transaction(self, rawtx, txid): """ @@ -108,12 +107,12 @@ def send_transaction(self, rawtx, txid): except VerifyRejectedError as e: # DISCUSS: 37-transaction-rejection receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_REJECTED) - self.logger.error("Transaction couldn't be broadcasted", error=e) + self.logger.error("Transaction couldn't be broadcasted", error=e.error) except VerifyError as e: # DISCUSS: 37-transaction-rejection receipt = Receipt(delivered=False, reason=rpc_errors.RPC_VERIFY_ERROR) - self.logger.error("Transaction couldn't be broadcasted", error=e) + self.logger.error("Transaction couldn't be broadcasted", error=e.error) except VerifyAlreadyInChainError as e: self.logger.info("Transaction is already in the blockchain. Getting confirmation count", txid=txid) diff --git a/teos/utils/auth_proxy.py b/teos/utils/auth_proxy.py deleted file mode 100644 index d0e049ee..00000000 --- a/teos/utils/auth_proxy.py +++ /dev/null @@ -1,225 +0,0 @@ -# Copyright (c) 2011 Jeff Garzik -# -# Previous copyright, from python-jsonrpc/jsonrpc/proxy.py: -# -# Copyright (c) 2007 Jan-Klaas Kollhof -# -# This file is part of jsonrpc. -# -# jsonrpc is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 2.1 of the License, or -# (at your option) any later version. -# -# This software is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this software; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""HTTP proxy for opening RPC connection to bitcoind. - -AuthServiceProxy has the following improvements over python-jsonrpc's -ServiceProxy class: - -- HTTP connections persist for the life of the AuthServiceProxy object - (if server supports HTTP/1.1) -- sends protocol 'version', per JSON-RPC 1.1 -- sends proper, incrementing 'id' -- sends Basic HTTP authentication headers -- parses all JSON numbers that look like floats as Decimal -- uses standard Python json lib -""" - -# bitcoin_rpc auth proxy does not handle broken pipes. Using Bitcoin Core's one which is more complete. -# Taken from https://github.com/bitcoin/bitcoin/blob/master/test/functional/test_framework/authproxy.py - -import base64 -import decimal -from http import HTTPStatus -import http.client -import json -import logging -import os -import socket -import time -import urllib.parse - -HTTP_TIMEOUT = 30 -USER_AGENT = "AuthServiceProxy/0.1" - -log = logging.getLogger("BitcoinRPC") - - -class JSONRPCException(Exception): - def __init__(self, rpc_error, http_status=None): - try: - errmsg = "%(message)s (%(code)i)" % rpc_error - except (KeyError, TypeError): - errmsg = "" - super().__init__(errmsg) - self.error = rpc_error - self.http_status = http_status - - -def EncodeDecimal(o): - if isinstance(o, decimal.Decimal): - return str(o) - raise TypeError(repr(o) + " is not JSON serializable") - - -class AuthServiceProxy: - __id_count = 0 - - # ensure_ascii: escape unicode as \uXXXX, passed to json.dumps - def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None, ensure_ascii=True): - self.__service_url = service_url - self._service_name = service_name - self.ensure_ascii = ensure_ascii # can be toggled on the fly by tests - self.__url = urllib.parse.urlparse(service_url) - user = None if self.__url.username is None else self.__url.username.encode("utf8") - passwd = None if self.__url.password is None else self.__url.password.encode("utf8") - authpair = user + b":" + passwd - self.__auth_header = b"Basic " + base64.b64encode(authpair) - self.timeout = timeout - self._set_conn(connection) - - def __getattr__(self, name): - if name.startswith("__") and name.endswith("__"): - # Python internal stuff - raise AttributeError - if self._service_name is not None: - name = "%s.%s" % (self._service_name, name) - return AuthServiceProxy(self.__service_url, name, connection=self.__conn) - - def _request(self, method, path, postdata): - """ - Do a HTTP request, with retry if we get disconnected (e.g. due to a timeout). - This is a workaround for https://bugs.python.org/issue3566 which is fixed in Python 3.5. - """ - headers = { - "Host": self.__url.hostname, - "User-Agent": USER_AGENT, - "Authorization": self.__auth_header, - "Content-type": "application/json", - } - if os.name == "nt": - # Windows somehow does not like to re-use connections - # TODO: Find out why the connection would disconnect occasionally and make it reusable on Windows - self._set_conn() - try: - self.__conn.request(method, path, postdata, headers) - return self._get_response() - except http.client.BadStatusLine as e: - if e.line == "''": # if connection was closed, try again - self.__conn.close() - self.__conn.request(method, path, postdata, headers) - return self._get_response() - else: - raise - except (BrokenPipeError, ConnectionResetError): - # Python 3.5+ raises BrokenPipeError instead of BadStatusLine when the connection was reset - # ConnectionResetError happens on FreeBSD with Python 3.4 - self.__conn.close() - self.__conn.request(method, path, postdata, headers) - return self._get_response() - - def get_request(self, *args, **argsn): - AuthServiceProxy.__id_count += 1 - - log.debug( - "-{}-> {} {}".format( - AuthServiceProxy.__id_count, - self._service_name, - json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii), - ) - ) - if args and argsn: - raise ValueError("Cannot handle both named and positional arguments") - return { - "version": "1.1", - "method": self._service_name, - "params": args or argsn, - "id": AuthServiceProxy.__id_count, - } - - def __call__(self, *args, **argsn): - postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) - response, status = self._request("POST", self.__url.path, postdata.encode("utf-8")) - if response["error"] is not None: - raise JSONRPCException(response["error"], status) - elif "result" not in response: - raise JSONRPCException({"code": -343, "message": "missing JSON-RPC result"}, status) - elif status != HTTPStatus.OK: - raise JSONRPCException({"code": -342, "message": "non-200 HTTP status code but no JSON-RPC error"}, status) - else: - return response["result"] - - def batch(self, rpc_call_list): - postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) - log.debug("--> " + postdata) - response, status = self._request("POST", self.__url.path, postdata.encode("utf-8")) - if status != HTTPStatus.OK: - raise JSONRPCException({"code": -342, "message": "non-200 HTTP status code but no JSON-RPC error"}, status) - return response - - def _get_response(self): - req_start_time = time.time() - try: - http_response = self.__conn.getresponse() - except socket.timeout: - raise JSONRPCException( - { - "code": -344, - "message": "%r RPC took longer than %f seconds. Consider " - "using larger timeout for calls that take " - "longer to return." % (self._service_name, self.__conn.timeout), - } - ) - if http_response is None: - raise JSONRPCException({"code": -342, "message": "missing HTTP response from server"}) - - content_type = http_response.getheader("Content-Type") - if content_type != "application/json": - raise JSONRPCException( - { - "code": -342, - "message": "non-JSON HTTP response with '%i %s' from server" - % (http_response.status, http_response.reason), - }, - http_response.status, - ) - - responsedata = http_response.read().decode("utf8") - response = json.loads(responsedata, parse_float=decimal.Decimal) - elapsed = time.time() - req_start_time - if "error" in response and response["error"] is None: - log.debug( - "<-%s- [%.6f] %s" - % ( - response["id"], - elapsed, - json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii), - ) - ) - else: - log.debug("<-- [%.6f] %s" % (elapsed, responsedata)) - return response, http_response.status - - def __truediv__(self, relative_uri): - return AuthServiceProxy( - "{}/{}".format(self.__service_url, relative_uri), self._service_name, connection=self.__conn - ) - - def _set_conn(self, connection=None): - port = 80 if self.__url.port is None else self.__url.port - if connection: - self.__conn = connection - self.timeout = connection.timeout - elif self.__url.scheme == "https": - self.__conn = http.client.HTTPSConnection(self.__url.hostname, port, timeout=self.timeout) - else: - self.__conn = http.client.HTTPConnection(self.__url.hostname, port, timeout=self.timeout) diff --git a/test/teos/e2e/conftest.py b/test/teos/e2e/conftest.py index abddd103..2e1defe0 100644 --- a/test/teos/e2e/conftest.py +++ b/test/teos/e2e/conftest.py @@ -6,6 +6,8 @@ from grpc import RpcError from multiprocessing import Process +from bitcoin.core import b2x + from teos.teosd import main from teos.cli.teos_cli import RPCClient from common.cryptographer import Cryptographer @@ -61,6 +63,6 @@ def run_teosd(): def build_appointment_data(commitment_tx_id, penalty_tx): - appointment_data = {"tx": penalty_tx, "tx_id": commitment_tx_id, "to_self_delay": 20} + appointment_data = {"tx": b2x(penalty_tx.serialize()), "tx_id": commitment_tx_id, "to_self_delay": 20} return appointment_data diff --git a/test/teos/e2e/test_basic_e2e.py b/test/teos/e2e/test_basic_e2e.py index 017ca544..c4202857 100644 --- a/test/teos/e2e/test_basic_e2e.py +++ b/test/teos/e2e/test_basic_e2e.py @@ -4,6 +4,9 @@ from riemann.tx import Tx from coincurve import PrivateKey +from bitcoin.core import b2x, lx +from bitcoin.rpc import JSONRPCError + from contrib.client import teos_client import common.receipts as receipts @@ -13,10 +16,10 @@ from common.cryptographer import Cryptographer from teos.cli.teos_cli import RPCClient -from teos.utils.auth_proxy import JSONRPCException from test.teos.conftest import ( get_random_value_hex, + makeCTransaction, create_txs, create_penalty_tx, bitcoin_cli, @@ -59,6 +62,9 @@ def test_commands_non_registered(run_bitcoind, teosd): # Add appointment commitment_tx, commitment_tx_id, penalty_tx = create_txs() + print(commitment_tx) + print(commitment_tx_id) + print(penalty_tx) appointment_data = build_appointment_data(commitment_tx_id, penalty_tx) with pytest.raises(TowerResponseError): @@ -119,7 +125,7 @@ def test_appointment_life_cycle(run_bitcoind): assert len(watching) == appointments_in_watcher and len(responding) == 0 # Trigger a breach and check again - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) appointment_info = get_appointment_info(locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED assert appointment_info.get("locator") == locator @@ -132,13 +138,13 @@ def test_appointment_life_cycle(run_bitcoind): assert len(watching) == appointments_in_watcher and len(responding) == appointments_in_responder # It can be also checked by ensuring that the penalty transaction made it to the network - penalty_tx_id = bitcoin_cli.decoderawtransaction(penalty_tx).get("txid") + penalty_tx_id = bitcoin_cli.call("decoderawtransaction", b2x(penalty_tx.serialize())).get("txid") try: - bitcoin_cli.getrawtransaction(penalty_tx_id) + bitcoin_cli.getrawtransaction(lx(penalty_tx_id)) assert True - except JSONRPCException: + except JSONRPCError: # If the transaction is not found. assert False @@ -174,8 +180,8 @@ def test_multiple_appointments_life_cycle(run_bitcoind): locator = compute_locator(commitment_txid) appointment = { "locator": locator, - "commitment_tx": commitment_tx, - "penalty_tx": penalty_tx, + "commitment_tx": b2x(commitment_tx.serialize()), + "penalty_tx": b2x(penalty_tx.serialize()), "appointment_data": appointment_data, } @@ -220,9 +226,9 @@ def test_appointment_malformed_penalty(run_bitcoind): commitment_tx, commitment_txid, penalty_tx = create_txs() # Now we can modify the penalty so it is invalid when broadcast (removing the witness should do) - mod_penalty_tx = Tx.from_hex(penalty_tx).no_witness() + mod_penalty_tx = Tx.from_hex(b2x(penalty_tx.serialize())).no_witness() - appointment_data = build_appointment_data(commitment_txid, mod_penalty_tx.hex()) + appointment_data = build_appointment_data(commitment_txid, makeCTransaction(mod_penalty_tx.hex())) locator = compute_locator(commitment_txid) appointment = teos_client.create_appointment(appointment_data) @@ -235,7 +241,7 @@ def test_appointment_malformed_penalty(run_bitcoind): assert appointment_info.get("appointment") == appointment.to_dict() # Broadcast the commitment transaction and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # The appointment should have been removed since the penalty_tx was malformed. with pytest.raises(TowerResponseError): @@ -252,8 +258,10 @@ def test_appointment_wrong_decryption_key(run_bitcoind): # We cannot use teos_client.add_appointment here since it computes the locator internally, so let's do it manually. # We will encrypt the blob using the random value and derive the locator from the commitment tx. - appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")) - appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32)) + appointment_data["locator"] = compute_locator( + bitcoin_cli.call("decoderawtransaction", b2x(commitment_tx.serialize())).get("txid") + ) + appointment_data["encrypted_blob"] = Cryptographer.encrypt(b2x(penalty_tx.serialize()), get_random_value_hex(32)) appointment = Appointment.from_dict(appointment_data) signature = Cryptographer.sign(appointment.serialize(), user_sk) @@ -271,7 +279,7 @@ def test_appointment_wrong_decryption_key(run_bitcoind): assert response_json.get("locator") == appointment.locator # Trigger the appointment - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # The appointment should have been removed since the decryption failed. with pytest.raises(TowerResponseError): @@ -293,13 +301,13 @@ def test_two_identical_appointments(run_bitcoind): add_appointment(appointment) # Broadcast the commitment transaction and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # The last appointment should have made it to the Responder appointment_info = get_appointment_info(locator) assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED - assert appointment_info.get("appointment").get("penalty_rawtx") == penalty_tx + assert appointment_info.get("appointment").get("penalty_rawtx") == b2x(penalty_tx.serialize()) # FIXME: This test won't work since we're still passing appointment replicas to the Responder. @@ -330,7 +338,7 @@ def test_two_identical_appointments(run_bitcoind): # assert appointment_info.get("status") == AppointmentStatus.BEING_WATCHED # # # Broadcast the commitment transaction and mine a block -# generate_block_with_transactions(commitment_tx) +# generate_block_with_transactions(b2x(commitment_tx.serialize())) # # # The last appointment should have made it to the Responder # sleep(1) @@ -345,7 +353,7 @@ def test_two_identical_appointments(run_bitcoind): # appointment_info = appointment_info if appointment_info is None else appointment_dup_info # # assert appointment_info.get("status") == AppointmentStatus.DISPUTE_RESPONDED -# assert appointment_info.get("appointment").get("penalty_rawtx") == penalty_tx +# assert appointment_info.get("appointment").get("penalty_rawtx") == b2x(penalty_tx.serialize()) def test_two_appointment_same_locator_different_penalty_different_users(run_bitcoind): @@ -353,7 +361,7 @@ def test_two_appointment_same_locator_different_penalty_different_users(run_bitc commitment_tx, commitment_txid, penalty_tx1 = create_txs() # We need to create a second penalty spending from the same commitment - decoded_commitment_tx = bitcoin_cli.decoderawtransaction(commitment_tx) + decoded_commitment_tx = bitcoin_cli.call("decoderawtransaction", b2x(commitment_tx.serialize())) new_addr = bitcoin_cli.getnewaddress() penalty_tx2 = create_penalty_tx(decoded_commitment_tx, new_addr) @@ -372,7 +380,7 @@ def test_two_appointment_same_locator_different_penalty_different_users(run_bitc add_appointment(appointment_2, sk=tmp_user_sk) # Broadcast the commitment transaction and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # One of the transactions must have made it to the Responder while the other must have been dropped for # double-spending. That means that one of the responses from the tower should fail @@ -397,7 +405,7 @@ def test_add_appointment_trigger_on_cache(run_bitcoind): locator = compute_locator(commitment_txid) # Let's send the commitment to the network and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # Send the data to the tower and request it back. It should have gone straightaway to the Responder appointment = teos_client.create_appointment(appointment_data) @@ -410,11 +418,12 @@ def test_add_appointment_invalid_trigger_on_cache(run_bitcoind): commitment_tx, commitment_txid, penalty_tx = create_txs() # We can just flip the justice tx so it is invalid - appointment_data = build_appointment_data(commitment_txid, penalty_tx[::-1]) + appointment_data = build_appointment_data(commitment_txid, penalty_tx) + appointment_data["tx"] = appointment_data["tx"][::-1] locator = compute_locator(commitment_txid) # Let's send the commitment to the network and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) sleep(1) # Send the data to the tower and request it back. It should get accepted but the data will be dropped. @@ -428,15 +437,17 @@ def test_add_appointment_trigger_on_cache_cannot_decrypt(run_bitcoind): commitment_tx, _, penalty_tx = create_txs() # Let's send the commitment to the network and mine a block - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) sleep(1) # The appointment data is built using a random 32-byte value. appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx) # We cannot use teos_client.add_appointment here since it computes the locator internally, so let's do it manually. - appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid")) - appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32)) + appointment_data["locator"] = compute_locator( + bitcoin_cli.call("decoderawtransaction", b2x(commitment_tx.serialize())).get("txid") + ) + appointment_data["encrypted_blob"] = Cryptographer.encrypt(b2x(penalty_tx.serialize()), get_random_value_hex(32)) appointment = Appointment.from_dict(appointment_data) signature = Cryptographer.sign(appointment.serialize(), user_sk) @@ -487,7 +498,7 @@ def test_appointment_shutdown_teos_trigger_back_online(run_bitcoind): assert appointment_info.get("appointment") == appointment.to_dict() # Trigger appointment after restart - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # The appointment should have been moved to the Responder appointment_info = get_appointment_info(locator) @@ -517,7 +528,7 @@ def test_appointment_shutdown_teos_trigger_while_offline(run_bitcoind): rpc_client.stop() teosd_process.join() - generate_block_with_transactions(commitment_tx) + generate_block_with_transactions(b2x(commitment_tx.serialize())) # Restart teosd_process, _ = run_teosd()