diff --git a/newsfragments/1957.feature.rst b/newsfragments/1957.feature.rst new file mode 100644 index 0000000000..de1f8a9bab --- /dev/null +++ b/newsfragments/1957.feature.rst @@ -0,0 +1 @@ +Add ``parity.trace_call``, deprecate ``parity.traceCall`` diff --git a/web3/_utils/module_testing/parity_module.py b/web3/_utils/module_testing/parity_module.py index a171815c26..50eca0d0e8 100644 --- a/web3/_utils/module_testing/parity_module.py +++ b/web3/_utils/module_testing/parity_module.py @@ -95,6 +95,23 @@ def test_trace_transaction(self, web3: "Web3", parity_fixture_data: Dict[str, st trace = web3.parity.traceTransaction(HexStr(parity_fixture_data['mined_txn_hash'])) assert trace[0]['action']['from'] == add_0x_prefix(HexStr(parity_fixture_data['coinbase'])) + def test_traceCall_deprecated( + self, web3: "Web3", math_contract: "Contract", math_contract_address: ChecksumAddress + ) -> None: + coinbase = web3.eth.coinbase + txn_params = math_contract._prepare_transaction( + fn_name='add', + fn_args=(7, 11), + transaction={'from': coinbase, 'to': math_contract_address}, + ) + with pytest.warns(DeprecationWarning, + match="traceCall is deprecated in favor of trace_call"): + trace = web3.parity.traceCall(txn_params) + assert trace['stateDiff'] is None + assert trace['vmTrace'] is None + result = hex_to_integer(trace['output']) + assert result == 18 + def test_trace_call( self, web3: "Web3", math_contract: "Contract", math_contract_address: ChecksumAddress ) -> None: @@ -104,13 +121,13 @@ def test_trace_call( fn_args=(7, 11), transaction={'from': coinbase, 'to': math_contract_address}, ) - trace = web3.parity.traceCall(txn_params) + trace = web3.parity.trace_call(txn_params) assert trace['stateDiff'] is None assert trace['vmTrace'] is None result = hex_to_integer(trace['output']) assert result == 18 - def test_eth_call_with_0_result( + def test_trace_call_with_0_result( self, web3: "Web3", math_contract: "Contract", math_contract_address: ChecksumAddress ) -> None: coinbase = web3.eth.coinbase @@ -119,7 +136,7 @@ def test_eth_call_with_0_result( fn_args=(0, 0), transaction={'from': coinbase, 'to': math_contract_address}, ) - trace = web3.parity.traceCall(txn_params) + trace = web3.parity.trace_call(txn_params) assert trace['stateDiff'] is None assert trace['vmTrace'] is None result = hex_to_integer(trace['output']) diff --git a/web3/parity.py b/web3/parity.py index b82d7c367f..dab71195bc 100644 --- a/web3/parity.py +++ b/web3/parity.py @@ -195,7 +195,7 @@ def trace_call_munger( return (transaction, mode, block_identifier) - traceCall: Method[Callable[..., ParityBlockTrace]] = Method( + trace_call: Method[Callable[..., ParityBlockTrace]] = Method( RPC.trace_call, mungers=[trace_call_munger], ) @@ -234,3 +234,4 @@ def trace_transactions_munger( traceReplayBlockTransactions = DeprecatedMethod(trace_replay_block_transactions, 'traceReplayBlockTransactions', 'trace_replay_block_transactions') + traceCall = DeprecatedMethod(trace_call, 'traceCall', 'trace_call') diff --git a/web3/providers/async_base.py b/web3/providers/async_base.py new file mode 100644 index 0000000000..7eb52887bf --- /dev/null +++ b/web3/providers/async_base.py @@ -0,0 +1,78 @@ +class AsyncBaseProvider: + _middlewares: Tuple[Middleware, ...] = () + # a tuple of (all_middlewares, request_func) + _request_func_cache: Tuple[Tuple[Middleware, ...], Callable[..., RPCResponse]] = (None, None) + + @property + def middlewares(self) -> Tuple[Middleware, ...]: + return self._middlewares + + @middlewares.setter + def middlewares( + self, values: MiddlewareOnion + ) -> None: + # tuple(values) converts to MiddlewareOnion -> Tuple[Middleware, ...] + self._middlewares = tuple(values) # type: ignore + + def request_func( + self, web3: "Web3", outer_middlewares: MiddlewareOnion + ) -> Callable[..., RPCResponse]: + """ + @param outer_middlewares is an iterable of middlewares, ordered by first to execute + @returns a function that calls all the middleware and eventually self.make_request() + """ + # type ignored b/c tuple(MiddlewareOnion) converts to tuple of middlewares + all_middlewares: Tuple[Middleware] = tuple(outer_middlewares) + tuple(self.middlewares) # type: ignore # noqa: E501 + + cache_key = self._request_func_cache[0] + if cache_key is None or cache_key != all_middlewares: + self._request_func_cache = ( + all_middlewares, + self._generate_request_func(web3, all_middlewares) + ) + return self._request_func_cache[-1] + + def _generate_request_func( + self, web3: "Web3", middlewares: Sequence[Middleware] + ) -> Callable[..., RPCResponse]: + return combine_middlewares( + middlewares=middlewares, + web3=web3, + provider_request_fn=self.make_request, + ) + + async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: + raise NotImplementedError("Providers must implement this method") + + async def isConnected(self) -> bool: + raise NotImplementedError("Providers must implement this method") + + +class AsyncJSONBaseProvider(AsyncBaseProvider): + def __init__(self) -> None: + self.request_counter = itertools.count() + + async def decode_rpc_response(self, raw_response: bytes) -> RPCResponse: + text_response = to_text(raw_response) + return await cast(RPCResponse, FriendlyJsonSerde().json_decode(text_response)) + + async def encode_rpc_request(self, method: RPCEndpoint, params: Any) -> bytes: + rpc_dict = { + "jsonrpc": "2.0", + "method": method, + "params": params or [], + "id": next(self.request_counter), + } + encoded = FriendlyJsonSerde().json_encode(rpc_dict) + return await to_bytes(text=encoded) + + def isConnected(self) -> bool: + try: + response = self.make_request(RPCEndpoint('web3_clientVersion'), []) + except IOError: + return False + + assert response['jsonrpc'] == '2.0' + assert 'error' not in response + + return True