web3 7.0.0b2__py3-none-any.whl → 7.7.0__py3-none-any.whl

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.
Files changed (144) hide show
  1. ens/__init__.py +13 -2
  2. ens/_normalization.py +4 -4
  3. ens/async_ens.py +27 -15
  4. ens/base_ens.py +3 -1
  5. ens/contract_data.py +2 -2
  6. ens/ens.py +10 -7
  7. ens/exceptions.py +16 -29
  8. ens/specs/nf.json +1 -1
  9. ens/specs/normalization_spec.json +1 -1
  10. ens/utils.py +24 -32
  11. web3/__init__.py +23 -12
  12. web3/_utils/abi.py +157 -263
  13. web3/_utils/async_transactions.py +34 -20
  14. web3/_utils/batching.py +217 -0
  15. web3/_utils/blocks.py +6 -2
  16. web3/_utils/caching/__init__.py +12 -0
  17. web3/_utils/caching/caching_utils.py +433 -0
  18. web3/_utils/caching/request_caching_validation.py +287 -0
  19. web3/_utils/compat/__init__.py +2 -3
  20. web3/_utils/contract_sources/compile_contracts.py +1 -1
  21. web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +42 -0
  22. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  24. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  25. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  26. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  27. web3/_utils/contract_sources/contract_data/event_contracts.py +50 -5
  28. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  29. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  30. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  31. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  32. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  33. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  34. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  35. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  36. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  37. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  38. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  39. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  40. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  41. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  42. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  43. web3/_utils/contracts.py +172 -220
  44. web3/_utils/datatypes.py +5 -1
  45. web3/_utils/decorators.py +6 -1
  46. web3/_utils/empty.py +1 -1
  47. web3/_utils/encoding.py +16 -12
  48. web3/_utils/error_formatters_utils.py +5 -3
  49. web3/_utils/events.py +78 -72
  50. web3/_utils/fee_utils.py +1 -3
  51. web3/_utils/filters.py +24 -22
  52. web3/_utils/formatters.py +2 -2
  53. web3/_utils/http.py +8 -2
  54. web3/_utils/http_session_manager.py +314 -0
  55. web3/_utils/math.py +14 -15
  56. web3/_utils/method_formatters.py +161 -34
  57. web3/_utils/module.py +2 -1
  58. web3/_utils/module_testing/__init__.py +3 -2
  59. web3/_utils/module_testing/eth_module.py +736 -583
  60. web3/_utils/module_testing/go_ethereum_debug_module.py +128 -0
  61. web3/_utils/module_testing/module_testing_utils.py +81 -24
  62. web3/_utils/module_testing/persistent_connection_provider.py +702 -220
  63. web3/_utils/module_testing/utils.py +114 -33
  64. web3/_utils/module_testing/web3_module.py +438 -17
  65. web3/_utils/normalizers.py +13 -11
  66. web3/_utils/rpc_abi.py +10 -22
  67. web3/_utils/threads.py +8 -7
  68. web3/_utils/transactions.py +32 -25
  69. web3/_utils/type_conversion.py +5 -1
  70. web3/_utils/validation.py +20 -17
  71. web3/beacon/__init__.py +5 -0
  72. web3/beacon/api_endpoints.py +3 -0
  73. web3/beacon/async_beacon.py +29 -6
  74. web3/beacon/beacon.py +24 -6
  75. web3/contract/__init__.py +7 -0
  76. web3/contract/async_contract.py +285 -82
  77. web3/contract/base_contract.py +556 -258
  78. web3/contract/contract.py +295 -84
  79. web3/contract/utils.py +251 -55
  80. web3/datastructures.py +49 -34
  81. web3/eth/__init__.py +7 -0
  82. web3/eth/async_eth.py +89 -69
  83. web3/eth/base_eth.py +7 -3
  84. web3/eth/eth.py +43 -66
  85. web3/exceptions.py +158 -83
  86. web3/gas_strategies/time_based.py +8 -6
  87. web3/geth.py +53 -184
  88. web3/main.py +77 -17
  89. web3/manager.py +362 -95
  90. web3/method.py +43 -15
  91. web3/middleware/__init__.py +17 -0
  92. web3/middleware/attrdict.py +12 -22
  93. web3/middleware/base.py +55 -2
  94. web3/middleware/filter.py +45 -23
  95. web3/middleware/formatting.py +6 -3
  96. web3/middleware/names.py +4 -1
  97. web3/middleware/signing.py +15 -6
  98. web3/middleware/stalecheck.py +2 -1
  99. web3/module.py +61 -25
  100. web3/providers/__init__.py +21 -0
  101. web3/providers/async_base.py +87 -32
  102. web3/providers/base.py +77 -32
  103. web3/providers/eth_tester/__init__.py +5 -0
  104. web3/providers/eth_tester/defaults.py +2 -55
  105. web3/providers/eth_tester/main.py +41 -15
  106. web3/providers/eth_tester/middleware.py +16 -17
  107. web3/providers/ipc.py +41 -17
  108. web3/providers/legacy_websocket.py +26 -1
  109. web3/providers/persistent/__init__.py +7 -0
  110. web3/providers/persistent/async_ipc.py +61 -121
  111. web3/providers/persistent/persistent.py +323 -16
  112. web3/providers/persistent/persistent_connection.py +54 -5
  113. web3/providers/persistent/request_processor.py +136 -56
  114. web3/providers/persistent/subscription_container.py +56 -0
  115. web3/providers/persistent/subscription_manager.py +233 -0
  116. web3/providers/persistent/websocket.py +29 -92
  117. web3/providers/rpc/__init__.py +5 -0
  118. web3/providers/rpc/async_rpc.py +73 -18
  119. web3/providers/rpc/rpc.py +73 -30
  120. web3/providers/rpc/utils.py +1 -13
  121. web3/scripts/install_pre_releases.py +33 -0
  122. web3/scripts/parse_pygeth_version.py +16 -0
  123. web3/testing.py +4 -4
  124. web3/tracing.py +9 -5
  125. web3/types.py +141 -74
  126. web3/utils/__init__.py +64 -5
  127. web3/utils/abi.py +790 -10
  128. web3/utils/address.py +8 -0
  129. web3/utils/async_exception_handling.py +20 -11
  130. web3/utils/caching.py +34 -4
  131. web3/utils/exception_handling.py +9 -12
  132. web3/utils/subscriptions.py +285 -0
  133. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/LICENSE +1 -1
  134. web3-7.7.0.dist-info/METADATA +130 -0
  135. web3-7.7.0.dist-info/RECORD +171 -0
  136. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/WHEEL +1 -1
  137. web3/_utils/caching.py +0 -155
  138. web3/_utils/contract_sources/contract_data/address_reflector.py +0 -29
  139. web3/_utils/module_testing/go_ethereum_personal_module.py +0 -300
  140. web3/_utils/request.py +0 -265
  141. web3-7.0.0b2.dist-info/METADATA +0 -106
  142. web3-7.0.0b2.dist-info/RECORD +0 -163
  143. /web3/_utils/{function_identifiers.py → abi_element_identifiers.py} +0 -0
  144. {web3-7.0.0b2.dist-info → web3-7.7.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,23 @@
1
1
  import asyncio
2
+ from dataclasses import (
3
+ dataclass,
4
+ )
2
5
  import pytest
3
6
  from typing import (
4
7
  TYPE_CHECKING,
5
8
  Any,
6
9
  Dict,
10
+ Generator,
11
+ List,
7
12
  Tuple,
13
+ Union,
8
14
  cast,
9
15
  )
10
16
 
17
+ from eth_typing import (
18
+ ChecksumAddress,
19
+ HexStr,
20
+ )
11
21
  from eth_utils import (
12
22
  is_hexstr,
13
23
  )
@@ -15,6 +25,13 @@ from hexbytes import (
15
25
  HexBytes,
16
26
  )
17
27
 
28
+ from web3 import (
29
+ AsyncWeb3,
30
+ PersistentConnectionProvider,
31
+ )
32
+ from web3.beacon import (
33
+ AsyncBeacon,
34
+ )
18
35
  from web3.datastructures import (
19
36
  AttributeDict,
20
37
  )
@@ -22,214 +39,226 @@ from web3.middleware import (
22
39
  ExtraDataToPOAMiddleware,
23
40
  )
24
41
  from web3.types import (
42
+ BlockData,
25
43
  FormattedEthSubscriptionResponse,
44
+ LogReceipt,
45
+ Nonce,
46
+ RPCEndpoint,
47
+ TxData,
48
+ Wei,
49
+ )
50
+ from web3.utils import (
51
+ EthSubscription,
52
+ )
53
+ from web3.utils.subscriptions import (
54
+ LogsSubscription,
55
+ LogsSubscriptionContext,
56
+ NewHeadsSubscription,
57
+ NewHeadsSubscriptionContext,
58
+ PendingTxSubscription,
59
+ PendingTxSubscriptionContext,
26
60
  )
27
61
 
28
62
  if TYPE_CHECKING:
29
- from web3.main import (
30
- AsyncWeb3,
63
+ from web3.contract.async_contract import (
64
+ AsyncContract,
65
+ AsyncContractFunction,
66
+ )
67
+ from web3.providers.persistent.subscription_manager import (
68
+ SubscriptionContainer,
69
+ )
70
+
71
+
72
+ # LogIndexedAndNotIndexed event args
73
+ INDEXED_ADDR = "0xdEad000000000000000000000000000000000000"
74
+ INDEXED_UINT256 = 1337
75
+ NON_INDEXED_ADDR = "0xbeeF000000000000000000000000000000000000"
76
+ NON_INDEXED_UINT256 = 1999
77
+ NON_INDEXED_STRING = "test logs subscriptions"
78
+
79
+ SOME_BLOCK_KEYS = [
80
+ "number",
81
+ "hash",
82
+ "parentHash",
83
+ "transactionsRoot",
84
+ "stateRoot",
85
+ "receiptsRoot",
86
+ "gasLimit",
87
+ "gasUsed",
88
+ "timestamp",
89
+ "baseFeePerGas",
90
+ "withdrawalsRoot",
91
+ ]
92
+
93
+
94
+ @dataclass
95
+ class SubscriptionHandlerTest:
96
+ passed: bool = False
97
+
98
+
99
+ async def new_heads_handler(
100
+ handler_context: NewHeadsSubscriptionContext,
101
+ ) -> None:
102
+ w3 = handler_context.async_w3
103
+ sub = handler_context.subscription
104
+ assert isinstance(w3, AsyncWeb3)
105
+ provider = cast(PersistentConnectionProvider, w3.provider)
106
+ assert isinstance(provider.get_endpoint_uri_or_ipc_path(), str)
107
+
108
+ assert isinstance(sub, EthSubscription)
109
+
110
+ block = handler_context.result
111
+ assert block is not None
112
+ assert all(k in block.keys() for k in SOME_BLOCK_KEYS)
113
+
114
+ assert handler_context.new_heads_handler_test.passed is False
115
+ handler_context.new_heads_handler_test.passed = True
116
+ assert await sub.unsubscribe()
117
+
118
+
119
+ async def pending_tx_handler(
120
+ handler_context: PendingTxSubscriptionContext,
121
+ ) -> None:
122
+ w3 = handler_context.async_w3
123
+ sub = handler_context.subscription
124
+ tx = handler_context.result
125
+
126
+ assert w3 is not None
127
+ provider = cast(PersistentConnectionProvider, w3.provider)
128
+ assert isinstance(provider.get_endpoint_uri_or_ipc_path(), str)
129
+
130
+ assert isinstance(sub, PendingTxSubscription)
131
+
132
+ assert tx is not None
133
+ tx = cast(TxData, tx)
134
+ accts = await w3.eth.accounts
135
+ assert tx["from"] == accts[0]
136
+ await w3.eth.wait_for_transaction_receipt(tx["hash"])
137
+
138
+ assert handler_context.pending_tx_handler_test.passed is False
139
+ handler_context.pending_tx_handler_test.passed = True
140
+ assert await sub.unsubscribe()
141
+
142
+
143
+ async def logs_handler(
144
+ handler_context: LogsSubscriptionContext,
145
+ ) -> None:
146
+ w3 = handler_context.async_w3
147
+ sub = handler_context.subscription
148
+ log_receipt = handler_context.result
149
+
150
+ assert w3 is not None
151
+ provider = cast(PersistentConnectionProvider, w3.provider)
152
+ assert isinstance(provider.get_endpoint_uri_or_ipc_path(), str)
153
+
154
+ assert isinstance(sub, LogsSubscription)
155
+ event_data = handler_context.event.process_log(log_receipt)
156
+ assert event_data.args.indexedAddress == INDEXED_ADDR
157
+ assert event_data.args.indexedUint256 == INDEXED_UINT256
158
+ assert event_data.args.nonIndexedAddress == NON_INDEXED_ADDR
159
+ assert event_data.args.nonIndexedUint256 == NON_INDEXED_UINT256
160
+ assert event_data.args.nonIndexedString == NON_INDEXED_STRING
161
+
162
+ assert handler_context.logs_handler_test.passed is False
163
+ handler_context.logs_handler_test.passed = True
164
+ assert await sub.unsubscribe()
165
+
166
+
167
+ async def idle_handler(
168
+ _handler_context: Any,
169
+ ) -> None:
170
+ pass
171
+
172
+
173
+ async def emit_contract_event(
174
+ async_w3: AsyncWeb3,
175
+ acct: ChecksumAddress,
176
+ contract_function: "AsyncContractFunction",
177
+ args: Any = (),
178
+ delay: float = 0.25,
179
+ ) -> None:
180
+ await asyncio.sleep(delay)
181
+ tx_hash = await contract_function(*args).transact({"from": acct})
182
+ receipt = await async_w3.eth.wait_for_transaction_receipt(tx_hash)
183
+ assert receipt["status"] == 1
184
+
185
+
186
+ async def log_indexed_and_non_indexed_args_task(
187
+ async_w3: AsyncWeb3,
188
+ async_emitter_contract: "AsyncContract",
189
+ acct: ChecksumAddress,
190
+ delay: float = 0.1,
191
+ ) -> "asyncio.Task[None]":
192
+ return asyncio.create_task(
193
+ emit_contract_event(
194
+ async_w3,
195
+ acct,
196
+ async_emitter_contract.functions.logIndexedAndNotIndexedArgs,
197
+ args=(
198
+ INDEXED_ADDR,
199
+ INDEXED_UINT256,
200
+ NON_INDEXED_ADDR,
201
+ NON_INDEXED_UINT256,
202
+ NON_INDEXED_STRING,
203
+ ),
204
+ delay=delay,
205
+ )
31
206
  )
32
207
 
33
208
 
209
+ def assert_no_subscriptions_left(sub_container: "SubscriptionContainer") -> None:
210
+ assert len(sub_container) == 0
211
+ assert len(sub_container.subscriptions) == 0
212
+ assert len(sub_container.subscriptions_by_id) == 0
213
+ assert len(sub_container.subscriptions_by_label) == 0
214
+ assert len(sub_container.handler_subscriptions) == 0
215
+
216
+
217
+ async def clean_up_task(task: "asyncio.Task[Any]") -> None:
218
+ task.cancel()
219
+ try:
220
+ await task
221
+ except asyncio.CancelledError:
222
+ pass
223
+
224
+
34
225
  class PersistentConnectionProviderTest:
226
+ @pytest.fixture(autouse=True)
227
+ def clear_caches(self, async_w3: AsyncWeb3) -> Generator[None, None, None]:
228
+ yield
229
+ async_w3.provider._request_processor.clear_caches()
230
+ async_w3.subscription_manager.total_handler_calls = 0
231
+
232
+ @staticmethod
233
+ async def seed_transactions_to_geth(
234
+ async_w3: AsyncWeb3,
235
+ acct: ChecksumAddress,
236
+ num_txs: int = 1,
237
+ delay: float = 0.1,
238
+ ) -> None:
239
+ nonce = int(await async_w3.eth.get_transaction_count(acct))
240
+
241
+ async def send_tx() -> None:
242
+ nonlocal nonce
243
+ await async_w3.eth.send_transaction(
244
+ {
245
+ "from": acct,
246
+ "to": acct,
247
+ "value": Wei(nonce),
248
+ "gas": 21000,
249
+ "nonce": Nonce(nonce),
250
+ }
251
+ )
252
+ nonce += 1
253
+
254
+ for _ in range(num_txs):
255
+ await asyncio.sleep(delay)
256
+ await send_tx()
257
+
35
258
  @pytest.mark.asyncio
36
259
  @pytest.mark.parametrize(
37
260
  "subscription_params,ws_subscription_response,expected_formatted_result",
38
261
  (
39
- (
40
- ("newHeads",),
41
- {
42
- "jsonrpc": "2.0",
43
- "method": "eth_subscription",
44
- "params": {
45
- "subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
46
- "result": {
47
- "number": "0x539",
48
- "hash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
49
- "parentHash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
50
- "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", # noqa: E501
51
- "logsBloom": "0x00",
52
- "transactionsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
53
- "stateRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
54
- "receiptsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
55
- "miner": "0x0000000000000000000000000000000000000000",
56
- "difficulty": "0x0",
57
- "extraData": "0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465", # noqa: E501
58
- "gasLimit": "0x1c9c380",
59
- "gasUsed": "0xd1ce44",
60
- "timestamp": "0x539",
61
- "baseFeePerGas": "0x26f93fef9",
62
- "withdrawalsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
63
- "nonce": "0x0000000000000000",
64
- "mixHash": "0x73e9e036ec894047f29954571d4b6d9e8717de7304269c263cbf150caa4e0768", # noqa: E501
65
- },
66
- },
67
- },
68
- AttributeDict(
69
- {
70
- "number": 1337,
71
- "hash": HexBytes(
72
- "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
73
- ),
74
- "parentHash": HexBytes(
75
- "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
76
- ),
77
- "sha3Uncles": HexBytes(
78
- "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" # noqa: E501
79
- ),
80
- "logsBloom": HexBytes("0x00"),
81
- "transactionsRoot": HexBytes(
82
- "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
83
- ),
84
- "stateRoot": HexBytes(
85
- "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
86
- ),
87
- "receiptsRoot": HexBytes(
88
- "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
89
- ),
90
- "miner": "0x0000000000000000000000000000000000000000",
91
- "difficulty": 0,
92
- "extraData": HexBytes(
93
- "0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465" # noqa: E501
94
- ),
95
- "gasLimit": 30000000,
96
- "gasUsed": 13749828,
97
- "timestamp": 1337,
98
- "baseFeePerGas": 10461904633,
99
- "withdrawalsRoot": HexBytes(
100
- "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
101
- ),
102
- "nonce": HexBytes("0x0000000000000000"),
103
- "mixHash": HexBytes(
104
- "0x73e9e036ec894047f29954571d4b6d9e8717de7304269c263cbf150caa4e0768" # noqa: E501
105
- ),
106
- }
107
- ),
108
- ),
109
- (
110
- ("newPendingTransactions", True),
111
- {
112
- "jsonrpc": "2.0",
113
- "method": "eth_subscription",
114
- "params": {
115
- "subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
116
- "result": {
117
- "blockHash": None,
118
- "blockNumber": None,
119
- "from": "0x0000000000000000000000000000000000000000",
120
- "gas": "0xf2f4",
121
- "gasPrice": "0x29035f36f",
122
- "maxFeePerGas": "0x29035f36f",
123
- "maxPriorityFeePerGas": "0x3b9aca00",
124
- "hash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
125
- "input": "0x00",
126
- "nonce": "0x2013",
127
- "to": "0x0000000000000000000000000000000000000000",
128
- "transactionIndex": None,
129
- "value": "0x0",
130
- "type": "0x2",
131
- "accessList": [],
132
- "chainId": "0x1",
133
- "v": "0x1",
134
- "r": "0x3c144a7c00ed3118d55445cd5be2ae4620ca377f7c685e9c5f3687671d4dece1", # noqa: E501
135
- "s": "0x284de67cbf75fec8a9edb368dee3a37cf6faba87f0af4413b2f869ebfa87d002", # noqa: E501
136
- "yParity": "0x1",
137
- },
138
- },
139
- },
140
- AttributeDict(
141
- {
142
- "blockHash": None,
143
- "blockNumber": None,
144
- "from": "0x0000000000000000000000000000000000000000",
145
- "gas": 62196,
146
- "gasPrice": 11009389423,
147
- "maxFeePerGas": 11009389423,
148
- "maxPriorityFeePerGas": 1000000000,
149
- "hash": HexBytes(
150
- "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
151
- ),
152
- "input": HexBytes("0x00"),
153
- "nonce": 8211,
154
- "to": "0x0000000000000000000000000000000000000000",
155
- "transactionIndex": None,
156
- "value": 0,
157
- "type": 2,
158
- "accessList": [],
159
- "chainId": 1,
160
- "v": 1,
161
- "r": HexBytes(
162
- "0x3c144a7c00ed3118d55445cd5be2ae4620ca377f7c685e9c5f3687671d4dece1" # noqa: E501
163
- ),
164
- "s": HexBytes(
165
- "0x284de67cbf75fec8a9edb368dee3a37cf6faba87f0af4413b2f869ebfa87d002" # noqa: E501
166
- ),
167
- "yParity": 1,
168
- }
169
- ),
170
- ),
171
- (
172
- ("newPendingTransactions", False),
173
- {
174
- "jsonrpc": "2.0",
175
- "method": "eth_subscription",
176
- "params": {
177
- "subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
178
- "result": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
179
- },
180
- },
181
- HexBytes(
182
- "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e"
183
- ),
184
- ),
185
- (
186
- ("logs", {"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}),
187
- {
188
- "jsonrpc": "2.0",
189
- "method": "eth_subscription",
190
- "params": {
191
- "subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
192
- "result": {
193
- "removed": False,
194
- "logIndex": "0x0",
195
- "transactionIndex": "0x0",
196
- "transactionHash": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
197
- "blockHash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
198
- "blockNumber": "0x539",
199
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
200
- "data": "0x00",
201
- "topics": [
202
- "0xe1fffdd4923d04f559f4d29e8bfc6cda04eb5b0d3c460751c2402c5c5cc9105c", # noqa: E501
203
- "0x00000000000000000000000016250d5630b4cf539739df2c5dacb4c659f2482d", # noqa: E501
204
- ],
205
- },
206
- },
207
- },
208
- AttributeDict(
209
- {
210
- "removed": False,
211
- "logIndex": 0,
212
- "transactionIndex": 0,
213
- "transactionHash": HexBytes(
214
- "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
215
- ),
216
- "blockHash": HexBytes(
217
- "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
218
- ),
219
- "blockNumber": 1337,
220
- "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
221
- "data": HexBytes("0x00"),
222
- "topics": [
223
- HexBytes(
224
- "0xe1fffdd4923d04f559f4d29e8bfc6cda04eb5b0d3c460751c2402c5c5cc9105c" # noqa: E501
225
- ),
226
- HexBytes(
227
- "0x00000000000000000000000016250d5630b4cf539739df2c5dacb4c659f2482d" # noqa: E501
228
- ),
229
- ],
230
- }
231
- ),
232
- ),
233
262
  (
234
263
  ("syncing",),
235
264
  {
@@ -268,17 +297,13 @@ class PersistentConnectionProviderTest:
268
297
  ),
269
298
  ),
270
299
  ids=[
271
- "newHeads",
272
- "newPendingTransactions-FullTxs",
273
- "newPendingTransactions-TxHashes",
274
- "logs",
275
300
  "syncing-False",
276
301
  "syncing-True",
277
302
  ],
278
303
  )
279
- async def test_async_eth_subscribe_mocked(
304
+ async def test_async_eth_subscribe_syncing_mocked(
280
305
  self,
281
- async_w3: "AsyncWeb3",
306
+ async_w3: AsyncWeb3,
282
307
  subscription_params: Tuple[Any, ...],
283
308
  ws_subscription_response: Dict[str, Any],
284
309
  expected_formatted_result: Any,
@@ -301,12 +326,264 @@ class PersistentConnectionProviderTest:
301
326
  assert response["result"] == expected_formatted_result
302
327
 
303
328
  # only testing one message, so break here
329
+ await async_w3.eth.unsubscribe(sub_id)
330
+ break
331
+
332
+ assert_no_subscriptions_left(
333
+ async_w3.subscription_manager._subscription_container
334
+ )
335
+
336
+ @pytest.mark.asyncio
337
+ async def test_async_eth_subscribe_new_heads(self, async_w3: AsyncWeb3) -> None:
338
+ sub_id = await async_w3.eth.subscribe("newHeads")
339
+ assert is_hexstr(sub_id)
340
+
341
+ async for msg in async_w3.socket.process_subscriptions():
342
+ response = cast(FormattedEthSubscriptionResponse, msg)
343
+ assert response["subscription"] == sub_id
344
+ result = cast(BlockData, response["result"])
345
+ assert all(k in result.keys() for k in SOME_BLOCK_KEYS)
346
+ break
347
+
348
+ assert await async_w3.eth.unsubscribe(sub_id)
349
+ assert_no_subscriptions_left(
350
+ async_w3.subscription_manager._subscription_container
351
+ )
352
+
353
+ @pytest.mark.asyncio
354
+ async def test_async_eth_subscribe_creates_and_handles_new_heads_subscription_type(
355
+ self,
356
+ async_w3: AsyncWeb3,
357
+ ) -> None:
358
+ sub_manager = async_w3.subscription_manager
359
+ new_heads_handler_test = SubscriptionHandlerTest()
360
+
361
+ sub_id = await async_w3.eth.subscribe(
362
+ "newHeads",
363
+ handler=new_heads_handler,
364
+ handler_context={"new_heads_handler_test": new_heads_handler_test},
365
+ )
366
+ assert is_hexstr(sub_id)
367
+
368
+ assert len(sub_manager.subscriptions) == 1
369
+ sub = sub_manager.subscriptions[0]
370
+ assert isinstance(sub, NewHeadsSubscription)
371
+
372
+ await sub_manager.handle_subscriptions()
373
+
374
+ assert new_heads_handler_test.passed
375
+ assert len(sub_manager.subscriptions) == 0
376
+
377
+ assert sub_manager.total_handler_calls == 1
378
+ assert sub.handler_call_count == 1
379
+
380
+ @pytest.mark.asyncio
381
+ async def test_async_eth_subscribe_process_pending_tx_true(
382
+ self,
383
+ async_w3: AsyncWeb3,
384
+ ) -> None:
385
+ sub_id = await async_w3.eth.subscribe("newPendingTransactions", True)
386
+ assert is_hexstr(sub_id)
387
+
388
+ accts = await async_w3.eth.accounts
389
+ acct = accts[0]
390
+
391
+ num_txs = 2
392
+ original_nonce = await async_w3.eth.get_transaction_count(acct)
393
+ tx_seeder_task = asyncio.create_task(
394
+ self.seed_transactions_to_geth(async_w3, acct, num_txs=num_txs)
395
+ )
396
+
397
+ nonce = int(original_nonce)
398
+ tx_hash = None
399
+ async for msg in async_w3.socket.process_subscriptions():
400
+ response = cast(FormattedEthSubscriptionResponse, msg)
401
+ assert response["subscription"] == sub_id
402
+ result = cast(TxData, response["result"])
403
+ assert result["gas"] == 21000
404
+ assert result["from"] == acct
405
+ assert result["to"] == acct
406
+ assert int(result["value"]) == int(nonce)
407
+ tx_hash = result["hash"]
408
+ assert tx_hash is not None
409
+
410
+ nonce += 1
411
+ if nonce == int(original_nonce) + num_txs:
412
+ break
413
+
414
+ # cleanup
415
+ assert await async_w3.eth.unsubscribe(sub_id)
416
+ assert_no_subscriptions_left(
417
+ async_w3.subscription_manager._subscription_container
418
+ )
419
+ async_w3.provider._request_processor.clear_caches()
420
+ await async_w3.eth.wait_for_transaction_receipt(tx_hash)
421
+ await clean_up_task(tx_seeder_task)
422
+
423
+ @pytest.mark.asyncio
424
+ async def test_async_eth_subscribe_and_process_pending_tx_false(
425
+ self,
426
+ async_w3: AsyncWeb3,
427
+ ) -> None:
428
+ sub_id = await async_w3.eth.subscribe("newPendingTransactions")
429
+ assert is_hexstr(sub_id)
430
+
431
+ accts = await async_w3.eth.accounts
432
+ acct = accts[0]
433
+ await async_w3.eth.get_transaction_count(acct)
434
+
435
+ num_txs = 2
436
+ tx_seeder_task = asyncio.create_task(
437
+ self.seed_transactions_to_geth(async_w3, acct, num_txs=num_txs)
438
+ )
439
+
440
+ tx_hash = None
441
+ i = 0
442
+ async for msg in async_w3.socket.process_subscriptions():
443
+ response = cast(FormattedEthSubscriptionResponse, msg)
444
+ assert response["subscription"] == sub_id
445
+ assert isinstance(response["result"], HexBytes)
446
+ tx_hash = response["result"]
447
+
448
+ i += 1
449
+ if i == num_txs:
450
+ break
451
+
452
+ # cleanup
453
+ await async_w3.eth.unsubscribe(sub_id)
454
+ assert_no_subscriptions_left(
455
+ async_w3.subscription_manager._subscription_container
456
+ )
457
+ await async_w3.eth.wait_for_transaction_receipt(tx_hash)
458
+ await clean_up_task(tx_seeder_task)
459
+
460
+ @pytest.mark.asyncio
461
+ async def test_async_eth_subscribe_creates_and_handles_pending_tx_subscription_type(
462
+ self,
463
+ async_w3: AsyncWeb3,
464
+ ) -> None:
465
+ sub_manager = async_w3.subscription_manager
466
+ pending_tx_handler_test = SubscriptionHandlerTest()
467
+
468
+ sub_id = await async_w3.eth.subscribe(
469
+ "newPendingTransactions",
470
+ True,
471
+ handler=pending_tx_handler,
472
+ handler_context={"pending_tx_handler_test": pending_tx_handler_test},
473
+ )
474
+ assert is_hexstr(sub_id)
475
+
476
+ assert len(sub_manager.subscriptions) == 1
477
+ sub = sub_manager.subscriptions[0]
478
+ assert isinstance(sub, PendingTxSubscription)
479
+
480
+ # seed transactions to geth
481
+ accts = await async_w3.eth.accounts
482
+ acct = accts[0]
483
+ tx_seeder_task = asyncio.create_task(
484
+ self.seed_transactions_to_geth(async_w3, acct)
485
+ )
486
+ await sub_manager.handle_subscriptions()
487
+
488
+ assert pending_tx_handler_test.passed
489
+ assert len(sub_manager.subscriptions) == 0
490
+
491
+ assert sub_manager.total_handler_calls == 1
492
+ assert sub.handler_call_count == 1
493
+
494
+ # cleanup
495
+ sub_manager.total_handler_calls = 0
496
+ await clean_up_task(tx_seeder_task)
497
+
498
+ @pytest.mark.asyncio
499
+ async def test_async_eth_subscribe_and_process_logs(
500
+ self, async_w3: AsyncWeb3, async_emitter_contract: "AsyncContract"
501
+ ) -> None:
502
+ event = async_emitter_contract.events.LogIndexedAndNotIndexed
503
+ event_topic = async_w3.keccak(text=event.abi_element_identifier).to_0x_hex()
504
+
505
+ sub_id = await async_w3.eth.subscribe(
506
+ "logs",
507
+ {
508
+ "address": async_emitter_contract.address,
509
+ "topics": [HexStr(event_topic)],
510
+ },
511
+ )
512
+ assert is_hexstr(sub_id)
513
+
514
+ accts = await async_w3.eth.accounts
515
+ acct = accts[0]
516
+ emit_event_task = await log_indexed_and_non_indexed_args_task(
517
+ async_w3, async_emitter_contract, acct
518
+ )
519
+
520
+ async for msg in async_w3.socket.process_subscriptions():
521
+ response = cast(FormattedEthSubscriptionResponse, msg)
522
+ assert response["subscription"] == sub_id
523
+ log_receipt = cast(LogReceipt, response["result"])
524
+ event_data = event.process_log(log_receipt)
525
+ assert event_data.args.indexedAddress == INDEXED_ADDR
526
+ assert event_data.args.indexedUint256 == INDEXED_UINT256
527
+ assert event_data.args.nonIndexedAddress == NON_INDEXED_ADDR
528
+ assert event_data.args.nonIndexedUint256 == NON_INDEXED_UINT256
529
+ assert event_data.args.nonIndexedString == NON_INDEXED_STRING
304
530
  break
305
531
 
532
+ assert await async_w3.eth.unsubscribe(sub_id)
533
+ assert len(async_w3.subscription_manager.subscriptions) == 0
534
+ await clean_up_task(emit_event_task)
535
+
536
+ @pytest.mark.asyncio
537
+ async def test_async_eth_subscribe_creates_and_handles_logs_subscription_type(
538
+ self,
539
+ async_w3: AsyncWeb3,
540
+ async_emitter_contract: "AsyncContract",
541
+ ) -> None:
542
+ sub_manager = async_w3.subscription_manager
543
+
544
+ event = async_emitter_contract.events.LogIndexedAndNotIndexed
545
+
546
+ logs_handler_test = SubscriptionHandlerTest()
547
+ sub_id = await async_w3.eth.subscribe(
548
+ "logs",
549
+ {
550
+ "address": async_emitter_contract.address,
551
+ "topics": [event.topic],
552
+ },
553
+ handler=logs_handler,
554
+ handler_context={
555
+ "logs_handler_test": logs_handler_test,
556
+ "event": event,
557
+ },
558
+ )
559
+ assert is_hexstr(sub_id)
560
+
561
+ assert len(sub_manager.subscriptions) == 1
562
+ sub = sub_manager.subscriptions[0]
563
+ assert isinstance(sub, LogsSubscription)
564
+
565
+ accts = await async_w3.eth.accounts
566
+ acct = accts[0]
567
+ emit_event_task = await log_indexed_and_non_indexed_args_task(
568
+ async_w3, async_emitter_contract, acct
569
+ )
570
+
571
+ await sub_manager.handle_subscriptions()
572
+
573
+ assert logs_handler_test.passed
574
+ assert len(sub_manager.subscriptions) == 0
575
+
576
+ assert sub_manager.total_handler_calls == 1
577
+ assert sub.handler_call_count == 1
578
+
579
+ # cleanup
580
+ sub_manager.total_handler_calls = 0
581
+ await clean_up_task(emit_event_task)
582
+
306
583
  @pytest.mark.asyncio
307
584
  async def test_async_extradata_poa_middleware_on_eth_subscription(
308
585
  self,
309
- async_w3: "AsyncWeb3",
586
+ async_w3: AsyncWeb3,
310
587
  ) -> None:
311
588
  async_w3.middleware_onion.inject(
312
589
  ExtraDataToPOAMiddleware, "poa_middleware", layer=0
@@ -343,12 +620,13 @@ class PersistentConnectionProviderTest:
343
620
  break
344
621
 
345
622
  # clean up
623
+ assert await async_w3.eth.unsubscribe(sub_id)
346
624
  async_w3.middleware_onion.remove("poa_middleware")
347
625
 
348
626
  @pytest.mark.asyncio
349
627
  async def test_asyncio_gather_for_multiple_requests_matches_the_responses(
350
628
  self,
351
- async_w3: "AsyncWeb3",
629
+ async_w3: AsyncWeb3,
352
630
  ) -> None:
353
631
  (
354
632
  latest,
@@ -371,22 +649,10 @@ class PersistentConnectionProviderTest:
371
649
  assert isinstance(pending, AttributeDict)
372
650
 
373
651
  # assert block values
374
- some_block_keys = [
375
- "number",
376
- "hash",
377
- "parentHash",
378
- "transactionsRoot",
379
- "stateRoot",
380
- "receiptsRoot",
381
- "size",
382
- "gasLimit",
383
- "gasUsed",
384
- "timestamp",
385
- "transactions",
386
- "baseFeePerGas",
387
- ]
388
- assert all(k in latest.keys() for k in some_block_keys)
389
- assert all(k in pending.keys() for k in some_block_keys)
652
+ assert latest is not None
653
+ assert all(k in latest.keys() for k in SOME_BLOCK_KEYS)
654
+ assert pending is not None
655
+ assert all(k in pending.keys() for k in SOME_BLOCK_KEYS)
390
656
 
391
657
  assert isinstance(block_num, int)
392
658
  assert latest["number"] == block_num
@@ -394,3 +660,219 @@ class PersistentConnectionProviderTest:
394
660
  assert isinstance(chain_id, int)
395
661
  assert isinstance(chain_id2, int)
396
662
  assert isinstance(chain_id3, int)
663
+
664
+ @pytest.mark.asyncio
665
+ async def test_async_public_socket_api(self, async_w3: AsyncWeb3) -> None:
666
+ # clear all caches and queues
667
+ async_w3.provider._request_processor.clear_caches()
668
+
669
+ # send a request over the socket
670
+ await async_w3.socket.send(
671
+ RPCEndpoint("eth_getBlockByNumber"), ["latest", True]
672
+ )
673
+
674
+ # recv and validate the unprocessed response
675
+ response = await async_w3.socket.recv()
676
+ assert "id" in response, "Expected 'id' key in response."
677
+ assert "jsonrpc" in response, "Expected 'jsonrpc' key in response."
678
+ assert "result" in response, "Expected 'result' key in response."
679
+ assert all(k in response["result"].keys() for k in SOME_BLOCK_KEYS)
680
+ assert not isinstance(response["result"]["number"], int) # assert not processed
681
+
682
+ # make a request over the socket
683
+ response = await async_w3.socket.make_request(
684
+ RPCEndpoint("eth_getBlockByNumber"), ["latest", True]
685
+ )
686
+ assert "id" in response, "Expected 'id' key in response."
687
+ assert "jsonrpc" in response, "Expected 'jsonrpc' key in response."
688
+ assert "result" in response, "Expected 'result' key in response."
689
+ assert all(k in response["result"].keys() for k in SOME_BLOCK_KEYS)
690
+ assert not isinstance(response["result"]["number"], int) # assert not processed
691
+
692
+ @pytest.mark.asyncio
693
+ async def test_async_subscription_manager_subscribes_to_many_subscriptions(
694
+ self, async_w3: AsyncWeb3, async_emitter_contract: "AsyncContract"
695
+ ) -> None:
696
+ sub_manager = async_w3.subscription_manager
697
+
698
+ event = async_emitter_contract.events.LogIndexedAndNotIndexed
699
+ event_topic = async_w3.keccak(text=event.abi_element_identifier).to_0x_hex()
700
+
701
+ new_heads_handler_test = SubscriptionHandlerTest()
702
+ pending_tx_handler_test = SubscriptionHandlerTest()
703
+ logs_handler_test = SubscriptionHandlerTest()
704
+
705
+ await sub_manager.subscribe(
706
+ [
707
+ NewHeadsSubscription(
708
+ handler=new_heads_handler,
709
+ handler_context={"new_heads_handler_test": new_heads_handler_test},
710
+ ),
711
+ PendingTxSubscription(
712
+ full_transactions=True,
713
+ handler=pending_tx_handler,
714
+ handler_context={
715
+ "pending_tx_handler_test": pending_tx_handler_test
716
+ },
717
+ ),
718
+ LogsSubscription(
719
+ address=async_emitter_contract.address,
720
+ topics=[HexStr(event_topic)],
721
+ handler=logs_handler,
722
+ handler_context={
723
+ "logs_handler_test": logs_handler_test,
724
+ "event": event,
725
+ },
726
+ ),
727
+ ]
728
+ )
729
+
730
+ # emit contract event for `logs` subscription
731
+ accts = await async_w3.eth.accounts
732
+ acct = accts[0]
733
+ emit_event_task = await log_indexed_and_non_indexed_args_task(
734
+ async_w3, async_emitter_contract, acct
735
+ )
736
+
737
+ # get subscriptions before they are unsubscribed and removed
738
+ subs = sub_manager.subscriptions
739
+
740
+ await sub_manager.handle_subscriptions()
741
+
742
+ # assert unsubscribed and removed subscriptions
743
+ assert len(sub_manager.subscriptions) == 0
744
+
745
+ assert sub_manager.total_handler_calls == 3
746
+ assert all(sub.handler_call_count == 1 for sub in subs)
747
+
748
+ assert new_heads_handler_test.passed
749
+ assert pending_tx_handler_test.passed
750
+ assert logs_handler_test.passed
751
+
752
+ # cleanup
753
+ sub_manager.total_handler_calls = 0
754
+ await clean_up_task(emit_event_task)
755
+
756
+ @pytest.mark.asyncio
757
+ async def test_subscription_handler_context(self, async_w3: AsyncWeb3) -> None:
758
+ base_url = "http://localhost:1337"
759
+ async_beacon = AsyncBeacon(base_url)
760
+ handler_test = SubscriptionHandlerTest()
761
+
762
+ async def test_sub_handler(
763
+ handler_context: NewHeadsSubscriptionContext,
764
+ ) -> None:
765
+ beacon = handler_context.beacon
766
+ assert isinstance(beacon, AsyncBeacon)
767
+ assert beacon.base_url == base_url
768
+ assert handler_context.int1 == 1337
769
+ assert handler_context.str1 == "foo"
770
+ assert handler_context.int2 == 1999
771
+ assert handler_context.str2 == "bar"
772
+
773
+ handler_context.handler_test.passed = True
774
+ unsubscribed = await handler_context.subscription.unsubscribe()
775
+ assert unsubscribed
776
+
777
+ subscribed = await async_w3.eth.subscribe(
778
+ "newHeads",
779
+ label="foo",
780
+ handler=test_sub_handler,
781
+ handler_context={
782
+ "beacon": async_beacon,
783
+ "int1": 1337,
784
+ "str1": "foo",
785
+ "int2": 1999,
786
+ "str2": "bar",
787
+ "handler_test": handler_test,
788
+ },
789
+ )
790
+ assert is_hexstr(subscribed)
791
+
792
+ sub_manager = async_w3.subscription_manager
793
+
794
+ await sub_manager.handle_subscriptions()
795
+
796
+ assert len(sub_manager.subscriptions) == 0
797
+ assert sub_manager.total_handler_calls == 1
798
+ assert handler_test.passed
799
+
800
+ # cleanup
801
+ sub_manager.total_handler_calls = 0
802
+
803
+ @pytest.mark.asyncio
804
+ async def test_subscriptions_with_handler_and_without(
805
+ self, async_w3: AsyncWeb3
806
+ ) -> None:
807
+ handler_test = SubscriptionHandlerTest()
808
+ stream_passed = False
809
+
810
+ async def test_sub_handler(
811
+ handler_context: NewHeadsSubscriptionContext,
812
+ ) -> None:
813
+ handler_context.handler_test.passed = True
814
+ await handler_context.subscription.unsubscribe()
815
+
816
+ async def handle_subscription_stream() -> None:
817
+ nonlocal stream_passed
818
+ async for msg in async_w3.socket.process_subscriptions():
819
+ response = cast(FormattedEthSubscriptionResponse, msg)
820
+ assert sub_manager.get_by_id(response["subscription"]) is not None
821
+ assert response["result"] is not None
822
+ # wait for the handler to unsubscribe:
823
+ stream_passed = True
824
+ await async_w3.eth.unsubscribe(response["subscription"])
825
+ break
826
+
827
+ await async_w3.eth.subscribe(
828
+ "newHeads",
829
+ handler=test_sub_handler,
830
+ label="managed",
831
+ handler_context={"handler_test": handler_test},
832
+ )
833
+ await async_w3.eth.subscribe("newHeads", label="streamed")
834
+
835
+ sub_manager = async_w3.subscription_manager
836
+ assert len(sub_manager.subscriptions) == 2
837
+
838
+ await asyncio.gather(
839
+ sub_manager.handle_subscriptions(),
840
+ handle_subscription_stream(),
841
+ )
842
+
843
+ assert len(sub_manager.subscriptions) == 0
844
+ assert sub_manager.total_handler_calls == 1
845
+ assert handler_test.passed
846
+ assert stream_passed
847
+
848
+ # cleanup
849
+ sub_manager.total_handler_calls = 0
850
+
851
+ @pytest.mark.asyncio
852
+ async def test_handle_subscriptions_breaks_on_unsubscribe(
853
+ self,
854
+ async_w3: AsyncWeb3,
855
+ ) -> None:
856
+ async def unsubscribe_subs(
857
+ subs: List[Union[NewHeadsSubscription, LogsSubscription]]
858
+ ) -> None:
859
+ for sub in subs:
860
+ await sub.unsubscribe()
861
+
862
+ sub_manager = async_w3.subscription_manager
863
+ sub1 = NewHeadsSubscription(label="foo", handler=idle_handler)
864
+ sub2 = LogsSubscription(label="bar", handler=idle_handler)
865
+
866
+ await sub_manager.subscribe([sub1, sub2])
867
+
868
+ assert sub_manager.subscriptions == [sub1, sub2]
869
+
870
+ unsubscribe_task = asyncio.create_task(unsubscribe_subs([sub1, sub2]))
871
+ # With no subscriptions in the queue, ``handle_subscriptions`` should hang
872
+ # indefinitely. Test that when the last subscription is unsubscribed from,
873
+ # the method breaks out of the loop. This is done via a raised
874
+ # ``SubscriptionProcessingFinished`` within the ``TaskReliantQueue``.
875
+ await sub_manager.handle_subscriptions()
876
+
877
+ assert_no_subscriptions_left(sub_manager._subscription_container)
878
+ await clean_up_task(unsubscribe_task)