diff --git a/newsfragments/1461.misc.rst b/newsfragments/1461.misc.rst new file mode 100644 index 0000000000..30ec892419 --- /dev/null +++ b/newsfragments/1461.misc.rst @@ -0,0 +1 @@ +Pull formatting methods out of middleware diff --git a/tests/core/method-class/test_method.py b/tests/core/method-class/test_method.py index d4a6ec7cbd..e6694585a4 100644 --- a/tests/core/method-class/test_method.py +++ b/tests/core/method-class/test_method.py @@ -4,8 +4,7 @@ import pytest from eth_utils.toolz import ( - identity, - pipe, + compose, ) from web3 import ( @@ -14,9 +13,12 @@ ) from web3.method import ( Method, + _apply_request_formatters, + default_root_munger, ) from web3.module import ( ModuleV2, + apply_result_formatters, ) @@ -24,16 +26,14 @@ def test_method_accepts_callable_for_selector(): method = Method( mungers=[], json_rpc_method=lambda *_: 'eth_method', - formatter_lookup_fn='' ) assert method.method_selector_fn() == 'eth_method' def test_method_selector_fn_accepts_str(): method = Method( - mungers=[], + mungers=None, json_rpc_method='eth_method', - formatter_lookup_fn='' ) assert method.method_selector_fn() == 'eth_method' @@ -43,7 +43,6 @@ def test_method_selector_fn_invalid_arg(): method = Method( mungers=[], json_rpc_method=555555, - formatter_lookup_fn='' ) method.method_selector_fn() @@ -52,129 +51,115 @@ def test_get_formatters_default_formatter_for_falsy_config(): method = Method( mungers=[], json_rpc_method='eth_method', - formatter_lookup_fn='' ) - default_input_formatters, default_output_formatters = method.get_formatters('') - - assert pipe(['a', 'b', 'c'], *default_input_formatters) == ['a', 'b', 'c'] - assert pipe(['a', 'b', 'c'], *default_output_formatters) == ['a', 'b', 'c'] + default_request_formatters = method.request_formatters(method.method_selector_fn()) + default_result_formatters = method.result_formatters(method.method_selector_fn()) + assert _apply_request_formatters(['a', 'b', 'c'], default_request_formatters) == ('a', 'b', 'c') + assert apply_result_formatters( + default_result_formatters, ['a', 'b', 'c']) == ['a', 'b', 'c'] def test_get_formatters_non_falsy_config_retrieval(): - def formatter_lookup_fn(method): - if method == 'eth_method': - return 'match' - return 'nonmatch' method = Method( mungers=[], - json_rpc_method='eth_method', - formatter_lookup_fn=formatter_lookup_fn, + json_rpc_method='eth_getBalance', ) - assert method.get_formatters('eth_method') == 'match' - assert method.get_formatters('eth_nonmatching') == 'nonmatch' + method_name = method.method_selector_fn() + first_formatter = (method.request_formatters(method_name).first,) + all_other_formatters = method.request_formatters(method_name).funcs + assert len(first_formatter + all_other_formatters) == 2 + # assert method.request_formatters('eth_nonmatching') == 'nonmatch' def test_input_munger_parameter_passthrough_matching_arity(): method = Method( mungers=[lambda m, z, y: ['success']], json_rpc_method='eth_method', - formatter_lookup_fn='' ) - method.input_munger((object(), ['first', 'second'], {})) == 'success' + method.input_munger(object(), ['first', 'second'], {}) == 'success' def test_input_munger_parameter_passthrough_mismatch_arity(): method = Method( mungers=[lambda m, z, y: 'success'], json_rpc_method='eth_method', - formatter_lookup_fn='' ) with pytest.raises(TypeError): - method.input_munger((object(), ['first', 'second', 'third'], {})) + method.input_munger(object(), ['first', 'second', 'third'], {}) def test_input_munger_falsy_config_result_in_default_munger(): method = Method( mungers=[], json_rpc_method='eth_method', - formatter_lookup_fn='' ) - method.input_munger((object(), [], {})) == [] + method.input_munger(object(), [], {}) == [] def test_default_input_munger_with_input_parameters_exception(): method = Method( mungers=[], json_rpc_method='eth_method', - formatter_lookup_fn='' ) with pytest.raises(TypeError): - method.input_munger((object(), [1], {})) - - -def get_test_formatters(method): - def formatter(params): - return ['ok'] - - if method == 'eth_method': - return ([formatter], [identity]) + method.input_munger(object(), [1], {}) @pytest.mark.parametrize( - "method_config,args,kwargs,expected_result", + "method_config,args,kwargs,expected_request_result,expected_result_formatters_len", ( ( { 'mungers': [], - 'formatter_lookup_fn': '' }, [], {}, - ValueError + ValueError, + 2 ), ( { 'mungers': [], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': '' + 'json_rpc_method': 'eth_getBalance', }, ['unexpected_argument'], {}, - TypeError + TypeError, + 2 ), ( { - 'mungers': [], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': '' + 'mungers': [default_root_munger], + 'json_rpc_method': 'eth_getBalance', }, - [], + ['0x0000000000000000000000000000000000000000', 3], {}, - ('eth_method', ()) + ('eth_getBalance', (('0x' + '00' * 20), "0x3")), + 2 ), ( { - 'mungers': [], - 'json_rpc_method': lambda *_: 'eth_method', - 'formatter_lookup_fn': '' + 'mungers': [default_root_munger], + 'json_rpc_method': lambda *_: 'eth_getBalance', }, - [], + ['0x0000000000000000000000000000000000000000', 3], {}, - ('eth_method', ()) + ('eth_getBalance', (('0x' + '00' * 20), "0x3")), + 2 ), ( { 'mungers': [ - lambda m, x, y, z: [x, y], - lambda m, x, y: [x], - lambda m, x: [str(x)]], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': '' + lambda m, x, y, z, addr: [x, y, addr], + lambda m, x, y, addr: [x, addr], + lambda m, x, addr: [addr, str(x)]], + 'json_rpc_method': 'eth_getBalance', }, - [1, 2, 3], + [1, 2, 3, ('0x' + '00' * 20)], {}, - ('eth_method', ["1"]) + ('eth_getBalance', (('0x' + '00' * 20), "1")), + 2, ), ( { @@ -182,62 +167,77 @@ def formatter(params): lambda m, x, y, z: [x, y], lambda m, x, y: [x], lambda m, x: [str(x)]], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': '' + 'json_rpc_method': 'eth_getBalance', }, [1, 2, 3, 4], {}, TypeError, + 2, ), ( { - 'mungers': [], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': get_test_formatters + 'mungers': [default_root_munger], + 'json_rpc_method': 'eth_getBalance', }, - [], + ('0x0000000000000000000000000000000000000000', 3), {}, - ('eth_method', ['ok']) + ('eth_getBalance', ('0x0000000000000000000000000000000000000000', '0x3')), + 2, ), ( { - 'mungers': [], - 'json_rpc_method': 'eth_mismatch', - 'formatter_lookup_fn': get_test_formatters + 'mungers': [ + lambda m, addr, x, y, z: [addr, x, y], + lambda m, addr, x, y: [addr, x], + lambda m, addr, x: [addr, str(x)]], + 'json_rpc_method': 'eth_getBalance', }, - [], + [('0x' + '00' * 20), 1, 2, 3], {}, - ('eth_mismatch', ()) + ('eth_getBalance', (('0x' + '00' * 20), '1')), + 2, ), ( { - 'mungers': [ - lambda m, x, y, z: [x, y], - lambda m, x, y: [x], - lambda m, x: [str(x)]], - 'json_rpc_method': 'eth_method', - 'formatter_lookup_fn': get_test_formatters + 'mungers': None, + 'json_rpc_method': 'eth_chainId', }, - [1, 2, 3], + [], {}, - ('eth_method', ['ok']) - ), - ) + ('eth_chainId', ()), + 2, + ) + ), + ids=[ + 'raises-error-no-rpc-method', + 'test-unexpected-arg', + 'test-rpc-method-as-string', + 'test-rpc-method-as-callable', + 'test-arg-munger', + 'test-munger-wrong-length-arg', + 'test-request-formatters', + 'test-mungers-and-request-formatters', + 'test-response-formatters', + ] ) def test_process_params( method_config, args, kwargs, - expected_result,): + expected_request_result, + expected_result_formatters_len): - if isclass(expected_result) and issubclass(expected_result, Exception): - with pytest.raises(expected_result): + if isclass(expected_request_result) and issubclass(expected_request_result, Exception): + with pytest.raises(expected_request_result): method = Method(**method_config) - req_params, output_formatter = method.process_params(object(), *args, **kwargs) + request_params, output_formatter = method.process_params(object(), *args, **kwargs) else: method = Method(**method_config) - req_params, output_formatter = method.process_params(object(), *args, **kwargs) - assert req_params == expected_result + request_params, output_formatter = method.process_params(object(), *args, **kwargs) + assert request_params == expected_request_result + first_formatter = (output_formatter[0].first,) + all_other_formatters = output_formatter[0].funcs + assert len(first_formatter + all_other_formatters) == expected_result_formatters_len def keywords(module, keyword_one, keyword_two): @@ -251,14 +251,14 @@ class Success(Exception): def return_exception_raising_formatter(method): def formatter(params): raise Success() - return ([formatter], []) + return compose(formatter) class FakeModule(ModuleV2): method = Method( 'eth_method', mungers=[keywords], - formatter_lookup_fn=return_exception_raising_formatter) + request_formatters=return_exception_raising_formatter) @pytest.fixture @@ -270,12 +270,12 @@ def dummy_w3(): def test_munger_class_method_access_raises_friendly_error(): - with pytest.raises(TypeError): + with pytest.raises(TypeError, match='Direct calls to methods are not supported'): FakeModule.method(1, 2) def test_munger_arguments_by_keyword(dummy_w3): with pytest.raises(Success): - dummy_w3.fake.method(keyword_one=1, keyword_two=2) + dummy_w3.fake.method(keyword_one=1, keyword_two='latest') with pytest.raises(Success): dummy_w3.fake.method(1, keyword_two=2) diff --git a/tests/core/method-class/test_result_formatters.py b/tests/core/method-class/test_result_formatters.py new file mode 100644 index 0000000000..e1b80a4e96 --- /dev/null +++ b/tests/core/method-class/test_result_formatters.py @@ -0,0 +1,54 @@ +import pytest + +from eth_utils.toolz import ( + compose, +) + +from web3 import Web3 +from web3.method import ( + Method, +) +from web3.middleware.fixture import ( + construct_result_generator_middleware, +) +from web3.module import ( + ModuleV2, +) +from web3.providers import ( + BaseProvider, +) + + +def result_formatter(method): + def formatter(self): + return 'OKAY' + return compose(formatter) + + +class DummyProvider(BaseProvider): + def make_request(method, params): + raise NotImplementedError + + +result_middleware = construct_result_generator_middleware({ + 'method_for_test': lambda m, p: 'ok', +}) + + +class ModuleForTest(ModuleV2): + method = Method( + 'method_for_test', + result_formatters=result_formatter) + + +@pytest.fixture +def dummy_w3(): + w3 = Web3( + DummyProvider(), + middlewares=[result_middleware], + modules={"module": (ModuleForTest,)}) + return w3 + + +def test_result_formatter(dummy_w3): + assert dummy_w3.module.method() == 'OKAY' diff --git a/tests/core/version-module/test_version_module.py b/tests/core/version-module/test_version_module.py index 8138a72f72..dbffc5e6a3 100644 --- a/tests/core/version-module/test_version_module.py +++ b/tests/core/version-module/test_version_module.py @@ -53,4 +53,4 @@ async def test_async_blocking_version(async_w3, blocking_w3): assert async_w3.async_version.api == blocking_w3.api assert await async_w3.async_version.node == blocking_w3.clientVersion - assert await async_w3.async_version.ethereum == int(blocking_w3.eth.protocolVersion) + assert await async_w3.async_version.ethereum == blocking_w3.eth.protocolVersion diff --git a/web3/_utils/method_formatters.py b/web3/_utils/method_formatters.py new file mode 100644 index 0000000000..70b59ab18b --- /dev/null +++ b/web3/_utils/method_formatters.py @@ -0,0 +1,471 @@ +import codecs +import operator + +from eth_utils.curried import ( + apply_formatter_at_index, + apply_formatter_if, + apply_formatter_to_array, + apply_formatters_to_dict, + apply_formatters_to_sequence, + apply_one_of_formatters, + is_address, + is_bytes, + is_dict, + is_integer, + is_null, + is_string, + remove_0x_prefix, + text_if_str, + to_checksum_address, + to_list, + to_tuple, +) +from eth_utils.toolz import ( + complement, + compose, + curried, + curry, + partial, +) +from hexbytes import ( + HexBytes, +) + +from web3._utils.abi import ( + is_length, +) +from web3._utils.encoding import ( + hexstr_if_str, + to_hex, +) +from web3._utils.formatters import ( + hex_to_integer, + integer_to_hex, + is_array_of_dicts, + is_array_of_strings, + remove_key_if, +) +from web3._utils.normalizers import ( + abi_address_to_hex, + abi_bytes_to_hex, + abi_int_to_hex, + abi_string_to_hex, +) +from web3._utils.rpc_abi import ( + RPC_ABIS, + abi_request_formatters, +) +from web3.datastructures import ( + AttributeDict, +) + + +def bytes_to_ascii(value): + return codecs.decode(value, 'ascii') + + +to_ascii_if_bytes = apply_formatter_if(is_bytes, bytes_to_ascii) +to_integer_if_hex = apply_formatter_if(is_string, hex_to_integer) +block_number_formatter = apply_formatter_if(is_integer, integer_to_hex) + + +is_false = partial(operator.is_, False) + +is_not_false = complement(is_false) +is_not_null = complement(is_null) + + +@curry +def to_hexbytes(num_bytes, val, variable_length=False): + if isinstance(val, (str, int, bytes)): + result = HexBytes(val) + else: + raise TypeError("Cannot convert %r to HexBytes" % val) + + extra_bytes = len(result) - num_bytes + if extra_bytes == 0 or (variable_length and extra_bytes < 0): + return result + elif all(byte == 0 for byte in result[:extra_bytes]): + return HexBytes(result[extra_bytes:]) + else: + raise ValueError( + "The value %r is %d bytes, but should be %d" % ( + result, len(result), num_bytes + ) + ) + + +def is_attrdict(val): + return isinstance(val, AttributeDict) + + +not_attrdict = complement(is_attrdict) + + +TRANSACTION_FORMATTERS = { + 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), + 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), + 'nonce': to_integer_if_hex, + 'gas': to_integer_if_hex, + 'gasPrice': to_integer_if_hex, + 'value': to_integer_if_hex, + 'from': to_checksum_address, + 'publicKey': apply_formatter_if(is_not_null, to_hexbytes(64)), + 'r': to_hexbytes(32, variable_length=True), + 'raw': HexBytes, + 's': to_hexbytes(32, variable_length=True), + 'to': apply_formatter_if(is_address, to_checksum_address), + 'hash': to_hexbytes(32), + 'v': apply_formatter_if(is_not_null, to_integer_if_hex), + 'standardV': apply_formatter_if(is_not_null, to_integer_if_hex), +} + + +transaction_formatter = apply_formatters_to_dict(TRANSACTION_FORMATTERS) + + +WHISPER_LOG_FORMATTERS = { + 'sig': to_hexbytes(130), + 'topic': to_hexbytes(8), + 'payload': HexBytes, + 'padding': apply_formatter_if(is_not_null, HexBytes), + 'hash': to_hexbytes(64), + 'recipientPublicKey': apply_formatter_if(is_not_null, to_hexbytes(130)), +} + + +whisper_log_formatter = apply_formatters_to_dict(WHISPER_LOG_FORMATTERS) + + +def apply_list_to_array_formatter(formatter): + return to_list(apply_formatter_to_array(formatter)) + + +LOG_ENTRY_FORMATTERS = { + 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), + 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), + 'transactionHash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'logIndex': to_integer_if_hex, + 'address': to_checksum_address, + 'topics': apply_list_to_array_formatter(to_hexbytes(32)), + 'data': to_ascii_if_bytes, +} + + +log_entry_formatter = apply_formatters_to_dict(LOG_ENTRY_FORMATTERS) + + +RECEIPT_FORMATTERS = { + 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), + 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), + 'transactionHash': to_hexbytes(32), + 'cumulativeGasUsed': to_integer_if_hex, + 'status': to_integer_if_hex, + 'gasUsed': to_integer_if_hex, + 'contractAddress': apply_formatter_if(is_not_null, to_checksum_address), + 'logs': apply_list_to_array_formatter(log_entry_formatter), + 'logsBloom': to_hexbytes(256), +} + + +receipt_formatter = apply_formatters_to_dict(RECEIPT_FORMATTERS) + +BLOCK_FORMATTERS = { + 'extraData': to_hexbytes(32, variable_length=True), + 'gasLimit': to_integer_if_hex, + 'gasUsed': to_integer_if_hex, + 'size': to_integer_if_hex, + 'timestamp': to_integer_if_hex, + 'hash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'logsBloom': apply_formatter_if(is_not_null, to_hexbytes(256)), + 'miner': apply_formatter_if(is_not_null, to_checksum_address), + 'mixHash': to_hexbytes(32), + 'nonce': apply_formatter_if(is_not_null, to_hexbytes(8, variable_length=True)), + 'number': apply_formatter_if(is_not_null, to_integer_if_hex), + 'parentHash': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'sha3Uncles': apply_formatter_if(is_not_null, to_hexbytes(32)), + 'uncles': apply_list_to_array_formatter(to_hexbytes(32)), + 'difficulty': to_integer_if_hex, + 'receiptsRoot': to_hexbytes(32), + 'stateRoot': to_hexbytes(32), + 'totalDifficulty': to_integer_if_hex, + 'transactions': apply_one_of_formatters(( + (is_array_of_dicts, apply_list_to_array_formatter(transaction_formatter)), + (is_array_of_strings, apply_list_to_array_formatter(to_hexbytes(32))), + )), + 'transactionsRoot': to_hexbytes(32), +} + + +block_formatter = apply_formatters_to_dict(BLOCK_FORMATTERS) + + +SYNCING_FORMATTERS = { + 'startingBlock': to_integer_if_hex, + 'currentBlock': to_integer_if_hex, + 'highestBlock': to_integer_if_hex, + 'knownStates': to_integer_if_hex, + 'pulledStates': to_integer_if_hex, +} + + +syncing_formatter = apply_formatters_to_dict(SYNCING_FORMATTERS) + + +TRANSACTION_POOL_CONTENT_FORMATTERS = { + 'pending': compose( + curried.keymap(to_ascii_if_bytes), + curried.valmap(transaction_formatter), + ), + 'queued': compose( + curried.keymap(to_ascii_if_bytes), + curried.valmap(transaction_formatter), + ), +} + + +transaction_pool_content_formatter = apply_formatters_to_dict( + TRANSACTION_POOL_CONTENT_FORMATTERS +) + + +TRANSACTION_POOL_INSPECT_FORMATTERS = { + 'pending': curried.keymap(to_ascii_if_bytes), + 'queued': curried.keymap(to_ascii_if_bytes), +} + + +transaction_pool_inspect_formatter = apply_formatters_to_dict( + TRANSACTION_POOL_INSPECT_FORMATTERS +) + +STORAGE_PROOF_FORMATTERS = { + 'key': HexBytes, + 'value': HexBytes, + 'proof': apply_list_to_array_formatter(HexBytes), +} + +ACCOUNT_PROOF_FORMATTERS = { + 'address': to_checksum_address, + 'accountProof': apply_list_to_array_formatter(HexBytes), + 'balance': to_integer_if_hex, + 'codeHash': to_hexbytes(32), + 'nonce': to_integer_if_hex, + 'storageHash': to_hexbytes(32), + 'storageProof': apply_list_to_array_formatter( + apply_formatters_to_dict(STORAGE_PROOF_FORMATTERS) + ) +} + +proof_formatter = apply_formatters_to_dict(ACCOUNT_PROOF_FORMATTERS) + +FILTER_PARAMS_FORMATTERS = { + 'fromBlock': apply_formatter_if(is_integer, integer_to_hex), + 'toBlock': apply_formatter_if(is_integer, integer_to_hex), +} + + +filter_params_formatter = apply_formatters_to_dict(FILTER_PARAMS_FORMATTERS) + + +filter_result_formatter = apply_one_of_formatters(( + (is_array_of_dicts, apply_list_to_array_formatter(log_entry_formatter)), + (is_array_of_strings, apply_list_to_array_formatter(to_hexbytes(32))), +)) + + +transaction_param_formatter = compose( + remove_key_if('to', lambda txn: txn['to'] in {'', b'', None}), +) + + +estimate_gas_without_block_id = apply_formatter_at_index(transaction_param_formatter, 0) +estimate_gas_with_block_id = apply_formatters_to_sequence([ + transaction_param_formatter, + block_number_formatter, +]) + +SIGNED_TX_FORMATTER = { + 'raw': HexBytes, + 'tx': transaction_formatter, +} + +signed_tx_formatter = apply_formatters_to_dict(SIGNED_TX_FORMATTER) + +FILTER_PARAM_NORMALIZERS = apply_formatters_to_dict({ + 'address': apply_formatter_if(is_string, lambda x: [x]) +}) + +PYTHONIC_REQUEST_FORMATTERS = { + # Eth + 'eth_getBalance': apply_formatter_at_index(block_number_formatter, 1), + 'eth_getBlockByNumber': apply_formatter_at_index(block_number_formatter, 0), + 'eth_getBlockTransactionCountByNumber': apply_formatter_at_index( + block_number_formatter, + 0, + ), + 'eth_getCode': apply_formatter_at_index(block_number_formatter, 1), + 'eth_getStorageAt': apply_formatter_at_index(block_number_formatter, 2), + 'eth_getTransactionByBlockNumberAndIndex': compose( + apply_formatter_at_index(block_number_formatter, 0), + apply_formatter_at_index(integer_to_hex, 1), + ), + 'eth_getTransactionCount': apply_formatter_at_index(block_number_formatter, 1), + 'eth_getUncleCountByBlockNumber': apply_formatter_at_index(block_number_formatter, 0), + 'eth_getUncleByBlockNumberAndIndex': compose( + apply_formatter_at_index(block_number_formatter, 0), + apply_formatter_at_index(integer_to_hex, 1), + ), + 'eth_getUncleByBlockHashAndIndex': apply_formatter_at_index(integer_to_hex, 1), + 'eth_newFilter': apply_formatter_at_index(filter_params_formatter, 0), + 'eth_getLogs': apply_formatter_at_index(filter_params_formatter, 0), + 'eth_call': apply_formatters_to_sequence([ + transaction_param_formatter, + block_number_formatter, + ]), + 'eth_estimateGas': apply_one_of_formatters(( + (is_length(1), estimate_gas_without_block_id), + (is_length(2), estimate_gas_with_block_id), + )), + 'eth_sendTransaction': apply_formatter_at_index(transaction_param_formatter, 0), + 'eth_getProof': apply_formatter_at_index(block_number_formatter, 2), + # personal + 'personal_importRawKey': apply_formatter_at_index( + compose(remove_0x_prefix, hexstr_if_str(to_hex)), + 0, + ), + 'personal_sign': apply_formatter_at_index(text_if_str(to_hex), 0), + 'personal_ecRecover': apply_formatter_at_index(text_if_str(to_hex), 0), + 'personal_sendTransaction': apply_formatter_at_index(transaction_param_formatter, 0), + # Snapshot and Revert + 'evm_revert': apply_formatter_at_index(integer_to_hex, 0), + 'trace_replayBlockTransactions': apply_formatter_at_index(block_number_formatter, 0), + 'trace_block': apply_formatter_at_index(block_number_formatter, 0), + 'trace_call': compose( + apply_formatter_at_index(transaction_param_formatter, 0), + apply_formatter_at_index(block_number_formatter, 2) + ), +} + + +PYTHONIC_RESULT_FORMATTERS = { + # Eth + 'eth_accounts': apply_list_to_array_formatter(to_checksum_address), + 'eth_blockNumber': to_integer_if_hex, + 'eth_chainId': to_integer_if_hex, + 'eth_coinbase': to_checksum_address, + 'eth_estimateGas': to_integer_if_hex, + 'eth_gasPrice': to_integer_if_hex, + 'eth_getBalance': to_integer_if_hex, + 'eth_getBlockByHash': apply_formatter_if(is_not_null, block_formatter), + 'eth_getBlockByNumber': apply_formatter_if(is_not_null, block_formatter), + 'eth_getBlockTransactionCountByHash': to_integer_if_hex, + 'eth_getBlockTransactionCountByNumber': to_integer_if_hex, + 'eth_getCode': HexBytes, + 'eth_getFilterChanges': filter_result_formatter, + 'eth_getFilterLogs': filter_result_formatter, + 'eth_getLogs': filter_result_formatter, + 'eth_getProof': apply_formatter_if(is_not_null, proof_formatter), + 'eth_getStorageAt': HexBytes, + 'eth_getTransactionByBlockHashAndIndex': apply_formatter_if( + is_not_null, + transaction_formatter, + ), + 'eth_getTransactionByBlockNumberAndIndex': apply_formatter_if( + is_not_null, + transaction_formatter, + ), + 'eth_getTransactionByHash': apply_formatter_if(is_not_null, transaction_formatter), + 'eth_getTransactionCount': to_integer_if_hex, + 'eth_getTransactionReceipt': apply_formatter_if( + is_not_null, + receipt_formatter, + ), + 'eth_getUncleCountByBlockHash': to_integer_if_hex, + 'eth_getUncleCountByBlockNumber': to_integer_if_hex, + 'eth_hashrate': to_integer_if_hex, + 'eth_protocolVersion': compose( + apply_formatter_if(is_integer, str), + to_integer_if_hex, + ), + 'eth_sendRawTransaction': to_hexbytes(32), + 'eth_sendTransaction': to_hexbytes(32), + 'eth_sign': HexBytes, + 'eth_signTransaction': apply_formatter_if(is_not_null, signed_tx_formatter), + 'eth_signTypedData': HexBytes, + 'eth_syncing': apply_formatter_if(is_not_false, syncing_formatter), + # personal + 'personal_importRawKey': to_checksum_address, + 'personal_listAccounts': apply_list_to_array_formatter(to_checksum_address), + 'personal_newAccount': to_checksum_address, + 'personal_sendTransaction': to_hexbytes(32), + 'personal_signTypedData': HexBytes, + # SHH + 'shh_getFilterMessages': apply_list_to_array_formatter(whisper_log_formatter), + # Transaction Pool + 'txpool_content': transaction_pool_content_formatter, + 'txpool_inspect': transaction_pool_inspect_formatter, + # Snapshot and Revert + 'evm_snapshot': hex_to_integer, + # Net + 'net_peerCount': to_integer_if_hex, +} + + +ATTRDICT_FORMATTER = { + '*': apply_formatter_if(is_dict and not_attrdict, AttributeDict.recursive) +} + +METHOD_NORMALIZERS = { + 'eth_getLogs': apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0), + 'eth_newFilter': apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0) +} + +STANDARD_NORMALIZERS = [ + abi_bytes_to_hex, + abi_int_to_hex, + abi_string_to_hex, + abi_address_to_hex, +] + + +ABI_REQUEST_FORMATTERS = abi_request_formatters(STANDARD_NORMALIZERS, RPC_ABIS) + + +@to_tuple +def combine_formatters(formatter_maps, method_name): + for formatter_map in formatter_maps: + if method_name in formatter_map: + yield formatter_map[method_name] + + +def get_request_formatters(method_name): + request_formatter_maps = ( + METHOD_NORMALIZERS, + PYTHONIC_REQUEST_FORMATTERS, + ABI_REQUEST_FORMATTERS + ) + formatters = combine_formatters(request_formatter_maps, method_name) + return compose(*formatters) + + +def get_result_formatters(method_name): + formatters = combine_formatters( + (PYTHONIC_RESULT_FORMATTERS,), + method_name + ) + attrdict_formatter = apply_formatter_if(is_dict and not_attrdict, AttributeDict.recursive) + + return compose(*formatters, attrdict_formatter) + + +def get_error_formatters(method_name): + # Note error formatters work on the full response dict + # TODO - test this function + error_formatter_maps = () + formatters = combine_formatters(error_formatter_maps, method_name) + + return compose(*formatters) diff --git a/web3/manager.py b/web3/manager.py index bb0d186794..793f7eb988 100644 --- a/web3/manager.py +++ b/web3/manager.py @@ -1,6 +1,10 @@ import logging import uuid +from eth_utils.toolz import ( + pipe, +) + from web3._utils.decorators import ( deprecated_for, ) @@ -25,6 +29,17 @@ ) +def apply_error_formatters( + error_formatters, + response): + + if 'error' in response and error_formatters: + formatted_response = pipe(response, error_formatters) + return formatted_response + else: + return response + + class RequestManager: logger = logging.getLogger("web3.RequestManager") @@ -60,14 +75,14 @@ def default_middlewares(web3): Leaving ens unspecified will prevent the middleware from resolving names. """ return [ - (request_parameter_normalizer, 'request_param_normalizer'), - (gas_price_strategy_middleware, 'gas_price_strategy'), - (name_to_address_middleware(web3), 'name_to_address'), - (attrdict_middleware, 'attrdict'), - (pythonic_middleware, 'pythonic'), - (normalize_errors_middleware, 'normalize_errors'), - (validation_middleware, 'validation'), - (abi_middleware, 'abi'), + (request_parameter_normalizer, 'request_param_normalizer'), # Delete + (gas_price_strategy_middleware, 'gas_price_strategy'), # Add Async + (name_to_address_middleware(web3), 'name_to_address'), # Add Async + (attrdict_middleware, 'attrdict'), # Delete + (pythonic_middleware, 'pythonic'), # Delete + (normalize_errors_middleware, 'normalize_errors'), # Add async + (validation_middleware, 'validation'), # Add async + (abi_middleware, 'abi'), # Delete ] # @@ -87,24 +102,26 @@ async def _coro_make_request(self, method, params): self.logger.debug("Making request. Method: %s", method) return await request_func(method, params) - def request_blocking(self, method, params): + def request_blocking(self, method, params, error_formatters=None): """ Make a synchronous request using the provider """ response = self._make_request(method, params) if "error" in response: + apply_error_formatters(error_formatters, response) raise ValueError(response["error"]) return response['result'] - async def coro_request(self, method, params): + async def coro_request(self, method, params, error_formatters=None): """ Couroutine for making a request using the provider """ response = await self._coro_make_request(method, params) if "error" in response: + apply_error_formatters(error_formatters, response) raise ValueError(response["error"]) if response['result'] is None: diff --git a/web3/method.py b/web3/method.py index 6888d79a1e..2d0816779c 100644 --- a/web3/method.py +++ b/web3/method.py @@ -1,14 +1,27 @@ import functools import warnings -from eth_utils import ( +from eth_utils.curried import ( to_tuple, ) from eth_utils.toolz import ( - identity, pipe, ) +from web3._utils.method_formatters import ( + get_error_formatters, + get_request_formatters, + get_result_formatters, +) + + +@to_tuple +def _apply_request_formatters(params, request_formatters): + if request_formatters: + formatted_params = pipe(params, request_formatters) + return formatted_params + return params + def _munger_star_apply(fn): @functools.wraps(fn) @@ -17,10 +30,6 @@ def inner(args): return inner -def get_default_formatters(*args, **kwargs): - return ([identity], [identity],) - - def default_munger(module, *args, **kwargs): if not args and not kwargs: return tuple() @@ -64,12 +73,8 @@ def getBalance_root_munger(module, account, block_identifier=None): method inputs are passed to the method selection function, and the returned method string is used. - 3. request and response formatters are retrieved - formatters are retrieved - using the json rpc method string. The lookup function provided by the - formatter_lookup_fn configuration is passed the method string and is - expected to return a 2-tuple of lists containing the - request_formatters and response_formatters in that order. - e.g. ([*request_formatters], [*response_formatters]). + 3. request and response formatters are set - formatters are retrieved + using the json rpc method string. 4. After the parameter processing from steps 1-3 the request is made using the calling function returned by the module attribute ``retrieve_caller_fn`` @@ -79,12 +84,16 @@ def __init__( self, json_rpc_method=None, mungers=None, - formatter_lookup_fn=None, + request_formatters=None, + result_formatters=None, + error_formatters=None, web3=None): self.json_rpc_method = json_rpc_method self.mungers = mungers or [default_munger] - self.formatter_lookup_fn = formatter_lookup_fn or get_default_formatters + self.request_formatters = request_formatters or get_request_formatters + self.result_formatters = result_formatters or get_result_formatters + self.error_formatters = get_error_formatters def __get__(self, obj=None, obj_type=None): if obj is None: @@ -104,21 +113,14 @@ def method_selector_fn(self): return lambda *_: self.json_rpc_method raise ValueError("``json_rpc_method`` config invalid. May be a string or function") - def get_formatters(self, method_string): - """Lookup the request formatters for the rpc_method - - The lookup_fn output is expected to be a 2 length tuple of lists of - the request and output formatters, respectively. - """ - formatters = self.formatter_lookup_fn(method_string) - return formatters or get_default_formatters() - - def input_munger(self, val): - try: - module, args, kwargs = val - except TypeError: - raise ValueError("input_munger expects a 3-tuple") - + def input_munger(self, module, args, kwargs): + # This function takes the "root_munger" - the first munger in + # the list of mungers) and then pipes the return value of the + # previous munger as an argument to the next munger to return + # an array of arguments that have been formatted. + # See the test_process_params test + # in tests/core/method-class/test_method.py for an example + # with multiple mungers. # TODO: Create friendly error output. mungers_iter = iter(self.mungers) root_munger = next(mungers_iter) @@ -129,28 +131,13 @@ def input_munger(self, val): return munged_inputs def process_params(self, module, *args, **kwargs): - # takes in input params, steps 1-3 - params, method, (req_formatters, ret_formatters) = _pipe_and_accumulate( - (module, args, kwargs,), - [self.input_munger, self.method_selector_fn, self.get_formatters]) - - return (method, pipe(params, *req_formatters)), ret_formatters - + params = self.input_munger(module, args, kwargs) + method = self.method_selector_fn() + response_formatters = (self.result_formatters(method), self.error_formatters(method)) -@to_tuple -def _pipe_and_accumulate(val, fns): - """pipes val through a list of fns while accumulating results from - each function, returning a tuple. - - e.g.: + request = (method, _apply_request_formatters(params, self.request_formatters(method))) - >>> _pipe_and_accumulate([lambda x: x**2, lambda x: x*10], 5) - (25, 250) - - """ - for fn in fns: - val = fn(val) - yield val + return request, response_formatters class DeprecatedMethod(): diff --git a/web3/middleware/abi.py b/web3/middleware/abi.py index cfcae4e8cd..97e2063df1 100644 --- a/web3/middleware/abi.py +++ b/web3/middleware/abi.py @@ -1,26 +1,11 @@ -from web3._utils.normalizers import ( - abi_address_to_hex, - abi_bytes_to_hex, - abi_int_to_hex, - abi_string_to_hex, -) -from web3._utils.rpc_abi import ( - RPC_ABIS, - abi_request_formatters, +from web3._utils.method_formatters import ( + ABI_REQUEST_FORMATTERS, ) from .formatting import ( construct_formatting_middleware, ) -STANDARD_NORMALIZERS = [ - abi_bytes_to_hex, - abi_int_to_hex, - abi_string_to_hex, - abi_address_to_hex, -] - - abi_middleware = construct_formatting_middleware( - request_formatters=abi_request_formatters(STANDARD_NORMALIZERS, RPC_ABIS) + request_formatters=ABI_REQUEST_FORMATTERS ) diff --git a/web3/middleware/normalize_request_parameters.py b/web3/middleware/normalize_request_parameters.py index cf2a306d14..93926c99a4 100644 --- a/web3/middleware/normalize_request_parameters.py +++ b/web3/middleware/normalize_request_parameters.py @@ -1,24 +1,11 @@ -from eth_utils import ( - is_string, -) -from eth_utils.curried import ( - apply_formatter_at_index, - apply_formatter_if, - apply_formatters_to_dict, +from web3._utils.method_formatters import ( + METHOD_NORMALIZERS, ) from .formatting import ( construct_formatting_middleware, ) -FILTER_PARAM_NORMALIZERS = apply_formatters_to_dict({ - 'address': apply_formatter_if(is_string, lambda x: [x])}) - -METHOD_NORMALIZERS = { - 'eth_getLogs': apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0), - 'eth_newFilter': apply_formatter_at_index(FILTER_PARAM_NORMALIZERS, 0) -} - request_parameter_normalizer = construct_formatting_middleware( request_formatters=METHOD_NORMALIZERS, ) diff --git a/web3/middleware/pythonic.py b/web3/middleware/pythonic.py index 3eaf84af01..1ffd253e68 100644 --- a/web3/middleware/pythonic.py +++ b/web3/middleware/pythonic.py @@ -1,398 +1,12 @@ -import codecs -import operator - -from eth_utils.curried import ( - apply_formatter_at_index, - apply_formatter_if, - apply_formatters_to_dict, - apply_formatters_to_sequence, - apply_one_of_formatters, - is_address, - is_bytes, - is_integer, - is_null, - is_string, - remove_0x_prefix, - text_if_str, - to_checksum_address, - to_list, -) -from eth_utils.toolz import ( - complement, - compose, - curried, - curry, - partial, -) -from hexbytes import ( - HexBytes, -) - -from web3._utils.abi import ( - is_length, +from web3._utils.method_formatters import ( + PYTHONIC_REQUEST_FORMATTERS, + PYTHONIC_RESULT_FORMATTERS, ) -from web3._utils.encoding import ( - hexstr_if_str, - to_hex, -) -from web3._utils.formatters import ( - apply_formatter_to_array, - hex_to_integer, - integer_to_hex, - is_array_of_dicts, - is_array_of_strings, - remove_key_if, -) - -from .formatting import ( +from web3.middleware.formatting import ( construct_formatting_middleware, ) - -def bytes_to_ascii(value): - return codecs.decode(value, 'ascii') - - -to_ascii_if_bytes = apply_formatter_if(is_bytes, bytes_to_ascii) -to_integer_if_hex = apply_formatter_if(is_string, hex_to_integer) -block_number_formatter = apply_formatter_if(is_integer, integer_to_hex) - - -is_false = partial(operator.is_, False) - -is_not_false = complement(is_false) -is_not_null = complement(is_null) - - -@curry -def to_hexbytes(num_bytes, val, variable_length=False): - if isinstance(val, (str, int, bytes)): - result = HexBytes(val) - else: - raise TypeError("Cannot convert %r to HexBytes" % val) - - extra_bytes = len(result) - num_bytes - if extra_bytes == 0 or (variable_length and extra_bytes < 0): - return result - elif all(byte == 0 for byte in result[:extra_bytes]): - return HexBytes(result[extra_bytes:]) - else: - raise ValueError( - "The value %r is %d bytes, but should be %d" % ( - result, len(result), num_bytes - ) - ) - - -TRANSACTION_FORMATTERS = { - 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), - 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), - 'nonce': to_integer_if_hex, - 'gas': to_integer_if_hex, - 'gasPrice': to_integer_if_hex, - 'value': to_integer_if_hex, - 'from': to_checksum_address, - 'publicKey': apply_formatter_if(is_not_null, to_hexbytes(64)), - 'r': to_hexbytes(32, variable_length=True), - 'raw': HexBytes, - 's': to_hexbytes(32, variable_length=True), - 'to': apply_formatter_if(is_address, to_checksum_address), - 'hash': to_hexbytes(32), - 'v': apply_formatter_if(is_not_null, to_integer_if_hex), - 'standardV': apply_formatter_if(is_not_null, to_integer_if_hex), -} - - -transaction_formatter = apply_formatters_to_dict(TRANSACTION_FORMATTERS) - - -SIGNED_TX_FORMATTER = { - 'raw': HexBytes, - 'tx': transaction_formatter, -} - - -signed_tx_formatter = apply_formatters_to_dict(SIGNED_TX_FORMATTER) - - -WHISPER_LOG_FORMATTERS = { - 'sig': to_hexbytes(130), - 'topic': to_hexbytes(8), - 'payload': HexBytes, - 'padding': apply_formatter_if(is_not_null, HexBytes), - 'hash': to_hexbytes(64), - 'recipientPublicKey': apply_formatter_if(is_not_null, to_hexbytes(130)), -} - - -whisper_log_formatter = apply_formatters_to_dict(WHISPER_LOG_FORMATTERS) - - -def apply_list_to_array_formatter(formatter): - return to_list(apply_formatter_to_array(formatter)) - - -LOG_ENTRY_FORMATTERS = { - 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), - 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), - 'transactionHash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'logIndex': to_integer_if_hex, - 'address': to_checksum_address, - 'topics': apply_list_to_array_formatter(to_hexbytes(32)), - 'data': to_ascii_if_bytes, -} - - -log_entry_formatter = apply_formatters_to_dict(LOG_ENTRY_FORMATTERS) - - -RECEIPT_FORMATTERS = { - 'blockHash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'blockNumber': apply_formatter_if(is_not_null, to_integer_if_hex), - 'transactionIndex': apply_formatter_if(is_not_null, to_integer_if_hex), - 'transactionHash': to_hexbytes(32), - 'cumulativeGasUsed': to_integer_if_hex, - 'status': to_integer_if_hex, - 'gasUsed': to_integer_if_hex, - 'contractAddress': apply_formatter_if(is_not_null, to_checksum_address), - 'logs': apply_list_to_array_formatter(log_entry_formatter), - 'logsBloom': to_hexbytes(256), -} - - -receipt_formatter = apply_formatters_to_dict(RECEIPT_FORMATTERS) - -BLOCK_FORMATTERS = { - 'extraData': to_hexbytes(32, variable_length=True), - 'gasLimit': to_integer_if_hex, - 'gasUsed': to_integer_if_hex, - 'size': to_integer_if_hex, - 'timestamp': to_integer_if_hex, - 'hash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'logsBloom': apply_formatter_if(is_not_null, to_hexbytes(256)), - 'miner': apply_formatter_if(is_not_null, to_checksum_address), - 'mixHash': to_hexbytes(32), - 'nonce': apply_formatter_if(is_not_null, to_hexbytes(8, variable_length=True)), - 'number': apply_formatter_if(is_not_null, to_integer_if_hex), - 'parentHash': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'sha3Uncles': apply_formatter_if(is_not_null, to_hexbytes(32)), - 'uncles': apply_list_to_array_formatter(to_hexbytes(32)), - 'difficulty': to_integer_if_hex, - 'receiptsRoot': to_hexbytes(32), - 'stateRoot': to_hexbytes(32), - 'totalDifficulty': to_integer_if_hex, - 'transactions': apply_one_of_formatters(( - (is_array_of_dicts, apply_list_to_array_formatter(transaction_formatter)), - (is_array_of_strings, apply_list_to_array_formatter(to_hexbytes(32))), - )), - 'transactionsRoot': to_hexbytes(32), -} - - -block_formatter = apply_formatters_to_dict(BLOCK_FORMATTERS) - - -STORAGE_PROOF_FORMATTERS = { - 'key': HexBytes, - 'value': HexBytes, - 'proof': apply_list_to_array_formatter(HexBytes), -} - -ACCOUNT_PROOF_FORMATTERS = { - 'address': to_checksum_address, - 'accountProof': apply_list_to_array_formatter(HexBytes), - 'balance': to_integer_if_hex, - 'codeHash': to_hexbytes(32), - 'nonce': to_integer_if_hex, - 'storageHash': to_hexbytes(32), - 'storageProof': apply_list_to_array_formatter( - apply_formatters_to_dict(STORAGE_PROOF_FORMATTERS) - ) -} - -proof_formatter = apply_formatters_to_dict(ACCOUNT_PROOF_FORMATTERS) - - -SYNCING_FORMATTERS = { - 'startingBlock': to_integer_if_hex, - 'currentBlock': to_integer_if_hex, - 'highestBlock': to_integer_if_hex, - 'knownStates': to_integer_if_hex, - 'pulledStates': to_integer_if_hex, -} - - -syncing_formatter = apply_formatters_to_dict(SYNCING_FORMATTERS) - - -TRANSACTION_POOL_CONTENT_FORMATTERS = { - 'pending': compose( - curried.keymap(to_ascii_if_bytes), - curried.valmap(transaction_formatter), - ), - 'queued': compose( - curried.keymap(to_ascii_if_bytes), - curried.valmap(transaction_formatter), - ), -} - - -transaction_pool_content_formatter = apply_formatters_to_dict( - TRANSACTION_POOL_CONTENT_FORMATTERS -) - - -TRANSACTION_POOL_INSPECT_FORMATTERS = { - 'pending': curried.keymap(to_ascii_if_bytes), - 'queued': curried.keymap(to_ascii_if_bytes), -} - - -transaction_pool_inspect_formatter = apply_formatters_to_dict( - TRANSACTION_POOL_INSPECT_FORMATTERS -) - - -FILTER_PARAMS_FORMATTERS = { - 'fromBlock': apply_formatter_if(is_integer, integer_to_hex), - 'toBlock': apply_formatter_if(is_integer, integer_to_hex), -} - - -filter_params_formatter = apply_formatters_to_dict(FILTER_PARAMS_FORMATTERS) - - -filter_result_formatter = apply_one_of_formatters(( - (is_array_of_dicts, apply_list_to_array_formatter(log_entry_formatter)), - (is_array_of_strings, apply_list_to_array_formatter(to_hexbytes(32))), -)) - - -transaction_param_formatter = compose( - remove_key_if('to', lambda txn: txn['to'] in {'', b'', None}), -) - - -estimate_gas_without_block_id = apply_formatter_at_index(transaction_param_formatter, 0) -estimate_gas_with_block_id = apply_formatters_to_sequence([ - transaction_param_formatter, - block_number_formatter, -]) - - pythonic_middleware = construct_formatting_middleware( - request_formatters={ - # Eth - 'eth_getBalance': apply_formatter_at_index(block_number_formatter, 1), - 'eth_getBlockByNumber': apply_formatter_at_index(block_number_formatter, 0), - 'eth_getBlockTransactionCountByNumber': apply_formatter_at_index( - block_number_formatter, - 0, - ), - 'eth_getCode': apply_formatter_at_index(block_number_formatter, 1), - 'eth_getStorageAt': apply_formatter_at_index(block_number_formatter, 2), - 'eth_getProof': apply_formatter_at_index(block_number_formatter, 2), - 'eth_getTransactionByBlockNumberAndIndex': compose( - apply_formatter_at_index(block_number_formatter, 0), - apply_formatter_at_index(integer_to_hex, 1), - ), - 'eth_getTransactionCount': apply_formatter_at_index(block_number_formatter, 1), - 'eth_getUncleCountByBlockNumber': apply_formatter_at_index(block_number_formatter, 0), - 'eth_getUncleByBlockNumberAndIndex': compose( - apply_formatter_at_index(block_number_formatter, 0), - apply_formatter_at_index(integer_to_hex, 1), - ), - 'eth_getUncleByBlockHashAndIndex': apply_formatter_at_index(integer_to_hex, 1), - 'eth_newFilter': apply_formatter_at_index(filter_params_formatter, 0), - 'eth_getLogs': apply_formatter_at_index(filter_params_formatter, 0), - 'eth_call': apply_formatters_to_sequence([ - transaction_param_formatter, - block_number_formatter, - ]), - 'eth_estimateGas': apply_one_of_formatters(( - (is_length(1), estimate_gas_without_block_id), - (is_length(2), estimate_gas_with_block_id), - )), - 'eth_sendTransaction': apply_formatter_at_index(transaction_param_formatter, 0), - # personal - 'personal_importRawKey': apply_formatter_at_index( - compose(remove_0x_prefix, hexstr_if_str(to_hex)), - 0, - ), - 'personal_sign': apply_formatter_at_index(text_if_str(to_hex), 0), - 'personal_ecRecover': apply_formatter_at_index(text_if_str(to_hex), 0), - 'personal_sendTransaction': apply_formatter_at_index(transaction_param_formatter, 0), - # Snapshot and Revert - 'evm_revert': apply_formatter_at_index(integer_to_hex, 0), - 'trace_replayBlockTransactions': apply_formatter_at_index(block_number_formatter, 0), - 'trace_block': apply_formatter_at_index(block_number_formatter, 0), - 'trace_call': compose( - apply_formatter_at_index(transaction_param_formatter, 0), - apply_formatter_at_index(block_number_formatter, 2) - ), - }, - result_formatters={ - # Eth - 'eth_accounts': apply_list_to_array_formatter(to_checksum_address), - 'eth_blockNumber': to_integer_if_hex, - 'eth_chainId': to_integer_if_hex, - 'eth_coinbase': to_checksum_address, - 'eth_estimateGas': to_integer_if_hex, - 'eth_gasPrice': to_integer_if_hex, - 'eth_getBalance': to_integer_if_hex, - 'eth_getBlockByHash': apply_formatter_if(is_not_null, block_formatter), - 'eth_getBlockByNumber': apply_formatter_if(is_not_null, block_formatter), - 'eth_getBlockTransactionCountByHash': to_integer_if_hex, - 'eth_getBlockTransactionCountByNumber': to_integer_if_hex, - 'eth_getCode': HexBytes, - 'eth_getFilterChanges': filter_result_formatter, - 'eth_getFilterLogs': filter_result_formatter, - 'eth_getLogs': filter_result_formatter, - 'eth_getStorageAt': HexBytes, - 'eth_getProof': apply_formatter_if(is_not_null, proof_formatter), - 'eth_getTransactionByBlockHashAndIndex': apply_formatter_if( - is_not_null, - transaction_formatter, - ), - 'eth_getTransactionByBlockNumberAndIndex': apply_formatter_if( - is_not_null, - transaction_formatter, - ), - 'eth_getTransactionByHash': apply_formatter_if(is_not_null, transaction_formatter), - 'eth_getTransactionCount': to_integer_if_hex, - 'eth_getTransactionReceipt': apply_formatter_if( - is_not_null, - receipt_formatter, - ), - 'eth_getUncleCountByBlockHash': to_integer_if_hex, - 'eth_getUncleCountByBlockNumber': to_integer_if_hex, - 'eth_hashrate': to_integer_if_hex, - 'eth_protocolVersion': compose( - apply_formatter_if(is_integer, str), - to_integer_if_hex, - ), - 'eth_sendRawTransaction': to_hexbytes(32), - 'eth_sendTransaction': to_hexbytes(32), - 'eth_signTransaction': apply_formatter_if(is_not_null, signed_tx_formatter), - 'eth_sign': HexBytes, - 'eth_signTypedData': HexBytes, - 'eth_syncing': apply_formatter_if(is_not_false, syncing_formatter), - # personal - 'personal_importRawKey': to_checksum_address, - 'personal_listAccounts': apply_list_to_array_formatter(to_checksum_address), - 'personal_newAccount': to_checksum_address, - 'personal_signTypedData': HexBytes, - 'personal_sendTransaction': to_hexbytes(32), - # SHH - 'shh_getFilterMessages': apply_list_to_array_formatter(whisper_log_formatter), - # Transaction Pool - 'txpool_content': transaction_pool_content_formatter, - 'txpool_inspect': transaction_pool_inspect_formatter, - # Snapshot and Revert - 'evm_snapshot': hex_to_integer, - # Net - 'net_peerCount': to_integer_if_hex, - }, + request_formatters=PYTHONIC_REQUEST_FORMATTERS, + result_formatters=PYTHONIC_RESULT_FORMATTERS, ) diff --git a/web3/middleware/signing.py b/web3/middleware/signing.py index fb50525112..0867f1b7e1 100644 --- a/web3/middleware/signing.py +++ b/web3/middleware/signing.py @@ -22,6 +22,9 @@ compose, ) +from web3._utils.method_formatters import ( + STANDARD_NORMALIZERS, +) from web3._utils.rpc_abi import ( TRANSACTION_PARAMS_ABIS, apply_abi_formatters_to_dict, @@ -31,10 +34,6 @@ fill_transaction_defaults, ) -from .abi import ( - STANDARD_NORMALIZERS, -) - to_hexstr_from_eth_key = operator.methodcaller('to_hex') diff --git a/web3/module.py b/web3/module.py index 45d3b3bd98..dc99f0a9dd 100644 --- a/web3/module.py +++ b/web3/module.py @@ -4,22 +4,32 @@ ) +@curry +def apply_result_formatters(result_formatters, result): + if result_formatters: + formatted_result = pipe(result, result_formatters) + return formatted_result + else: + return result + + @curry def retrieve_blocking_method_call_fn(w3, module, method): def caller(*args, **kwargs): - (method_str, params), output_formatters = method.process_params(module, *args, **kwargs) - return pipe( - w3.manager.request_blocking(method_str, params), - *output_formatters) + (method_str, params), response_formatters = method.process_params(module, *args, **kwargs) + result_formatters, error_formatters = response_formatters + result = w3.manager.request_blocking(method_str, params, error_formatters) + return apply_result_formatters(result_formatters, result) return caller @curry def retrieve_async_method_call_fn(w3, module, method): async def caller(*args, **kwargs): - (method_str, params), output_formatters = method.process_params(module, *args, **kwargs) - raw_result = await w3.manager.coro_request(method_str, params) - return pipe(raw_result, *output_formatters) + (method_str, params), response_formatters = method.process_params(module, *args, **kwargs) + result_formatters, error_formatters = response_formatters + result = await w3.manager.coro_request(method_str, params, error_formatters) + return apply_result_formatters(result_formatters, result) return caller