diff --git a/.travis.yml b/.travis.yml index a76ba32..c34efed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - pip install pyrocksdb - pip install tribus-hash - pip install pytest-cov + - pip install pylru # command to run tests script: pytest --cov=server --cov=lib --cov=wallet # Dont report coverage from nightly diff --git a/docs/PROTOCOL.rst b/docs/PROTOCOL.rst index 173eba1..66e7fc8 100644 --- a/docs/PROTOCOL.rst +++ b/docs/PROTOCOL.rst @@ -930,11 +930,11 @@ Protocol Version 1.2 Protocol version 1.2 introduces new methods `blockchain.block.headers`, `mempool.get_fee_histogram`. -`blockchain.block.get_chunk` and all methods beginning - `blockchain.address.` are deprecated and support will be removed in - some future protocol version. You should update your code to use - `blockchain.block.headers` and `Script Hashes`_ with the scripthash - methods introduced in protocol 1.1 instead. +* `blockchain.block.get_chunk` and all methods beginning `blockchain.address.` are deprecated + and support will be removed in some future protocol version. You should update your code to use `blockchain.block.headers` + and `Script Hashes`_ with the scripthash methods introduced in protocol 1.1 instead. + +* `blockchain.transaction.get` now has an optional parameter *verbose*. blockchain.block.headers ======================== @@ -978,7 +978,7 @@ Return concatenated block headers as hexadecimal from the main chain. { "count": 2, - "hex": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299'" + "hex": "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e36299" "max": 2016 } @@ -1003,3 +1003,25 @@ intervals is currently not part of the protocol. .. _JSON RPC 1.0: http://json-rpc.org/wiki/specification .. _JSON RPC 2.0: http://json-rpc.org/specification + + +blockchain.transaction.get +========================== + +Return a raw transaction. + + blockchain.transaction.get(**tx_hash**, **verbose**=False) + + **tx_hash** + + The transaction hash as a hexadecimal string. + + **verbose** + Verbose mode, passed on to bitcoind in **getrawtransaction**. + +**Response** + + In non-verbose mode return the raw transaction as a hexadecimal string. + + If verbose, returns what your coin's bitcoind returns; see its + documentation for details. diff --git a/server/controller.py b/server/controller.py index 454de2b..b77a228 100644 --- a/server/controller.py +++ b/server/controller.py @@ -842,13 +842,14 @@ class Controller(ServerBase): to the daemon's memory pool.''' return await self.daemon_request('relayfee') - async def transaction_get(self, tx_hash): + async def transaction_get(self, tx_hash, verbose=False): '''Return the serialized raw transaction given its hash tx_hash: the transaction hash as a hexadecimal string + verbose: passed on to the daemon ''' self.assert_tx_hash(tx_hash) - return await self.daemon_request('getrawtransaction', tx_hash) + return await self.daemon_request('getrawtransaction', tx_hash, verbose) async def transaction_get_1_0(self, tx_hash, height=None): '''Return the serialized raw transaction given its hash diff --git a/server/daemon.py b/server/daemon.py index d5554e8..bd70a3c 100644 --- a/server/daemon.py +++ b/server/daemon.py @@ -262,9 +262,9 @@ class Daemon(LoggedClass): network_info = await self.getnetworkinfo() return network_info['relayfee'] - async def getrawtransaction(self, hex_hash): + async def getrawtransaction(self, hex_hash, verbose=False): '''Return the serialized raw transaction with the given hash.''' - return await self._send_single('getrawtransaction', (hex_hash, 0)) + return await self._send_single('getrawtransaction', (hex_hash, int(verbose))) async def getrawtransactions(self, hex_hashes, replace_errs=True): '''Return the serialized raw transactions with the given hashes. diff --git a/tests/server/test_api.py b/tests/server/test_api.py new file mode 100644 index 0000000..4d52629 --- /dev/null +++ b/tests/server/test_api.py @@ -0,0 +1,104 @@ +import asyncio +from unittest import mock + +from lib.jsonrpc import RPCError +from server.env import Env +from server.controller import Controller + +loop = asyncio.get_event_loop() + + +def set_env(): + env = mock.create_autospec(Env) + env.coin = mock.Mock() + env.loop_policy = None + env.max_sessions = 0 + env.max_subs = 0 + env.max_send = 0 + env.bandwidth_limit = 0 + env.identities = '' + env.tor_proxy_host = env.tor_proxy_port = None + env.peer_discovery = env.PD_SELF = False + env.daemon_url = 'http://localhost:8000/' + return env + + +async def coro(res): + return res + + +def raise_exception(exc, msg): + raise exc(msg) + + +def ensure_text_exception(test, exception): + res = err = None + try: + res = loop.run_until_complete(test) + except Exception as e: + err = e + assert isinstance(err, exception), (res, err) + + +def test_transaction_get(): + async def test_verbose_ignore_by_backend(): + env = set_env() + sut = Controller(env) + sut.daemon_request = mock.Mock() + sut.daemon_request.return_value = coro('11'*32) + res = await sut.transaction_get('ff'*32, True) + assert res == '11'*32 + + async def test_verbose_ok(): + env = set_env() + sut = Controller(env) + sut.daemon_request = mock.Mock() + response = { + "hex": "00"*32, + "blockhash": "ff"*32 + } + sut.daemon_request.return_value = coro(response) + res = await sut.transaction_get('ff'*32, True) + assert res == response + + response = { + "hex": "00"*32, + "blockhash": None + } + sut.daemon_request.return_value = coro(response) + res = await sut.transaction_get('ff'*32, True) + assert res == response + + async def test_no_verbose(): + env = set_env() + sut = Controller(env) + sut.daemon_request = mock.Mock() + response = 'cafebabe'*64 + sut.daemon_request.return_value = coro(response) + res = await sut.transaction_get('ff'*32) + assert res == response + + async def test_verbose_failure(): + env = set_env() + sut = Controller(env) + sut.daemon_request = mock.Mock() + sut.daemon_request.return_value = coro(raise_exception(RPCError, 'some unhandled error')) + await sut.transaction_get('ff' * 32, True) + + async def test_wrong_txhash(): + env = set_env() + sut = Controller(env) + sut.daemon_request = mock.Mock() + await sut.transaction_get('cafe') + sut.daemon_request.assert_not_called() + + loop.run_until_complete(asyncio.gather( + *[ + test_verbose_ignore_by_backend(), + test_verbose_ok(), + test_no_verbose() + ] + )) + + for error_test in [test_verbose_failure, test_wrong_txhash]: + ensure_text_exception(error_test(), RPCError)