web3 7.6.1__py3-none-any.whl → 7.8.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.
- ens/async_ens.py +1 -1
- ens/ens.py +1 -1
- web3/_utils/caching/caching_utils.py +64 -0
- web3/_utils/caching/request_caching_validation.py +1 -0
- web3/_utils/events.py +1 -1
- web3/_utils/http_session_manager.py +32 -3
- web3/_utils/module_testing/eth_module.py +5 -18
- web3/_utils/module_testing/module_testing_utils.py +1 -43
- web3/_utils/module_testing/persistent_connection_provider.py +696 -207
- web3/_utils/module_testing/utils.py +99 -33
- web3/beacon/api_endpoints.py +10 -0
- web3/beacon/async_beacon.py +47 -0
- web3/beacon/beacon.py +45 -0
- web3/contract/async_contract.py +2 -206
- web3/contract/base_contract.py +217 -13
- web3/contract/contract.py +2 -205
- web3/datastructures.py +15 -16
- web3/eth/async_eth.py +23 -5
- web3/exceptions.py +7 -0
- web3/main.py +24 -3
- web3/manager.py +140 -48
- web3/method.py +1 -1
- web3/middleware/attrdict.py +12 -22
- web3/middleware/base.py +14 -6
- web3/module.py +17 -21
- web3/providers/async_base.py +23 -14
- web3/providers/base.py +6 -8
- web3/providers/ipc.py +7 -6
- web3/providers/legacy_websocket.py +1 -1
- web3/providers/persistent/async_ipc.py +5 -3
- web3/providers/persistent/persistent.py +121 -17
- web3/providers/persistent/persistent_connection.py +11 -4
- web3/providers/persistent/request_processor.py +49 -41
- web3/providers/persistent/subscription_container.py +56 -0
- web3/providers/persistent/subscription_manager.py +298 -0
- web3/providers/persistent/websocket.py +4 -4
- web3/providers/rpc/async_rpc.py +16 -3
- web3/providers/rpc/rpc.py +9 -5
- web3/types.py +28 -14
- web3/utils/__init__.py +4 -0
- web3/utils/subscriptions.py +289 -0
- {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/LICENSE +1 -1
- {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/METADATA +68 -56
- {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/RECORD +46 -43
- {web3-7.6.1.dist-info → web3-7.8.0.dist-info}/WHEEL +1 -1
- {web3-7.6.1.dist-info → web3-7.8.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,16 +39,43 @@ from web3.middleware import (
|
|
|
22
39
|
ExtraDataToPOAMiddleware,
|
|
23
40
|
)
|
|
24
41
|
from web3.types import (
|
|
42
|
+
BlockData,
|
|
25
43
|
FormattedEthSubscriptionResponse,
|
|
44
|
+
LogReceipt,
|
|
45
|
+
Nonce,
|
|
26
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,
|
|
27
60
|
)
|
|
28
61
|
|
|
29
62
|
if TYPE_CHECKING:
|
|
30
|
-
from web3.
|
|
31
|
-
|
|
63
|
+
from web3.contract.async_contract import (
|
|
64
|
+
AsyncContract,
|
|
65
|
+
AsyncContractFunction,
|
|
66
|
+
)
|
|
67
|
+
from web3.providers.persistent.subscription_manager import (
|
|
68
|
+
SubscriptionContainer,
|
|
32
69
|
)
|
|
33
70
|
|
|
34
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
|
+
|
|
35
79
|
SOME_BLOCK_KEYS = [
|
|
36
80
|
"number",
|
|
37
81
|
"hash",
|
|
@@ -39,214 +83,182 @@ SOME_BLOCK_KEYS = [
|
|
|
39
83
|
"transactionsRoot",
|
|
40
84
|
"stateRoot",
|
|
41
85
|
"receiptsRoot",
|
|
42
|
-
"size",
|
|
43
86
|
"gasLimit",
|
|
44
87
|
"gasUsed",
|
|
45
88
|
"timestamp",
|
|
46
|
-
"transactions",
|
|
47
89
|
"baseFeePerGas",
|
|
90
|
+
"withdrawalsRoot",
|
|
48
91
|
]
|
|
49
92
|
|
|
50
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
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
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
|
+
|
|
51
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
|
+
|
|
52
258
|
@pytest.mark.asyncio
|
|
53
259
|
@pytest.mark.parametrize(
|
|
54
260
|
"subscription_params,ws_subscription_response,expected_formatted_result",
|
|
55
261
|
(
|
|
56
|
-
(
|
|
57
|
-
("newHeads",),
|
|
58
|
-
{
|
|
59
|
-
"jsonrpc": "2.0",
|
|
60
|
-
"method": "eth_subscription",
|
|
61
|
-
"params": {
|
|
62
|
-
"subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
|
|
63
|
-
"result": {
|
|
64
|
-
"number": "0x539",
|
|
65
|
-
"hash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
|
|
66
|
-
"parentHash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
|
|
67
|
-
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", # noqa: E501
|
|
68
|
-
"logsBloom": "0x00",
|
|
69
|
-
"transactionsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
|
|
70
|
-
"stateRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
|
|
71
|
-
"receiptsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
|
|
72
|
-
"miner": "0x0000000000000000000000000000000000000000",
|
|
73
|
-
"difficulty": "0x0",
|
|
74
|
-
"extraData": "0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465", # noqa: E501
|
|
75
|
-
"gasLimit": "0x1c9c380",
|
|
76
|
-
"gasUsed": "0xd1ce44",
|
|
77
|
-
"timestamp": "0x539",
|
|
78
|
-
"baseFeePerGas": "0x26f93fef9",
|
|
79
|
-
"withdrawalsRoot": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
|
|
80
|
-
"nonce": "0x0000000000000000",
|
|
81
|
-
"mixHash": "0x73e9e036ec894047f29954571d4b6d9e8717de7304269c263cbf150caa4e0768", # noqa: E501
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
AttributeDict(
|
|
86
|
-
{
|
|
87
|
-
"number": 1337,
|
|
88
|
-
"hash": HexBytes(
|
|
89
|
-
"0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
|
|
90
|
-
),
|
|
91
|
-
"parentHash": HexBytes(
|
|
92
|
-
"0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
|
|
93
|
-
),
|
|
94
|
-
"sha3Uncles": HexBytes(
|
|
95
|
-
"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" # noqa: E501
|
|
96
|
-
),
|
|
97
|
-
"logsBloom": HexBytes("0x00"),
|
|
98
|
-
"transactionsRoot": HexBytes(
|
|
99
|
-
"0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
|
|
100
|
-
),
|
|
101
|
-
"stateRoot": HexBytes(
|
|
102
|
-
"0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
|
|
103
|
-
),
|
|
104
|
-
"receiptsRoot": HexBytes(
|
|
105
|
-
"0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
|
|
106
|
-
),
|
|
107
|
-
"miner": "0x0000000000000000000000000000000000000000",
|
|
108
|
-
"difficulty": 0,
|
|
109
|
-
"extraData": HexBytes(
|
|
110
|
-
"0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465" # noqa: E501
|
|
111
|
-
),
|
|
112
|
-
"gasLimit": 30000000,
|
|
113
|
-
"gasUsed": 13749828,
|
|
114
|
-
"timestamp": 1337,
|
|
115
|
-
"baseFeePerGas": 10461904633,
|
|
116
|
-
"withdrawalsRoot": HexBytes(
|
|
117
|
-
"0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
|
|
118
|
-
),
|
|
119
|
-
"nonce": HexBytes("0x0000000000000000"),
|
|
120
|
-
"mixHash": HexBytes(
|
|
121
|
-
"0x73e9e036ec894047f29954571d4b6d9e8717de7304269c263cbf150caa4e0768" # noqa: E501
|
|
122
|
-
),
|
|
123
|
-
}
|
|
124
|
-
),
|
|
125
|
-
),
|
|
126
|
-
(
|
|
127
|
-
("newPendingTransactions", True),
|
|
128
|
-
{
|
|
129
|
-
"jsonrpc": "2.0",
|
|
130
|
-
"method": "eth_subscription",
|
|
131
|
-
"params": {
|
|
132
|
-
"subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
|
|
133
|
-
"result": {
|
|
134
|
-
"blockHash": None,
|
|
135
|
-
"blockNumber": None,
|
|
136
|
-
"from": "0x0000000000000000000000000000000000000000",
|
|
137
|
-
"gas": "0xf2f4",
|
|
138
|
-
"gasPrice": "0x29035f36f",
|
|
139
|
-
"maxFeePerGas": "0x29035f36f",
|
|
140
|
-
"maxPriorityFeePerGas": "0x3b9aca00",
|
|
141
|
-
"hash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
|
|
142
|
-
"input": "0x00",
|
|
143
|
-
"nonce": "0x2013",
|
|
144
|
-
"to": "0x0000000000000000000000000000000000000000",
|
|
145
|
-
"transactionIndex": None,
|
|
146
|
-
"value": "0x0",
|
|
147
|
-
"type": "0x2",
|
|
148
|
-
"accessList": [],
|
|
149
|
-
"chainId": "0x1",
|
|
150
|
-
"v": "0x1",
|
|
151
|
-
"r": "0x3c144a7c00ed3118d55445cd5be2ae4620ca377f7c685e9c5f3687671d4dece1", # noqa: E501
|
|
152
|
-
"s": "0x284de67cbf75fec8a9edb368dee3a37cf6faba87f0af4413b2f869ebfa87d002", # noqa: E501
|
|
153
|
-
"yParity": "0x1",
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
AttributeDict(
|
|
158
|
-
{
|
|
159
|
-
"blockHash": None,
|
|
160
|
-
"blockNumber": None,
|
|
161
|
-
"from": "0x0000000000000000000000000000000000000000",
|
|
162
|
-
"gas": 62196,
|
|
163
|
-
"gasPrice": 11009389423,
|
|
164
|
-
"maxFeePerGas": 11009389423,
|
|
165
|
-
"maxPriorityFeePerGas": 1000000000,
|
|
166
|
-
"hash": HexBytes(
|
|
167
|
-
"0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
|
|
168
|
-
),
|
|
169
|
-
"input": HexBytes("0x00"),
|
|
170
|
-
"nonce": 8211,
|
|
171
|
-
"to": "0x0000000000000000000000000000000000000000",
|
|
172
|
-
"transactionIndex": None,
|
|
173
|
-
"value": 0,
|
|
174
|
-
"type": 2,
|
|
175
|
-
"accessList": [],
|
|
176
|
-
"chainId": 1,
|
|
177
|
-
"v": 1,
|
|
178
|
-
"r": HexBytes(
|
|
179
|
-
"0x3c144a7c00ed3118d55445cd5be2ae4620ca377f7c685e9c5f3687671d4dece1" # noqa: E501
|
|
180
|
-
),
|
|
181
|
-
"s": HexBytes(
|
|
182
|
-
"0x284de67cbf75fec8a9edb368dee3a37cf6faba87f0af4413b2f869ebfa87d002" # noqa: E501
|
|
183
|
-
),
|
|
184
|
-
"yParity": 1,
|
|
185
|
-
}
|
|
186
|
-
),
|
|
187
|
-
),
|
|
188
|
-
(
|
|
189
|
-
("newPendingTransactions", False),
|
|
190
|
-
{
|
|
191
|
-
"jsonrpc": "2.0",
|
|
192
|
-
"method": "eth_subscription",
|
|
193
|
-
"params": {
|
|
194
|
-
"subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
|
|
195
|
-
"result": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
HexBytes(
|
|
199
|
-
"0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e"
|
|
200
|
-
),
|
|
201
|
-
),
|
|
202
|
-
(
|
|
203
|
-
("logs", {"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"}),
|
|
204
|
-
{
|
|
205
|
-
"jsonrpc": "2.0",
|
|
206
|
-
"method": "eth_subscription",
|
|
207
|
-
"params": {
|
|
208
|
-
"subscription": "THIS_WILL_BE_REPLACED_IN_THE_TEST",
|
|
209
|
-
"result": {
|
|
210
|
-
"removed": False,
|
|
211
|
-
"logIndex": "0x0",
|
|
212
|
-
"transactionIndex": "0x0",
|
|
213
|
-
"transactionHash": "0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988", # noqa: E501
|
|
214
|
-
"blockHash": "0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e", # noqa: E501
|
|
215
|
-
"blockNumber": "0x539",
|
|
216
|
-
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
217
|
-
"data": "0x00",
|
|
218
|
-
"topics": [
|
|
219
|
-
"0xe1fffdd4923d04f559f4d29e8bfc6cda04eb5b0d3c460751c2402c5c5cc9105c", # noqa: E501
|
|
220
|
-
"0x00000000000000000000000016250d5630b4cf539739df2c5dacb4c659f2482d", # noqa: E501
|
|
221
|
-
],
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
AttributeDict(
|
|
226
|
-
{
|
|
227
|
-
"removed": False,
|
|
228
|
-
"logIndex": 0,
|
|
229
|
-
"transactionIndex": 0,
|
|
230
|
-
"transactionHash": HexBytes(
|
|
231
|
-
"0x56260fe8298aff6d360e3a68fa855693f25dcb2708d8a7e509e8519b265d3988" # noqa: E501
|
|
232
|
-
),
|
|
233
|
-
"blockHash": HexBytes(
|
|
234
|
-
"0xb46b85928f2c2264c2bf7ad5c6d6985664f1527e744193ef990cc0d3da5afc5e" # noqa: E501
|
|
235
|
-
),
|
|
236
|
-
"blockNumber": 1337,
|
|
237
|
-
"address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
238
|
-
"data": HexBytes("0x00"),
|
|
239
|
-
"topics": [
|
|
240
|
-
HexBytes(
|
|
241
|
-
"0xe1fffdd4923d04f559f4d29e8bfc6cda04eb5b0d3c460751c2402c5c5cc9105c" # noqa: E501
|
|
242
|
-
),
|
|
243
|
-
HexBytes(
|
|
244
|
-
"0x00000000000000000000000016250d5630b4cf539739df2c5dacb4c659f2482d" # noqa: E501
|
|
245
|
-
),
|
|
246
|
-
],
|
|
247
|
-
}
|
|
248
|
-
),
|
|
249
|
-
),
|
|
250
262
|
(
|
|
251
263
|
("syncing",),
|
|
252
264
|
{
|
|
@@ -285,17 +297,13 @@ class PersistentConnectionProviderTest:
|
|
|
285
297
|
),
|
|
286
298
|
),
|
|
287
299
|
ids=[
|
|
288
|
-
"newHeads",
|
|
289
|
-
"newPendingTransactions-FullTxs",
|
|
290
|
-
"newPendingTransactions-TxHashes",
|
|
291
|
-
"logs",
|
|
292
300
|
"syncing-False",
|
|
293
301
|
"syncing-True",
|
|
294
302
|
],
|
|
295
303
|
)
|
|
296
|
-
async def
|
|
304
|
+
async def test_async_eth_subscribe_syncing_mocked(
|
|
297
305
|
self,
|
|
298
|
-
async_w3:
|
|
306
|
+
async_w3: AsyncWeb3,
|
|
299
307
|
subscription_params: Tuple[Any, ...],
|
|
300
308
|
ws_subscription_response: Dict[str, Any],
|
|
301
309
|
expected_formatted_result: Any,
|
|
@@ -321,10 +329,261 @@ class PersistentConnectionProviderTest:
|
|
|
321
329
|
await async_w3.eth.unsubscribe(sub_id)
|
|
322
330
|
break
|
|
323
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
|
|
530
|
+
break
|
|
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
|
+
|
|
324
583
|
@pytest.mark.asyncio
|
|
325
584
|
async def test_async_extradata_poa_middleware_on_eth_subscription(
|
|
326
585
|
self,
|
|
327
|
-
async_w3:
|
|
586
|
+
async_w3: AsyncWeb3,
|
|
328
587
|
) -> None:
|
|
329
588
|
async_w3.middleware_onion.inject(
|
|
330
589
|
ExtraDataToPOAMiddleware, "poa_middleware", layer=0
|
|
@@ -361,12 +620,13 @@ class PersistentConnectionProviderTest:
|
|
|
361
620
|
break
|
|
362
621
|
|
|
363
622
|
# clean up
|
|
623
|
+
assert await async_w3.eth.unsubscribe(sub_id)
|
|
364
624
|
async_w3.middleware_onion.remove("poa_middleware")
|
|
365
625
|
|
|
366
626
|
@pytest.mark.asyncio
|
|
367
627
|
async def test_asyncio_gather_for_multiple_requests_matches_the_responses(
|
|
368
628
|
self,
|
|
369
|
-
async_w3:
|
|
629
|
+
async_w3: AsyncWeb3,
|
|
370
630
|
) -> None:
|
|
371
631
|
(
|
|
372
632
|
latest,
|
|
@@ -402,7 +662,10 @@ class PersistentConnectionProviderTest:
|
|
|
402
662
|
assert isinstance(chain_id3, int)
|
|
403
663
|
|
|
404
664
|
@pytest.mark.asyncio
|
|
405
|
-
async def
|
|
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
|
+
|
|
406
669
|
# send a request over the socket
|
|
407
670
|
await async_w3.socket.send(
|
|
408
671
|
RPCEndpoint("eth_getBlockByNumber"), ["latest", True]
|
|
@@ -425,3 +688,229 @@ class PersistentConnectionProviderTest:
|
|
|
425
688
|
assert "result" in response, "Expected 'result' key in response."
|
|
426
689
|
assert all(k in response["result"].keys() for k in SOME_BLOCK_KEYS)
|
|
427
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)
|
|
879
|
+
|
|
880
|
+
@pytest.mark.asyncio
|
|
881
|
+
async def test_run_forever_starts_with_0_subs_and_runs_until_task_cancelled(
|
|
882
|
+
self, async_w3: AsyncWeb3
|
|
883
|
+
) -> None:
|
|
884
|
+
sub_manager = async_w3.subscription_manager
|
|
885
|
+
assert_no_subscriptions_left(sub_manager._subscription_container)
|
|
886
|
+
|
|
887
|
+
run_forever_task = asyncio.create_task(
|
|
888
|
+
sub_manager.handle_subscriptions(run_forever=True)
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
await asyncio.sleep(0.1)
|
|
892
|
+
assert run_forever_task.done() is False
|
|
893
|
+
assert sub_manager.subscriptions == []
|
|
894
|
+
|
|
895
|
+
# subscribe to newHeads and validate it
|
|
896
|
+
new_heads_handler_test = SubscriptionHandlerTest()
|
|
897
|
+
sub1 = NewHeadsSubscription(
|
|
898
|
+
label="foo",
|
|
899
|
+
handler=new_heads_handler,
|
|
900
|
+
handler_context={"new_heads_handler_test": new_heads_handler_test},
|
|
901
|
+
)
|
|
902
|
+
sub_id = await sub_manager.subscribe(sub1)
|
|
903
|
+
assert is_hexstr(sub_id)
|
|
904
|
+
assert len(sub_manager.subscriptions) == 1
|
|
905
|
+
assert sub_manager.subscriptions[0] == sub1
|
|
906
|
+
|
|
907
|
+
# wait for the handler to unsubscribe
|
|
908
|
+
while sub_manager.subscriptions:
|
|
909
|
+
await asyncio.sleep(0.1)
|
|
910
|
+
|
|
911
|
+
assert new_heads_handler_test.passed
|
|
912
|
+
assert run_forever_task.done() is False
|
|
913
|
+
assert run_forever_task.cancelled() is False
|
|
914
|
+
|
|
915
|
+
# cleanup
|
|
916
|
+
await clean_up_task(run_forever_task)
|