web3 6.18.0__tar.gz → 6.20.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {web3-6.18.0 → web3-6.20.0}/PKG-INFO +1 -1
- {web3-6.18.0 → web3-6.20.0}/pyproject.toml +1 -1
- {web3-6.18.0 → web3-6.20.0}/setup.py +1 -1
- {web3-6.18.0 → web3-6.20.0}/web3/__init__.py +21 -3
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/module_testing_utils.py +13 -0
- {web3-6.18.0 → web3-6.20.0}/web3/exceptions.py +19 -1
- {web3-6.18.0 → web3-6.20.0}/web3/main.py +5 -8
- {web3-6.18.0 → web3-6.20.0}/web3/manager.py +19 -8
- {web3-6.18.0 → web3-6.20.0}/web3/providers/__init__.py +19 -0
- web3-6.20.0/web3/providers/persistent.py +214 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/websocket/request_processor.py +40 -5
- web3-6.20.0/web3/providers/websocket/websocket_v2.py +146 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/deployer.py +1 -1
- {web3-6.18.0 → web3-6.20.0}/web3/types.py +27 -1
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/PKG-INFO +1 -1
- web3-6.18.0/web3/providers/persistent.py +0 -55
- web3-6.18.0/web3/providers/websocket/websocket_v2.py +0 -242
- {web3-6.18.0 → web3-6.20.0}/LICENSE +0 -0
- {web3-6.18.0 → web3-6.20.0}/MANIFEST.in +0 -0
- {web3-6.18.0 → web3-6.20.0}/README.md +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/_normalization.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/abis.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/async_ens.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/auto.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/base_ens.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/constants.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/contract_data.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/ens.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/exceptions.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/specs/nf.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/specs/normalization_spec.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ens/utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/backend.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/cache.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/chains.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/deployments.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/ipfs.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/protobuf/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/protobuf/ipfs_file_pb2.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/_utils/registry.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/ens/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/escrow/with_bytecode_v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/ipfs_file.proto +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/owned/output_v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/owned/with_contract_type_v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/Authority.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/IndexedOrderedSetLib.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/PackageDB.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/PackageRegistry.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/PackageRegistryInterface.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/ReleaseDB.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/contracts/ReleaseValidator.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/solc_input.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/solc_output.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/registry/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/safe-math-lib/v3-strict-no-deployments.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/contracts/Ownable.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/contracts/PackageRegistry.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/contracts/PackageRegistryInterface.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/solc_input.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/solc_output.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/simple-registry/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/standard-token/output_v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/standard-token/with_bytecode_v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/vyper_registry/0.1.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/vyper_registry/registry.vy +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/assets/vyper_registry/registry_with_delete.vy +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/backends/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/backends/base.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/backends/http.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/backends/ipfs.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/backends/registry.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/constants.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/dependencies.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/deployments.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/contracts/Escrow.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/contracts/SafeSendLib.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/escrow/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/owned/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/owned/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/owned/contracts/Owned.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/owned/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/owned/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/piper-coin/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/piper-coin/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/piper-coin/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/piper-coin/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/safe-math-lib/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/safe-math-lib/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/safe-math-lib/contracts/SafeMathLib.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/safe-math-lib/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/safe-math-lib/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/contracts/AbstractToken.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/contracts/StandardToken.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/standard-token/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/transferable/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/transferable/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/transferable/contracts/Transferable.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/transferable/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/transferable/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet/contracts/Wallet.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet-with-send/1.0.0-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet-with-send/1.0.0.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet-with-send/contracts/WalletWithSend.sol +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet-with-send/v3-pretty.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/examples/wallet-with-send/v3.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/spec/package.spec.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/ethpm-spec/spec/v3.spec.json +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/exceptions.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/package.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/tools/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/tools/builder.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/tools/checker.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/tools/get_manifest.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/uri.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/validation/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/validation/manifest.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/validation/misc.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/validation/package.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/ethpm/validation/uri.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/setup.cfg +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/abi.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/async_caching.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/async_transactions.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/blocks.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/caching.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/compat/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/compile_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/_custom_contract_data.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/address_reflector.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/arrays_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/bytes_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/constructor_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/contract_caller_tester.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/emitter_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/event_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/extended_resolver.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/fallback_function_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/math_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/offchain_lookup.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/offchain_resolver.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/panic_errors_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/payable_tester.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/receive_function_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/reflector_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/revert_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/simple_resolver.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/storage_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/string_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contract_sources/contract_data/tuple_contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/contracts.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/datatypes.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/decorators.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/empty.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/encoding.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/ens.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/error_formatters_utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/events.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/fee_utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/filters.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/formatters.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/function_identifiers.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/http.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/hypothesis.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/math.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/method_formatters.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/miner.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/eth_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/go_ethereum_admin_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/go_ethereum_personal_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/go_ethereum_txpool_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/net_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/persistent_connection_provider.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/module_testing/web3_module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/normalizers.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/request.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/rpc_abi.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/threads.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/transactions.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/type_conversion.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/utility_methods.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/validation.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/_utils/windows.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/auto/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/auto/gethdev.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/beacon/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/beacon/api_endpoints.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/beacon/async_beacon.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/beacon/main.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/constants.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/contract/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/contract/async_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/contract/base_contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/contract/contract.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/contract/utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/datastructures.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/eth/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/eth/async_eth.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/eth/base_eth.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/eth/eth.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/gas_strategies/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/gas_strategies/rpc.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/gas_strategies/time_based.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/geth.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/logs.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/method.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/abi.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/async_cache.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/attrdict.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/buffered_gas_estimate.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/cache.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/exception_handling.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/exception_retry_request.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/filter.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/fixture.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/formatting.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/gas_price_strategy.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/geth_poa.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/names.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/normalize_request_parameters.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/pythonic.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/signing.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/simulate_unmined_transaction.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/stalecheck.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/middleware/validation.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/module.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/net.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/pm.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/async_base.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/async_rpc.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/auto.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/base.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/eth_tester/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/eth_tester/defaults.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/eth_tester/main.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/eth_tester/middleware.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/ipc.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/rpc.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/websocket/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/websocket/websocket.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/providers/websocket/websocket_connection.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/py.typed +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/scripts/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/scripts/release/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/scripts/release/test_package.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/testing.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/benchmark/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/benchmark/main.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/benchmark/node.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/benchmark/reporting.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/benchmark/utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/_utils.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/exceptions.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/linker.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tools/pytest_ethereum/plugins.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/tracing.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/__init__.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/abi.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/address.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/async_exception_handling.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/caching.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3/utils/exception_handling.py +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/SOURCES.txt +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/dependency_links.txt +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/entry_points.txt +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/not-zip-safe +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/requires.txt +0 -0
- {web3-6.18.0 → web3-6.20.0}/web3.egg-info/top_level.txt +0 -0
|
@@ -34,7 +34,7 @@ log_date_format = "%m-%d %H:%M:%S"
|
|
|
34
34
|
[tool.towncrier]
|
|
35
35
|
# Read https://github.com/ethereum/web3.py/blob/main/newsfragments/README.md for instructions
|
|
36
36
|
package = "web3"
|
|
37
|
-
filename = "docs/
|
|
37
|
+
filename = "docs/release_notes.rst"
|
|
38
38
|
directory = "newsfragments"
|
|
39
39
|
underlines = ["-", "~", "^"]
|
|
40
40
|
title_format = "web3.py v{version} ({project_date})"
|
|
@@ -51,7 +51,7 @@ with open("./README.md") as readme:
|
|
|
51
51
|
setup(
|
|
52
52
|
name="web3",
|
|
53
53
|
# *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility.
|
|
54
|
-
version="6.
|
|
54
|
+
version="6.20.0",
|
|
55
55
|
description="""web3.py""",
|
|
56
56
|
long_description_content_type="text/markdown",
|
|
57
57
|
long_description=long_description,
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
from eth_account import Account # noqa: E402
|
|
2
2
|
import sys
|
|
3
3
|
|
|
4
|
+
from web3.providers import (
|
|
5
|
+
AsyncBaseProvider,
|
|
6
|
+
AutoProvider,
|
|
7
|
+
BaseProvider,
|
|
8
|
+
JSONBaseProvider,
|
|
9
|
+
PersistentConnectionProvider,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
if sys.version_info.major == 3 and sys.version_info.minor < 8:
|
|
5
14
|
import pkg_resources
|
|
6
15
|
|
|
@@ -19,6 +28,7 @@ from web3.providers.async_rpc import ( # noqa: E402
|
|
|
19
28
|
AsyncHTTPProvider,
|
|
20
29
|
)
|
|
21
30
|
from web3.providers.eth_tester import ( # noqa: E402
|
|
31
|
+
AsyncEthereumTesterProvider,
|
|
22
32
|
EthereumTesterProvider,
|
|
23
33
|
)
|
|
24
34
|
from web3.providers.ipc import ( # noqa: E402
|
|
@@ -35,13 +45,21 @@ from web3.providers.websocket import ( # noqa: E402
|
|
|
35
45
|
|
|
36
46
|
__all__ = [
|
|
37
47
|
"__version__",
|
|
48
|
+
"Account",
|
|
49
|
+
# web3:
|
|
38
50
|
"AsyncWeb3",
|
|
39
51
|
"Web3",
|
|
52
|
+
# providers:
|
|
53
|
+
"AsyncBaseProvider",
|
|
54
|
+
"AsyncEthereumTesterProvider",
|
|
55
|
+
"AsyncHTTPProvider",
|
|
56
|
+
"AutoProvider",
|
|
57
|
+
"BaseProvider",
|
|
58
|
+
"EthereumTesterProvider",
|
|
40
59
|
"HTTPProvider",
|
|
41
60
|
"IPCProvider",
|
|
61
|
+
"JSONBaseProvider",
|
|
62
|
+
"PersistentConnectionProvider",
|
|
42
63
|
"WebsocketProvider",
|
|
43
64
|
"WebsocketProviderV2",
|
|
44
|
-
"EthereumTesterProvider",
|
|
45
|
-
"Account",
|
|
46
|
-
"AsyncHTTPProvider",
|
|
47
65
|
]
|
|
@@ -193,6 +193,12 @@ class WebsocketMessageStreamMock:
|
|
|
193
193
|
self.messages = deque(messages) if messages else deque()
|
|
194
194
|
self.raise_exception = raise_exception
|
|
195
195
|
|
|
196
|
+
def __await__(self) -> Generator[Any, Any, "Self"]:
|
|
197
|
+
async def __async_init__() -> "Self":
|
|
198
|
+
return self
|
|
199
|
+
|
|
200
|
+
return __async_init__().__await__()
|
|
201
|
+
|
|
196
202
|
def __aiter__(self) -> "Self":
|
|
197
203
|
return self
|
|
198
204
|
|
|
@@ -205,6 +211,13 @@ class WebsocketMessageStreamMock:
|
|
|
205
211
|
|
|
206
212
|
return self.messages.popleft()
|
|
207
213
|
|
|
214
|
+
@staticmethod
|
|
215
|
+
async def pong() -> Literal[False]:
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
async def connect(self) -> None:
|
|
219
|
+
pass
|
|
220
|
+
|
|
208
221
|
async def send(self, data: bytes) -> None:
|
|
209
222
|
pass
|
|
210
223
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import time
|
|
3
3
|
from typing import (
|
|
4
|
+
TYPE_CHECKING,
|
|
4
5
|
Any,
|
|
5
6
|
Dict,
|
|
6
7
|
Optional,
|
|
@@ -15,6 +16,9 @@ from web3.types import (
|
|
|
15
16
|
BlockData,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import asyncio
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
class Web3Exception(Exception):
|
|
20
24
|
"""
|
|
@@ -341,7 +345,21 @@ class BadResponseFormat(Web3Exception):
|
|
|
341
345
|
Raised when a JSON-RPC response comes back in an unexpected format
|
|
342
346
|
"""
|
|
343
347
|
|
|
344
|
-
|
|
348
|
+
|
|
349
|
+
class TaskNotRunning(Web3Exception):
|
|
350
|
+
"""
|
|
351
|
+
Used to signal between asyncio contexts that a task that is being awaited
|
|
352
|
+
is not currently running.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(
|
|
356
|
+
self, task: "asyncio.Task[Any]", message: Optional[str] = None
|
|
357
|
+
) -> None:
|
|
358
|
+
self.task = task
|
|
359
|
+
if message is None:
|
|
360
|
+
message = f"Task {task} is not running."
|
|
361
|
+
self.message = message
|
|
362
|
+
super().__init__(message)
|
|
345
363
|
|
|
346
364
|
|
|
347
365
|
class MethodUnavailable(Web3Exception):
|
|
@@ -573,12 +573,9 @@ class _PersistentConnectionWeb3(AsyncWeb3):
|
|
|
573
573
|
|
|
574
574
|
# async for w3 in w3.persistent_websocket(provider)
|
|
575
575
|
async def __aiter__(self) -> AsyncIterator[Self]:
|
|
576
|
-
|
|
577
|
-
await self.provider.connect()
|
|
578
|
-
|
|
576
|
+
provider = self.provider
|
|
579
577
|
while True:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
continue
|
|
578
|
+
await provider.connect()
|
|
579
|
+
yield self
|
|
580
|
+
provider.logger.error("Connection interrupted, attempting to reconnect...")
|
|
581
|
+
await provider.disconnect()
|
|
@@ -35,6 +35,7 @@ from web3.exceptions import (
|
|
|
35
35
|
BadResponseFormat,
|
|
36
36
|
MethodUnavailable,
|
|
37
37
|
ProviderConnectionError,
|
|
38
|
+
TaskNotRunning,
|
|
38
39
|
)
|
|
39
40
|
from web3.middleware import (
|
|
40
41
|
abi_middleware,
|
|
@@ -377,14 +378,24 @@ class RequestManager:
|
|
|
377
378
|
raise ProviderConnectionError("No listener found for websocket connection.")
|
|
378
379
|
|
|
379
380
|
while True:
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
381
|
+
try:
|
|
382
|
+
response = await self._request_processor.pop_raw_response(
|
|
383
|
+
subscription=True
|
|
384
|
+
)
|
|
385
|
+
if (
|
|
386
|
+
response is not None
|
|
387
|
+
and response.get("params", {}).get("subscription")
|
|
388
|
+
in self._request_processor.active_subscriptions
|
|
389
|
+
):
|
|
390
|
+
# if response is an active subscription response, process it
|
|
391
|
+
yield await self._process_ws_response(response)
|
|
392
|
+
except TaskNotRunning:
|
|
393
|
+
self._provider._handle_listener_task_exceptions()
|
|
394
|
+
self.logger.error(
|
|
395
|
+
"Message listener background task has stopped unexpectedly. "
|
|
396
|
+
"Stopping message stream."
|
|
397
|
+
)
|
|
398
|
+
raise StopAsyncIteration
|
|
388
399
|
|
|
389
400
|
async def _process_ws_response(self, response: RPCResponse) -> RPCResponse:
|
|
390
401
|
provider = cast(PersistentConnectionProvider, self._provider)
|
|
@@ -8,6 +8,10 @@ from .base import (
|
|
|
8
8
|
BaseProvider,
|
|
9
9
|
JSONBaseProvider,
|
|
10
10
|
)
|
|
11
|
+
from .eth_tester import (
|
|
12
|
+
AsyncEthereumTesterProvider,
|
|
13
|
+
EthereumTesterProvider,
|
|
14
|
+
)
|
|
11
15
|
from .ipc import (
|
|
12
16
|
IPCProvider,
|
|
13
17
|
)
|
|
@@ -24,3 +28,18 @@ from .persistent import (
|
|
|
24
28
|
from .auto import (
|
|
25
29
|
AutoProvider,
|
|
26
30
|
)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"AsyncBaseProvider",
|
|
34
|
+
"AsyncEthereumTesterProvider",
|
|
35
|
+
"AsyncHTTPProvider",
|
|
36
|
+
"AutoProvider",
|
|
37
|
+
"BaseProvider",
|
|
38
|
+
"EthereumTesterProvider",
|
|
39
|
+
"HTTPProvider",
|
|
40
|
+
"IPCProvider",
|
|
41
|
+
"JSONBaseProvider",
|
|
42
|
+
"PersistentConnectionProvider",
|
|
43
|
+
"WebsocketProvider",
|
|
44
|
+
"WebsocketProviderV2",
|
|
45
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from abc import (
|
|
2
|
+
ABC,
|
|
3
|
+
)
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
from typing import (
|
|
7
|
+
Optional,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from websockets import (
|
|
11
|
+
ConnectionClosed,
|
|
12
|
+
WebSocketClientProtocol,
|
|
13
|
+
WebSocketException,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from web3._utils.caching import (
|
|
17
|
+
generate_cache_key,
|
|
18
|
+
)
|
|
19
|
+
from web3.exceptions import (
|
|
20
|
+
ProviderConnectionError,
|
|
21
|
+
TaskNotRunning,
|
|
22
|
+
TimeExhausted,
|
|
23
|
+
)
|
|
24
|
+
from web3.providers.async_base import (
|
|
25
|
+
AsyncJSONBaseProvider,
|
|
26
|
+
)
|
|
27
|
+
from web3.providers.websocket.request_processor import (
|
|
28
|
+
RequestProcessor,
|
|
29
|
+
)
|
|
30
|
+
from web3.types import (
|
|
31
|
+
RPCId,
|
|
32
|
+
RPCResponse,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
DEFAULT_PERSISTENT_CONNECTION_TIMEOUT = 50.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
|
|
39
|
+
logger = logging.getLogger("web3.providers.PersistentConnectionProvider")
|
|
40
|
+
has_persistent_connection = True
|
|
41
|
+
endpoint_uri: Optional[str] = None
|
|
42
|
+
|
|
43
|
+
_max_connection_retries: int = 5
|
|
44
|
+
|
|
45
|
+
_ws: Optional[WebSocketClientProtocol] = None
|
|
46
|
+
_request_processor: RequestProcessor
|
|
47
|
+
_message_listener_task: Optional["asyncio.Task[None]"] = None
|
|
48
|
+
_listen_event: asyncio.Event = asyncio.Event()
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
request_timeout: float = DEFAULT_PERSISTENT_CONNECTION_TIMEOUT,
|
|
53
|
+
subscription_response_queue_size: int = 500,
|
|
54
|
+
request_information_cache_size: int = 500,
|
|
55
|
+
silence_listener_task_exceptions: bool = False,
|
|
56
|
+
) -> None:
|
|
57
|
+
super().__init__()
|
|
58
|
+
self._request_processor = RequestProcessor(
|
|
59
|
+
self,
|
|
60
|
+
subscription_response_queue_size=subscription_response_queue_size,
|
|
61
|
+
request_information_cache_size=request_information_cache_size,
|
|
62
|
+
)
|
|
63
|
+
self.request_timeout = request_timeout
|
|
64
|
+
self.silence_listener_task_exceptions = silence_listener_task_exceptions
|
|
65
|
+
|
|
66
|
+
async def connect(self) -> None:
|
|
67
|
+
_connection_attempts = 0
|
|
68
|
+
_backoff_rate_change = 1.75
|
|
69
|
+
_backoff_time = 1.75
|
|
70
|
+
|
|
71
|
+
while _connection_attempts != self._max_connection_retries:
|
|
72
|
+
try:
|
|
73
|
+
_connection_attempts += 1
|
|
74
|
+
self.logger.info(f"Connecting to: {self.endpoint_uri}")
|
|
75
|
+
await self._provider_specific_connect()
|
|
76
|
+
self._message_listener_task = asyncio.create_task(
|
|
77
|
+
self._message_listener()
|
|
78
|
+
)
|
|
79
|
+
self._message_listener_task.add_done_callback(
|
|
80
|
+
self._message_listener_callback
|
|
81
|
+
)
|
|
82
|
+
self.logger.info(f"Successfully connected to: {self.endpoint_uri}")
|
|
83
|
+
break
|
|
84
|
+
except (WebSocketException, OSError) as e:
|
|
85
|
+
if _connection_attempts == self._max_connection_retries:
|
|
86
|
+
raise ProviderConnectionError(
|
|
87
|
+
f"Could not connect to: {self.endpoint_uri}. "
|
|
88
|
+
f"Retries exceeded max of {self._max_connection_retries}."
|
|
89
|
+
) from e
|
|
90
|
+
self.logger.info(
|
|
91
|
+
f"Could not connect to: {self.endpoint_uri}. "
|
|
92
|
+
f"Retrying in {round(_backoff_time, 1)} seconds.",
|
|
93
|
+
exc_info=True,
|
|
94
|
+
)
|
|
95
|
+
await asyncio.sleep(_backoff_time)
|
|
96
|
+
_backoff_time *= _backoff_rate_change
|
|
97
|
+
|
|
98
|
+
async def disconnect(self) -> None:
|
|
99
|
+
try:
|
|
100
|
+
if self._message_listener_task:
|
|
101
|
+
self._message_listener_task.cancel()
|
|
102
|
+
await self._message_listener_task
|
|
103
|
+
except (asyncio.CancelledError, StopAsyncIteration, ConnectionClosed):
|
|
104
|
+
pass
|
|
105
|
+
finally:
|
|
106
|
+
self._message_listener_task = None
|
|
107
|
+
self.logger.info("Message listener background task successfully shut down.")
|
|
108
|
+
|
|
109
|
+
await self._provider_specific_disconnect()
|
|
110
|
+
self._request_processor.clear_caches()
|
|
111
|
+
self.logger.info(f"Successfully disconnected from: {self.endpoint_uri}")
|
|
112
|
+
|
|
113
|
+
# -- private methods -- #
|
|
114
|
+
|
|
115
|
+
async def _provider_specific_connect(self) -> None:
|
|
116
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
117
|
+
|
|
118
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
119
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
120
|
+
|
|
121
|
+
async def _provider_specific_message_listener(self) -> None:
|
|
122
|
+
raise NotImplementedError("Must be implemented by subclasses")
|
|
123
|
+
|
|
124
|
+
def _message_listener_callback(
|
|
125
|
+
self, message_listener_task: "asyncio.Task[None]"
|
|
126
|
+
) -> None:
|
|
127
|
+
# Puts a `TaskNotRunning` in the queue to signal the end of the listener task
|
|
128
|
+
# to any running subscription streams that are awaiting a response.
|
|
129
|
+
self._request_processor._subscription_response_queue.put_nowait(
|
|
130
|
+
TaskNotRunning(message_listener_task)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
async def _message_listener(self) -> None:
|
|
134
|
+
self.logger.info(
|
|
135
|
+
f"{self.__class__.__qualname__} listener background task started. Storing "
|
|
136
|
+
"all messages in appropriate request processor queues / caches to be "
|
|
137
|
+
"processed."
|
|
138
|
+
)
|
|
139
|
+
while True:
|
|
140
|
+
# the use of sleep(0) seems to be the most efficient way to yield control
|
|
141
|
+
# back to the event loop to share the loop with other tasks.
|
|
142
|
+
await asyncio.sleep(0)
|
|
143
|
+
try:
|
|
144
|
+
await self._provider_specific_message_listener()
|
|
145
|
+
except Exception as e:
|
|
146
|
+
if not self.silence_listener_task_exceptions:
|
|
147
|
+
raise e
|
|
148
|
+
else:
|
|
149
|
+
self._error_log_listener_task_exception(e)
|
|
150
|
+
|
|
151
|
+
def _error_log_listener_task_exception(self, e: Exception) -> None:
|
|
152
|
+
"""
|
|
153
|
+
When silencing listener task exceptions, this method is used to log the
|
|
154
|
+
exception and keep the listener task alive. Override this method to fine-tune
|
|
155
|
+
error logging behavior for the implementation class.
|
|
156
|
+
"""
|
|
157
|
+
self.logger.error(
|
|
158
|
+
"Exception caught in listener, error logging and keeping "
|
|
159
|
+
"listener background task alive."
|
|
160
|
+
f"\n error={e.__class__.__name__}: {e}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _handle_listener_task_exceptions(self) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Should be called every time a `PersistentConnectionProvider` is polling for
|
|
166
|
+
messages in the main loop. If the message listener task has completed and an
|
|
167
|
+
exception was recorded, raise the exception in the main loop.
|
|
168
|
+
"""
|
|
169
|
+
msg_listener_task = getattr(self, "_message_listener_task", None)
|
|
170
|
+
if (
|
|
171
|
+
msg_listener_task
|
|
172
|
+
and msg_listener_task.done()
|
|
173
|
+
and msg_listener_task.exception()
|
|
174
|
+
):
|
|
175
|
+
raise msg_listener_task.exception()
|
|
176
|
+
|
|
177
|
+
async def _get_response_for_request_id(
|
|
178
|
+
self, request_id: RPCId, timeout: Optional[float] = None
|
|
179
|
+
) -> RPCResponse:
|
|
180
|
+
if timeout is None:
|
|
181
|
+
timeout = self.request_timeout
|
|
182
|
+
|
|
183
|
+
async def _match_response_id_to_request_id() -> RPCResponse:
|
|
184
|
+
request_cache_key = generate_cache_key(request_id)
|
|
185
|
+
|
|
186
|
+
while True:
|
|
187
|
+
# check if an exception was recorded in the listener task and raise it
|
|
188
|
+
# in the main loop if so
|
|
189
|
+
self._handle_listener_task_exceptions()
|
|
190
|
+
|
|
191
|
+
if request_cache_key in self._request_processor._request_response_cache:
|
|
192
|
+
self.logger.debug(
|
|
193
|
+
f"Popping response for id {request_id} from cache."
|
|
194
|
+
)
|
|
195
|
+
popped_response = await self._request_processor.pop_raw_response(
|
|
196
|
+
cache_key=request_cache_key,
|
|
197
|
+
)
|
|
198
|
+
return popped_response
|
|
199
|
+
else:
|
|
200
|
+
await asyncio.sleep(0)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# Add the request timeout around the while loop that checks the request
|
|
204
|
+
# cache. If the request is not in the cache within the request_timeout,
|
|
205
|
+
# raise ``TimeExhausted``.
|
|
206
|
+
return await asyncio.wait_for(_match_response_id_to_request_id(), timeout)
|
|
207
|
+
except asyncio.TimeoutError:
|
|
208
|
+
raise TimeExhausted(
|
|
209
|
+
f"Timed out waiting for response with request id `{request_id}` after "
|
|
210
|
+
f"{self.request_timeout} second(s). This may be due to the provider "
|
|
211
|
+
"not returning a response with the same id that was sent in the "
|
|
212
|
+
"request or an exception raised during the request was caught and "
|
|
213
|
+
"allowed to continue."
|
|
214
|
+
)
|
|
@@ -2,19 +2,26 @@ import asyncio
|
|
|
2
2
|
from copy import (
|
|
3
3
|
copy,
|
|
4
4
|
)
|
|
5
|
+
import sys
|
|
5
6
|
from typing import (
|
|
6
7
|
TYPE_CHECKING,
|
|
7
8
|
Any,
|
|
8
9
|
Callable,
|
|
9
10
|
Dict,
|
|
11
|
+
Generic,
|
|
10
12
|
Optional,
|
|
11
13
|
Tuple,
|
|
14
|
+
TypeVar,
|
|
15
|
+
Union,
|
|
12
16
|
)
|
|
13
17
|
|
|
14
18
|
from web3._utils.caching import (
|
|
15
19
|
RequestInformation,
|
|
16
20
|
generate_cache_key,
|
|
17
21
|
)
|
|
22
|
+
from web3.exceptions import (
|
|
23
|
+
TaskNotRunning,
|
|
24
|
+
)
|
|
18
25
|
from web3.types import (
|
|
19
26
|
RPCEndpoint,
|
|
20
27
|
RPCResponse,
|
|
@@ -28,6 +35,34 @@ if TYPE_CHECKING:
|
|
|
28
35
|
PersistentConnectionProvider,
|
|
29
36
|
)
|
|
30
37
|
|
|
38
|
+
T = TypeVar("T")
|
|
39
|
+
|
|
40
|
+
# TODO: This is an ugly hack for python 3.8. Remove this after we drop support for it
|
|
41
|
+
# and use `asyncio.Queue[T]` type directly in the `TaskReliantQueue` class.
|
|
42
|
+
if sys.version_info >= (3, 9):
|
|
43
|
+
|
|
44
|
+
class _TaskReliantQueue(asyncio.Queue[T], Generic[T]):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
else:
|
|
48
|
+
|
|
49
|
+
class _TaskReliantQueue(asyncio.Queue, Generic[T]): # type: ignore
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TaskReliantQueue(_TaskReliantQueue[T]):
|
|
54
|
+
"""
|
|
55
|
+
A queue that relies on a task to be running to process items in the queue.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
async def get(self) -> T:
|
|
59
|
+
item = await super().get()
|
|
60
|
+
if isinstance(item, Exception):
|
|
61
|
+
# if the item is an exception, raise it so the task can handle this case
|
|
62
|
+
# more gracefully
|
|
63
|
+
raise item
|
|
64
|
+
return item
|
|
65
|
+
|
|
31
66
|
|
|
32
67
|
class RequestProcessor:
|
|
33
68
|
_subscription_queue_synced_with_ws_stream: bool = False
|
|
@@ -41,9 +76,9 @@ class RequestProcessor:
|
|
|
41
76
|
self._provider = provider
|
|
42
77
|
|
|
43
78
|
self._request_response_cache: SimpleCache = SimpleCache(500)
|
|
44
|
-
self._subscription_response_queue:
|
|
45
|
-
|
|
46
|
-
)
|
|
79
|
+
self._subscription_response_queue: TaskReliantQueue[
|
|
80
|
+
Union[RPCResponse, TaskNotRunning]
|
|
81
|
+
] = TaskReliantQueue(maxsize=subscription_response_queue_size)
|
|
47
82
|
self._request_information_cache: SimpleCache = SimpleCache(
|
|
48
83
|
request_information_cache_size
|
|
49
84
|
)
|
|
@@ -203,7 +238,7 @@ class RequestProcessor:
|
|
|
203
238
|
) -> None:
|
|
204
239
|
if subscription:
|
|
205
240
|
if self._subscription_response_queue.full():
|
|
206
|
-
self._provider.logger.
|
|
241
|
+
self._provider.logger.debug(
|
|
207
242
|
"Subscription queue is full. Waiting for provider to consume "
|
|
208
243
|
"messages before caching."
|
|
209
244
|
)
|
|
@@ -276,6 +311,6 @@ class RequestProcessor:
|
|
|
276
311
|
|
|
277
312
|
self._request_information_cache.clear()
|
|
278
313
|
self._request_response_cache.clear()
|
|
279
|
-
self._subscription_response_queue =
|
|
314
|
+
self._subscription_response_queue = TaskReliantQueue(
|
|
280
315
|
maxsize=self._subscription_response_queue.maxsize
|
|
281
316
|
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Dict,
|
|
8
|
+
Optional,
|
|
9
|
+
Union,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from eth_typing import (
|
|
13
|
+
URI,
|
|
14
|
+
)
|
|
15
|
+
from toolz import (
|
|
16
|
+
merge,
|
|
17
|
+
)
|
|
18
|
+
from websockets.client import (
|
|
19
|
+
connect,
|
|
20
|
+
)
|
|
21
|
+
from websockets.exceptions import (
|
|
22
|
+
WebSocketException,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from web3.exceptions import (
|
|
26
|
+
ProviderConnectionError,
|
|
27
|
+
Web3ValidationError,
|
|
28
|
+
)
|
|
29
|
+
from web3.providers.persistent import (
|
|
30
|
+
PersistentConnectionProvider,
|
|
31
|
+
)
|
|
32
|
+
from web3.types import (
|
|
33
|
+
RPCEndpoint,
|
|
34
|
+
RPCResponse,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
DEFAULT_PING_INTERVAL = 30 # 30 seconds
|
|
38
|
+
DEFAULT_PING_TIMEOUT = 300 # 5 minutes
|
|
39
|
+
|
|
40
|
+
VALID_WEBSOCKET_URI_PREFIXES = {"ws://", "wss://"}
|
|
41
|
+
RESTRICTED_WEBSOCKET_KWARGS = {"uri", "loop"}
|
|
42
|
+
DEFAULT_WEBSOCKET_KWARGS = {
|
|
43
|
+
# set how long to wait between pings from the server
|
|
44
|
+
"ping_interval": DEFAULT_PING_INTERVAL,
|
|
45
|
+
# set how long to wait without a pong response before closing the connection
|
|
46
|
+
"ping_timeout": DEFAULT_PING_TIMEOUT,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_default_endpoint() -> URI:
|
|
51
|
+
return URI(os.environ.get("WEB3_WS_PROVIDER_URI", "ws://127.0.0.1:8546"))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class WebsocketProviderV2(PersistentConnectionProvider):
|
|
55
|
+
logger = logging.getLogger("web3.providers.WebsocketProviderV2")
|
|
56
|
+
is_async: bool = True
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
endpoint_uri: Optional[Union[URI, str]] = None,
|
|
61
|
+
websocket_kwargs: Optional[Dict[str, Any]] = None,
|
|
62
|
+
silence_listener_task_exceptions: bool = False,
|
|
63
|
+
# `PersistentConnectionProvider` kwargs can be passed through
|
|
64
|
+
**kwargs: Any,
|
|
65
|
+
) -> None:
|
|
66
|
+
self.endpoint_uri = (
|
|
67
|
+
URI(endpoint_uri) if endpoint_uri is not None else get_default_endpoint()
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not any(
|
|
71
|
+
self.endpoint_uri.startswith(prefix)
|
|
72
|
+
for prefix in VALID_WEBSOCKET_URI_PREFIXES
|
|
73
|
+
):
|
|
74
|
+
raise Web3ValidationError(
|
|
75
|
+
"WebSocket endpoint uri must begin with 'ws://' or 'wss://': "
|
|
76
|
+
f"{self.endpoint_uri}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if websocket_kwargs is not None:
|
|
80
|
+
found_restricted_keys = set(websocket_kwargs).intersection(
|
|
81
|
+
RESTRICTED_WEBSOCKET_KWARGS
|
|
82
|
+
)
|
|
83
|
+
if found_restricted_keys:
|
|
84
|
+
raise Web3ValidationError(
|
|
85
|
+
"Found restricted keys for websocket_kwargs: "
|
|
86
|
+
f"{found_restricted_keys}."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self.websocket_kwargs = merge(DEFAULT_WEBSOCKET_KWARGS, websocket_kwargs or {})
|
|
90
|
+
|
|
91
|
+
super().__init__(
|
|
92
|
+
silence_listener_task_exceptions=silence_listener_task_exceptions, **kwargs
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __str__(self) -> str:
|
|
96
|
+
return f"WebSocket connection: {self.endpoint_uri}"
|
|
97
|
+
|
|
98
|
+
async def is_connected(self, show_traceback: bool = False) -> bool:
|
|
99
|
+
if not self._ws:
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
await self._ws.pong()
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
except WebSocketException as e:
|
|
107
|
+
if show_traceback:
|
|
108
|
+
raise ProviderConnectionError(
|
|
109
|
+
f"Error connecting to endpoint: '{self.endpoint_uri}'"
|
|
110
|
+
) from e
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
async def _provider_specific_connect(self) -> None:
|
|
114
|
+
self._ws = await connect(self.endpoint_uri, **self.websocket_kwargs)
|
|
115
|
+
|
|
116
|
+
async def _provider_specific_disconnect(self) -> None:
|
|
117
|
+
if self._ws is not None and not self._ws.closed:
|
|
118
|
+
await self._ws.close()
|
|
119
|
+
self._ws = None
|
|
120
|
+
|
|
121
|
+
async def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse:
|
|
122
|
+
request_data = self.encode_rpc_request(method, params)
|
|
123
|
+
|
|
124
|
+
if self._ws is None:
|
|
125
|
+
raise ProviderConnectionError(
|
|
126
|
+
"Connection to websocket has not been initiated for the provider."
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
await asyncio.wait_for(
|
|
130
|
+
self._ws.send(request_data), timeout=self.request_timeout
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
current_request_id = json.loads(request_data)["id"]
|
|
134
|
+
response = await self._get_response_for_request_id(current_request_id)
|
|
135
|
+
|
|
136
|
+
return response
|
|
137
|
+
|
|
138
|
+
async def _provider_specific_message_listener(self) -> None:
|
|
139
|
+
async for raw_message in self._ws:
|
|
140
|
+
await asyncio.sleep(0)
|
|
141
|
+
|
|
142
|
+
response = json.loads(raw_message)
|
|
143
|
+
subscription = response.get("method") == "eth_subscription"
|
|
144
|
+
await self._request_processor.cache_raw_response(
|
|
145
|
+
response, subscription=subscription
|
|
146
|
+
)
|