brawny 0.1.13__py3-none-any.whl → 0.1.22__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.
- brawny/__init__.py +2 -0
- brawny/_context.py +5 -5
- brawny/_rpc/__init__.py +36 -12
- brawny/_rpc/broadcast.py +14 -13
- brawny/_rpc/caller.py +243 -0
- brawny/_rpc/client.py +539 -0
- brawny/_rpc/clients.py +11 -11
- brawny/_rpc/context.py +23 -0
- brawny/_rpc/errors.py +465 -31
- brawny/_rpc/gas.py +7 -6
- brawny/_rpc/pool.py +18 -0
- brawny/_rpc/retry.py +266 -0
- brawny/_rpc/retry_policy.py +81 -0
- brawny/accounts.py +28 -9
- brawny/alerts/__init__.py +15 -18
- brawny/alerts/abi_resolver.py +212 -36
- brawny/alerts/base.py +2 -2
- brawny/alerts/contracts.py +77 -10
- brawny/alerts/errors.py +30 -3
- brawny/alerts/events.py +38 -5
- brawny/alerts/health.py +19 -13
- brawny/alerts/send.py +513 -55
- brawny/api.py +39 -11
- brawny/assets/AGENTS.md +325 -0
- brawny/async_runtime.py +48 -0
- brawny/chain.py +3 -3
- brawny/cli/commands/__init__.py +2 -0
- brawny/cli/commands/console.py +69 -19
- brawny/cli/commands/contract.py +2 -2
- brawny/cli/commands/controls.py +121 -0
- brawny/cli/commands/health.py +2 -2
- brawny/cli/commands/job_dev.py +6 -5
- brawny/cli/commands/jobs.py +99 -2
- brawny/cli/commands/maintenance.py +13 -29
- brawny/cli/commands/migrate.py +1 -0
- brawny/cli/commands/run.py +10 -3
- brawny/cli/commands/script.py +8 -3
- brawny/cli/commands/signer.py +143 -26
- brawny/cli/helpers.py +0 -3
- brawny/cli_templates.py +25 -349
- brawny/config/__init__.py +4 -1
- brawny/config/models.py +43 -57
- brawny/config/parser.py +268 -57
- brawny/config/validation.py +52 -15
- brawny/daemon/context.py +4 -2
- brawny/daemon/core.py +185 -63
- brawny/daemon/loops.py +166 -98
- brawny/daemon/supervisor.py +261 -0
- brawny/db/__init__.py +14 -26
- brawny/db/base.py +248 -151
- brawny/db/global_cache.py +11 -1
- brawny/db/migrate.py +175 -28
- brawny/db/migrations/001_init.sql +4 -3
- brawny/db/migrations/010_add_nonce_gap_index.sql +1 -1
- brawny/db/migrations/011_add_job_logs.sql +1 -2
- brawny/db/migrations/012_add_claimed_by.sql +2 -2
- brawny/db/migrations/013_attempt_unique.sql +10 -0
- brawny/db/migrations/014_add_lease_expires_at.sql +5 -0
- brawny/db/migrations/015_add_signer_alias.sql +14 -0
- brawny/db/migrations/016_runtime_controls_and_quarantine.sql +32 -0
- brawny/db/migrations/017_add_job_drain.sql +6 -0
- brawny/db/migrations/018_add_nonce_reset_audit.sql +20 -0
- brawny/db/migrations/019_add_job_cooldowns.sql +8 -0
- brawny/db/migrations/020_attempt_unique_initial.sql +7 -0
- brawny/db/ops/__init__.py +3 -25
- brawny/db/ops/logs.py +1 -2
- brawny/db/queries.py +47 -91
- brawny/db/serialized.py +65 -0
- brawny/db/sqlite/__init__.py +1001 -0
- brawny/db/sqlite/connection.py +231 -0
- brawny/db/sqlite/execute.py +116 -0
- brawny/db/sqlite/mappers.py +190 -0
- brawny/db/sqlite/repos/attempts.py +372 -0
- brawny/db/sqlite/repos/block_state.py +102 -0
- brawny/db/sqlite/repos/cache.py +104 -0
- brawny/db/sqlite/repos/intents.py +1021 -0
- brawny/db/sqlite/repos/jobs.py +200 -0
- brawny/db/sqlite/repos/maintenance.py +182 -0
- brawny/db/sqlite/repos/signers_nonces.py +566 -0
- brawny/db/sqlite/tx.py +119 -0
- brawny/http.py +194 -0
- brawny/invariants.py +11 -24
- brawny/jobs/base.py +8 -0
- brawny/jobs/job_validation.py +2 -1
- brawny/keystore.py +83 -7
- brawny/lifecycle.py +64 -12
- brawny/logging.py +0 -2
- brawny/metrics.py +84 -12
- brawny/model/contexts.py +111 -9
- brawny/model/enums.py +1 -0
- brawny/model/errors.py +18 -0
- brawny/model/types.py +47 -131
- brawny/network_guard.py +133 -0
- brawny/networks/__init__.py +5 -5
- brawny/networks/config.py +1 -7
- brawny/networks/manager.py +14 -11
- brawny/runtime_controls.py +74 -0
- brawny/scheduler/poller.py +11 -7
- brawny/scheduler/reorg.py +95 -39
- brawny/scheduler/runner.py +442 -168
- brawny/scheduler/shutdown.py +3 -3
- brawny/script_tx.py +3 -3
- brawny/telegram.py +53 -7
- brawny/testing.py +1 -0
- brawny/timeout.py +38 -0
- brawny/tx/executor.py +922 -308
- brawny/tx/intent.py +54 -16
- brawny/tx/monitor.py +31 -12
- brawny/tx/nonce.py +212 -90
- brawny/tx/replacement.py +69 -18
- brawny/tx/retry_policy.py +24 -0
- brawny/tx/stages/types.py +75 -0
- brawny/types.py +18 -0
- brawny/utils.py +41 -0
- {brawny-0.1.13.dist-info → brawny-0.1.22.dist-info}/METADATA +3 -3
- brawny-0.1.22.dist-info/RECORD +163 -0
- brawny/_rpc/manager.py +0 -982
- brawny/_rpc/selector.py +0 -156
- brawny/db/base_new.py +0 -165
- brawny/db/mappers.py +0 -182
- brawny/db/migrations/008_add_transactions.sql +0 -72
- brawny/db/ops/attempts.py +0 -108
- brawny/db/ops/blocks.py +0 -83
- brawny/db/ops/cache.py +0 -93
- brawny/db/ops/intents.py +0 -296
- brawny/db/ops/jobs.py +0 -110
- brawny/db/ops/nonces.py +0 -322
- brawny/db/postgres.py +0 -2535
- brawny/db/postgres_new.py +0 -196
- brawny/db/sqlite.py +0 -2733
- brawny/db/sqlite_new.py +0 -191
- brawny-0.1.13.dist-info/RECORD +0 -141
- {brawny-0.1.13.dist-info → brawny-0.1.22.dist-info}/WHEEL +0 -0
- {brawny-0.1.13.dist-info → brawny-0.1.22.dist-info}/entry_points.txt +0 -0
- {brawny-0.1.13.dist-info → brawny-0.1.22.dist-info}/top_level.txt +0 -0
brawny/tx/intent.py
CHANGED
|
@@ -17,10 +17,14 @@ from uuid import UUID, uuid4
|
|
|
17
17
|
from brawny.logging import LogEvents, get_logger
|
|
18
18
|
from brawny.metrics import INTENT_TRANSITIONS, get_metrics
|
|
19
19
|
from brawny.model.enums import IntentStatus
|
|
20
|
+
from brawny.model.errors import CancelledCheckError
|
|
20
21
|
from brawny.model.types import TxIntent, TxIntentSpec, Trigger, idempotency_key
|
|
22
|
+
from brawny.types import ClaimedIntent
|
|
23
|
+
from brawny.utils import db_address, is_valid_address
|
|
21
24
|
|
|
22
25
|
if TYPE_CHECKING:
|
|
23
26
|
from brawny.db.base import Database
|
|
27
|
+
from brawny.model.contexts import CancellationToken
|
|
24
28
|
|
|
25
29
|
logger = get_logger(__name__)
|
|
26
30
|
|
|
@@ -58,6 +62,7 @@ def create_intent(
|
|
|
58
62
|
broadcast_group: str | None = None,
|
|
59
63
|
broadcast_endpoints: list[str] | None = None,
|
|
60
64
|
trigger: Trigger | None = None,
|
|
65
|
+
cancellation_token: "CancellationToken | None" = None,
|
|
61
66
|
) -> tuple[TxIntent, bool]:
|
|
62
67
|
"""Create a new transaction intent with idempotency.
|
|
63
68
|
|
|
@@ -76,13 +81,28 @@ def create_intent(
|
|
|
76
81
|
Returns:
|
|
77
82
|
Tuple of (intent, is_new) where is_new is True if newly created
|
|
78
83
|
"""
|
|
84
|
+
# Resolve signer alias early; idempotency is scoped to canonical address.
|
|
85
|
+
signer_alias: str | None = None
|
|
86
|
+
if is_valid_address(spec.signer_address):
|
|
87
|
+
resolved_signer = spec.signer_address
|
|
88
|
+
else:
|
|
89
|
+
if isinstance(spec.signer_address, str) and spec.signer_address.startswith("0x"):
|
|
90
|
+
raise ValueError(f"Invalid signer address: {spec.signer_address}")
|
|
91
|
+
from brawny.api import get_address_from_alias
|
|
92
|
+
|
|
93
|
+
signer_alias = spec.signer_address
|
|
94
|
+
resolved_signer = get_address_from_alias(spec.signer_address)
|
|
95
|
+
|
|
96
|
+
canonical_signer = db_address(resolved_signer)
|
|
97
|
+
canonical_to = db_address(spec.to_address)
|
|
98
|
+
|
|
79
99
|
# Generate idempotency key from job_id and parts
|
|
80
100
|
idem_key = idempotency_key(job_id, *idem_parts)
|
|
81
101
|
|
|
82
102
|
# Check for existing intent (scoped to chain + signer)
|
|
83
103
|
existing = db.get_intent_by_idempotency_key(
|
|
84
104
|
chain_id=chain_id,
|
|
85
|
-
signer_address=
|
|
105
|
+
signer_address=canonical_signer,
|
|
86
106
|
idempotency_key=idem_key,
|
|
87
107
|
)
|
|
88
108
|
if existing:
|
|
@@ -91,12 +111,15 @@ def create_intent(
|
|
|
91
111
|
job_id=job_id,
|
|
92
112
|
idempotency_key=idem_key,
|
|
93
113
|
chain_id=chain_id,
|
|
94
|
-
signer=
|
|
114
|
+
signer=canonical_signer,
|
|
95
115
|
existing_intent_id=str(existing.intent_id),
|
|
96
116
|
existing_status=existing.status.value,
|
|
97
117
|
)
|
|
98
118
|
return existing, False
|
|
99
119
|
|
|
120
|
+
if cancellation_token is not None and cancellation_token.cancelled:
|
|
121
|
+
raise CancelledCheckError("Check cancelled before intent creation")
|
|
122
|
+
|
|
100
123
|
# Calculate deadline if specified
|
|
101
124
|
deadline_ts: datetime | None = None
|
|
102
125
|
if spec.deadline_seconds:
|
|
@@ -118,9 +141,10 @@ def create_intent(
|
|
|
118
141
|
intent_id=intent_id,
|
|
119
142
|
job_id=job_id,
|
|
120
143
|
chain_id=chain_id,
|
|
121
|
-
signer_address=
|
|
144
|
+
signer_address=canonical_signer,
|
|
145
|
+
signer_alias=signer_alias,
|
|
122
146
|
idempotency_key=idem_key,
|
|
123
|
-
to_address=
|
|
147
|
+
to_address=canonical_to,
|
|
124
148
|
data=spec.data,
|
|
125
149
|
value_wei=spec.value_wei,
|
|
126
150
|
gas_limit=spec.gas_limit,
|
|
@@ -138,7 +162,7 @@ def create_intent(
|
|
|
138
162
|
# This is expected with idempotency - just get the existing one
|
|
139
163
|
existing = db.get_intent_by_idempotency_key(
|
|
140
164
|
chain_id=chain_id,
|
|
141
|
-
signer_address=
|
|
165
|
+
signer_address=canonical_signer,
|
|
142
166
|
idempotency_key=idem_key,
|
|
143
167
|
)
|
|
144
168
|
if existing:
|
|
@@ -147,7 +171,7 @@ def create_intent(
|
|
|
147
171
|
job_id=job_id,
|
|
148
172
|
idempotency_key=idem_key,
|
|
149
173
|
chain_id=chain_id,
|
|
150
|
-
signer=
|
|
174
|
+
signer=canonical_signer,
|
|
151
175
|
existing_intent_id=str(existing.intent_id),
|
|
152
176
|
note="race_condition",
|
|
153
177
|
)
|
|
@@ -160,8 +184,8 @@ def create_intent(
|
|
|
160
184
|
intent_id=str(intent.intent_id),
|
|
161
185
|
job_id=job_id,
|
|
162
186
|
idempotency_key=idem_key,
|
|
163
|
-
signer=
|
|
164
|
-
to=
|
|
187
|
+
signer=canonical_signer,
|
|
188
|
+
to=canonical_to,
|
|
165
189
|
)
|
|
166
190
|
|
|
167
191
|
return intent, True
|
|
@@ -207,11 +231,11 @@ def claim_intent(
|
|
|
207
231
|
db: Database,
|
|
208
232
|
worker_id: str,
|
|
209
233
|
claimed_by: str | None = None,
|
|
210
|
-
|
|
234
|
+
lease_seconds: int = 300,
|
|
235
|
+
) -> ClaimedIntent | None:
|
|
211
236
|
"""Claim the next available intent for processing.
|
|
212
237
|
|
|
213
|
-
Uses
|
|
214
|
-
IMMEDIATE transaction locking (SQLite) to prevent
|
|
238
|
+
Uses IMMEDIATE transaction locking (SQLite) to prevent
|
|
215
239
|
multiple workers from claiming the same intent.
|
|
216
240
|
|
|
217
241
|
Args:
|
|
@@ -224,18 +248,21 @@ def claim_intent(
|
|
|
224
248
|
# Generate unique claim token
|
|
225
249
|
claim_token = f"{worker_id}_{uuid4().hex[:8]}"
|
|
226
250
|
|
|
227
|
-
|
|
251
|
+
claimed = db.claim_next_intent(
|
|
252
|
+
claim_token,
|
|
253
|
+
claimed_by=claimed_by,
|
|
254
|
+
lease_seconds=lease_seconds,
|
|
255
|
+
)
|
|
228
256
|
|
|
229
|
-
if
|
|
257
|
+
if claimed:
|
|
230
258
|
logger.info(
|
|
231
259
|
LogEvents.INTENT_CLAIM,
|
|
232
|
-
intent_id=str(
|
|
233
|
-
job_id=intent.job_id,
|
|
260
|
+
intent_id=str(claimed.intent_id),
|
|
234
261
|
worker_id=worker_id,
|
|
235
262
|
claim_token=claim_token,
|
|
236
263
|
)
|
|
237
264
|
|
|
238
|
-
return
|
|
265
|
+
return claimed
|
|
239
266
|
|
|
240
267
|
|
|
241
268
|
def release_claim(db: Database, intent_id: UUID) -> bool:
|
|
@@ -297,6 +324,8 @@ def transition_intent(
|
|
|
297
324
|
to_status: IntentStatus,
|
|
298
325
|
reason: str,
|
|
299
326
|
chain_id: int | None = None,
|
|
327
|
+
actor: str | None = None,
|
|
328
|
+
source: str | None = None,
|
|
300
329
|
) -> bool:
|
|
301
330
|
"""Transition an intent using the centralized transition map.
|
|
302
331
|
|
|
@@ -333,6 +362,15 @@ def transition_intent(
|
|
|
333
362
|
to_status=to_status.value,
|
|
334
363
|
reason=reason,
|
|
335
364
|
)
|
|
365
|
+
db.record_mutation_audit(
|
|
366
|
+
entity_type="intent",
|
|
367
|
+
entity_id=str(intent_id),
|
|
368
|
+
action=f"transition:{to_status.value}",
|
|
369
|
+
actor=actor,
|
|
370
|
+
reason=reason,
|
|
371
|
+
source=source,
|
|
372
|
+
metadata={"from_status": old_status, "to_status": to_status.value},
|
|
373
|
+
)
|
|
336
374
|
logger.info(
|
|
337
375
|
"intent.transition",
|
|
338
376
|
intent_id=str(intent_id),
|
brawny/tx/monitor.py
CHANGED
|
@@ -30,6 +30,7 @@ from brawny.metrics import (
|
|
|
30
30
|
from brawny.model.enums import AttemptStatus, IntentStatus, NonceStatus
|
|
31
31
|
from brawny.model.errors import DatabaseError, FailureType, FailureStage
|
|
32
32
|
from brawny._rpc.errors import RPCError
|
|
33
|
+
from brawny.timeout import Deadline
|
|
33
34
|
from brawny.tx.intent import transition_intent
|
|
34
35
|
|
|
35
36
|
if TYPE_CHECKING:
|
|
@@ -37,11 +38,13 @@ if TYPE_CHECKING:
|
|
|
37
38
|
from brawny.db.base import Database
|
|
38
39
|
from brawny.lifecycle import LifecycleDispatcher
|
|
39
40
|
from brawny.model.types import TxAttempt, TxIntent
|
|
40
|
-
from brawny._rpc.
|
|
41
|
+
from brawny._rpc.clients import ReadClient
|
|
41
42
|
from brawny.tx.nonce import NonceManager
|
|
42
43
|
|
|
43
44
|
logger = get_logger(__name__)
|
|
44
45
|
|
|
46
|
+
MONITOR_TICK_TIMEOUT_SECONDS = 10.0
|
|
47
|
+
|
|
45
48
|
|
|
46
49
|
class ConfirmationResult(str, Enum):
|
|
47
50
|
"""Result of confirmation monitoring."""
|
|
@@ -78,7 +81,7 @@ class TxMonitor:
|
|
|
78
81
|
def __init__(
|
|
79
82
|
self,
|
|
80
83
|
db: Database,
|
|
81
|
-
rpc:
|
|
84
|
+
rpc: ReadClient,
|
|
82
85
|
nonce_manager: NonceManager,
|
|
83
86
|
config: Config,
|
|
84
87
|
lifecycle: "LifecycleDispatcher | None" = None,
|
|
@@ -101,6 +104,7 @@ class TxMonitor:
|
|
|
101
104
|
self,
|
|
102
105
|
intent: TxIntent,
|
|
103
106
|
attempt: TxAttempt,
|
|
107
|
+
deadline: Deadline | None = None,
|
|
104
108
|
) -> ConfirmationStatus:
|
|
105
109
|
"""Check confirmation status for a transaction attempt.
|
|
106
110
|
|
|
@@ -114,6 +118,8 @@ class TxMonitor:
|
|
|
114
118
|
Returns:
|
|
115
119
|
Current confirmation status
|
|
116
120
|
"""
|
|
121
|
+
if deadline is not None and deadline.expired():
|
|
122
|
+
return ConfirmationStatus(result=ConfirmationResult.PENDING)
|
|
117
123
|
if not attempt.tx_hash:
|
|
118
124
|
logger.warning(
|
|
119
125
|
"monitor.no_tx_hash",
|
|
@@ -123,11 +129,11 @@ class TxMonitor:
|
|
|
123
129
|
return ConfirmationStatus(result=ConfirmationResult.PENDING)
|
|
124
130
|
|
|
125
131
|
# Get receipt
|
|
126
|
-
receipt = self._rpc.get_transaction_receipt(attempt.tx_hash)
|
|
132
|
+
receipt = self._rpc.get_transaction_receipt(attempt.tx_hash, deadline=deadline)
|
|
127
133
|
|
|
128
134
|
if receipt is None:
|
|
129
135
|
# No receipt yet - check if nonce has been consumed by another tx
|
|
130
|
-
if self._is_nonce_consumed(intent, attempt):
|
|
136
|
+
if self._is_nonce_consumed(intent, attempt, deadline):
|
|
131
137
|
metrics = get_metrics()
|
|
132
138
|
metrics.counter(TX_FAILED).inc(
|
|
133
139
|
chain_id=intent.chain_id,
|
|
@@ -137,7 +143,7 @@ class TxMonitor:
|
|
|
137
143
|
return ConfirmationStatus(result=ConfirmationResult.DROPPED)
|
|
138
144
|
|
|
139
145
|
# Check if stuck
|
|
140
|
-
if self._is_stuck(attempt):
|
|
146
|
+
if self._is_stuck(attempt, deadline):
|
|
141
147
|
metrics = get_metrics()
|
|
142
148
|
metrics.counter(TX_FAILED).inc(
|
|
143
149
|
chain_id=intent.chain_id,
|
|
@@ -161,7 +167,7 @@ class TxMonitor:
|
|
|
161
167
|
|
|
162
168
|
# Verify block hash matches current chain
|
|
163
169
|
try:
|
|
164
|
-
current_block = self._rpc.get_block(receipt_block_number)
|
|
170
|
+
current_block = self._rpc.get_block(receipt_block_number, deadline=deadline)
|
|
165
171
|
current_hash = current_block.get("hash")
|
|
166
172
|
if hasattr(current_hash, "hex"):
|
|
167
173
|
current_hash = current_hash.hex()
|
|
@@ -188,7 +194,7 @@ class TxMonitor:
|
|
|
188
194
|
return ConfirmationStatus(result=ConfirmationResult.PENDING)
|
|
189
195
|
|
|
190
196
|
# Count confirmations
|
|
191
|
-
current_block_number = self._rpc.get_block_number()
|
|
197
|
+
current_block_number = self._rpc.get_block_number(deadline=deadline)
|
|
192
198
|
confirmations = current_block_number - receipt_block_number + 1
|
|
193
199
|
|
|
194
200
|
# Check if confirmed with enough confirmations
|
|
@@ -241,7 +247,12 @@ class TxMonitor:
|
|
|
241
247
|
block_hash=receipt_block_hash,
|
|
242
248
|
)
|
|
243
249
|
|
|
244
|
-
def _is_nonce_consumed(
|
|
250
|
+
def _is_nonce_consumed(
|
|
251
|
+
self,
|
|
252
|
+
intent: TxIntent,
|
|
253
|
+
attempt: TxAttempt,
|
|
254
|
+
deadline: Deadline | None,
|
|
255
|
+
) -> bool:
|
|
245
256
|
"""Check if the nonce has been consumed by another transaction.
|
|
246
257
|
|
|
247
258
|
Args:
|
|
@@ -257,12 +268,13 @@ class TxMonitor:
|
|
|
257
268
|
chain_nonce = self._rpc.get_transaction_count(
|
|
258
269
|
signer_address,
|
|
259
270
|
"latest", # Use "latest" not "pending" to check confirmed
|
|
271
|
+
deadline=deadline,
|
|
260
272
|
)
|
|
261
273
|
|
|
262
274
|
# If chain nonce is greater than our nonce, it was consumed
|
|
263
275
|
if chain_nonce > attempt.nonce:
|
|
264
276
|
# Verify our tx isn't the one that consumed it
|
|
265
|
-
receipt = self._rpc.get_transaction_receipt(attempt.tx_hash)
|
|
277
|
+
receipt = self._rpc.get_transaction_receipt(attempt.tx_hash, deadline=deadline)
|
|
266
278
|
if receipt is None:
|
|
267
279
|
# Nonce consumed but not by our tx
|
|
268
280
|
logger.warning(
|
|
@@ -281,7 +293,7 @@ class TxMonitor:
|
|
|
281
293
|
)
|
|
282
294
|
return False
|
|
283
295
|
|
|
284
|
-
def _is_stuck(self, attempt: TxAttempt) -> bool:
|
|
296
|
+
def _is_stuck(self, attempt: TxAttempt, deadline: Deadline | None) -> bool:
|
|
285
297
|
"""Check if transaction is stuck.
|
|
286
298
|
|
|
287
299
|
Stuck is defined as:
|
|
@@ -304,7 +316,7 @@ class TxMonitor:
|
|
|
304
316
|
|
|
305
317
|
# Check blocks elapsed
|
|
306
318
|
try:
|
|
307
|
-
current_block = self._rpc.get_block_number()
|
|
319
|
+
current_block = self._rpc.get_block_number(deadline=deadline)
|
|
308
320
|
blocks_since = current_block - attempt.broadcast_block
|
|
309
321
|
if blocks_since > self._config.stuck_tx_blocks:
|
|
310
322
|
return True
|
|
@@ -584,10 +596,17 @@ class TxMonitor:
|
|
|
584
596
|
}
|
|
585
597
|
|
|
586
598
|
pending = self.get_pending_attempts()
|
|
599
|
+
deadline = Deadline.from_seconds(MONITOR_TICK_TIMEOUT_SECONDS)
|
|
587
600
|
|
|
588
601
|
for intent, attempt in pending:
|
|
602
|
+
if deadline.expired():
|
|
603
|
+
logger.warning(
|
|
604
|
+
"monitor.tick_timeout",
|
|
605
|
+
pending_remaining=len(pending),
|
|
606
|
+
)
|
|
607
|
+
break
|
|
589
608
|
try:
|
|
590
|
-
status = self.check_confirmation(intent, attempt)
|
|
609
|
+
status = self.check_confirmation(intent, attempt, deadline=deadline)
|
|
591
610
|
|
|
592
611
|
if status.result == ConfirmationResult.CONFIRMED:
|
|
593
612
|
self.handle_confirmed(intent, attempt, status)
|