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/db/queries.py
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
"""Canonical SQL queries for brawny database operations.
|
|
2
|
-
|
|
3
|
-
All queries use :name placeholder style.
|
|
4
|
-
- SQLite: Supports :name natively with dict params
|
|
5
|
-
- Postgres: Rewritten to %(name)s in postgres.py
|
|
6
|
-
|
|
7
|
-
Dialect-specific queries use dict format: {"postgres": "...", "sqlite": "..."}
|
|
8
|
-
"""
|
|
1
|
+
"""Canonical SQL queries for brawny database operations (SQLite-only)."""
|
|
9
2
|
|
|
10
3
|
from __future__ import annotations
|
|
11
4
|
|
|
@@ -70,7 +63,11 @@ GET_OLDEST_BLOCK_IN_HISTORY = """
|
|
|
70
63
|
|
|
71
64
|
GET_JOB = "SELECT * FROM jobs WHERE job_id = :job_id"
|
|
72
65
|
|
|
73
|
-
GET_ENABLED_JOBS =
|
|
66
|
+
GET_ENABLED_JOBS = (
|
|
67
|
+
"SELECT * FROM jobs WHERE enabled = 1 "
|
|
68
|
+
"AND (drain_until IS NULL OR drain_until <= CURRENT_TIMESTAMP) "
|
|
69
|
+
"ORDER BY job_id"
|
|
70
|
+
)
|
|
74
71
|
|
|
75
72
|
LIST_ALL_JOBS = "SELECT * FROM jobs ORDER BY job_id"
|
|
76
73
|
|
|
@@ -231,19 +228,11 @@ UPDATE_NONCE_RESERVATION_STATUS_WITH_INTENT = """
|
|
|
231
228
|
WHERE chain_id = :chain_id AND signer_address = :address AND nonce = :nonce
|
|
232
229
|
"""
|
|
233
230
|
|
|
234
|
-
#
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
WHERE chain_id = :chain_id AND signer_address = :address
|
|
240
|
-
FOR UPDATE
|
|
241
|
-
""",
|
|
242
|
-
"sqlite": """
|
|
243
|
-
SELECT * FROM signers
|
|
244
|
-
WHERE chain_id = :chain_id AND signer_address = :address
|
|
245
|
-
""",
|
|
246
|
-
}
|
|
231
|
+
# Lock signer for nonce reservation (SQLite uses BEGIN IMMEDIATE in caller).
|
|
232
|
+
LOCK_SIGNER_FOR_UPDATE = """
|
|
233
|
+
SELECT * FROM signers
|
|
234
|
+
WHERE chain_id = :chain_id AND signer_address = :address
|
|
235
|
+
"""
|
|
247
236
|
|
|
248
237
|
ENSURE_SIGNER_EXISTS = """
|
|
249
238
|
INSERT INTO signers (chain_id, signer_address, next_nonce, last_synced_chain_nonce)
|
|
@@ -251,21 +240,13 @@ ENSURE_SIGNER_EXISTS = """
|
|
|
251
240
|
ON CONFLICT(chain_id, signer_address) DO NOTHING
|
|
252
241
|
"""
|
|
253
242
|
|
|
254
|
-
#
|
|
255
|
-
CLEANUP_ORPHANED_NONCES =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
""",
|
|
262
|
-
"sqlite": """
|
|
263
|
-
DELETE FROM nonce_reservations
|
|
264
|
-
WHERE chain_id = :chain_id
|
|
265
|
-
AND status = 'orphaned'
|
|
266
|
-
AND updated_at < datetime('now', :hours_offset)
|
|
267
|
-
""",
|
|
268
|
-
}
|
|
243
|
+
# Cleanup orphaned nonces (SQLite).
|
|
244
|
+
CLEANUP_ORPHANED_NONCES = """
|
|
245
|
+
DELETE FROM nonce_reservations
|
|
246
|
+
WHERE chain_id = :chain_id
|
|
247
|
+
AND status = 'orphaned'
|
|
248
|
+
AND updated_at < datetime('now', :hours_offset)
|
|
249
|
+
"""
|
|
269
250
|
|
|
270
251
|
# =============================================================================
|
|
271
252
|
# Intents
|
|
@@ -298,43 +279,24 @@ GET_INTENT_BY_IDEMPOTENCY_KEY = """
|
|
|
298
279
|
AND idempotency_key = :idempotency_key
|
|
299
280
|
"""
|
|
300
281
|
|
|
301
|
-
#
|
|
302
|
-
CLAIM_NEXT_INTENT =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
RETURNING *
|
|
320
|
-
""",
|
|
321
|
-
"sqlite": """
|
|
322
|
-
UPDATE tx_intents
|
|
323
|
-
SET status = 'claimed', claim_token = :claim_token,
|
|
324
|
-
claimed_at = CURRENT_TIMESTAMP, claimed_by = :claimed_by,
|
|
325
|
-
retry_after = NULL,
|
|
326
|
-
updated_at = CURRENT_TIMESTAMP
|
|
327
|
-
WHERE intent_id = (
|
|
328
|
-
SELECT intent_id FROM tx_intents
|
|
329
|
-
WHERE status = 'created'
|
|
330
|
-
AND (deadline_ts IS NULL OR deadline_ts > CURRENT_TIMESTAMP)
|
|
331
|
-
AND (retry_after IS NULL OR retry_after <= CURRENT_TIMESTAMP)
|
|
332
|
-
ORDER BY created_at ASC
|
|
333
|
-
LIMIT 1
|
|
334
|
-
)
|
|
335
|
-
RETURNING *
|
|
336
|
-
""",
|
|
337
|
-
}
|
|
282
|
+
# Claim next intent (SQLite).
|
|
283
|
+
CLAIM_NEXT_INTENT = """
|
|
284
|
+
UPDATE tx_intents
|
|
285
|
+
SET status = 'claimed', claim_token = :claim_token,
|
|
286
|
+
claimed_at = CURRENT_TIMESTAMP, claimed_by = :claimed_by,
|
|
287
|
+
lease_expires_at = datetime(CURRENT_TIMESTAMP, :lease_offset),
|
|
288
|
+
retry_after = NULL,
|
|
289
|
+
updated_at = CURRENT_TIMESTAMP
|
|
290
|
+
WHERE intent_id = (
|
|
291
|
+
SELECT intent_id FROM tx_intents
|
|
292
|
+
WHERE status = 'created'
|
|
293
|
+
AND (deadline_ts IS NULL OR deadline_ts > CURRENT_TIMESTAMP)
|
|
294
|
+
AND (retry_after IS NULL OR retry_after <= CURRENT_TIMESTAMP)
|
|
295
|
+
ORDER BY created_at ASC
|
|
296
|
+
LIMIT 1
|
|
297
|
+
)
|
|
298
|
+
RETURNING *
|
|
299
|
+
"""
|
|
338
300
|
|
|
339
301
|
UPDATE_INTENT_STATUS = """
|
|
340
302
|
UPDATE tx_intents
|
|
@@ -414,10 +376,12 @@ GET_BACKING_OFF_INTENT_COUNT = """
|
|
|
414
376
|
CREATE_ATTEMPT = """
|
|
415
377
|
INSERT INTO tx_attempts (
|
|
416
378
|
attempt_id, intent_id, nonce, tx_hash, gas_params_json,
|
|
417
|
-
status, broadcast_block, broadcast_at, broadcast_group, endpoint_url
|
|
379
|
+
status, broadcast_block, broadcast_at, broadcast_group, endpoint_url,
|
|
380
|
+
endpoint_binding_id
|
|
418
381
|
) VALUES (
|
|
419
382
|
:attempt_id, :intent_id, :nonce, :tx_hash, :gas_params_json,
|
|
420
|
-
:status, :broadcast_block, :broadcast_at, :broadcast_group, :endpoint_url
|
|
383
|
+
:status, :broadcast_block, :broadcast_at, :broadcast_group, :endpoint_url,
|
|
384
|
+
:endpoint_binding_id
|
|
421
385
|
)
|
|
422
386
|
RETURNING *
|
|
423
387
|
"""
|
|
@@ -562,21 +526,13 @@ LIST_ALL_JOB_LOGS = """
|
|
|
562
526
|
LIMIT :limit
|
|
563
527
|
"""
|
|
564
528
|
|
|
565
|
-
LIST_LATEST_JOB_LOGS =
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
"sqlite": """
|
|
573
|
-
SELECT * FROM job_logs l1
|
|
574
|
-
WHERE chain_id = :chain_id
|
|
575
|
-
AND ts = (SELECT MAX(ts) FROM job_logs l2
|
|
576
|
-
WHERE l2.job_id = l1.job_id AND l2.chain_id = :chain_id)
|
|
577
|
-
ORDER BY job_id
|
|
578
|
-
""",
|
|
579
|
-
}
|
|
529
|
+
LIST_LATEST_JOB_LOGS = """
|
|
530
|
+
SELECT * FROM job_logs l1
|
|
531
|
+
WHERE chain_id = :chain_id
|
|
532
|
+
AND ts = (SELECT MAX(ts) FROM job_logs l2
|
|
533
|
+
WHERE l2.job_id = l1.job_id AND l2.chain_id = :chain_id)
|
|
534
|
+
ORDER BY job_id
|
|
535
|
+
"""
|
|
580
536
|
|
|
581
537
|
DELETE_OLD_JOB_LOGS = """
|
|
582
538
|
DELETE FROM job_logs
|
brawny/db/serialized.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
from typing import Any, Iterator
|
|
6
|
+
|
|
7
|
+
from brawny.model.errors import DatabaseError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SerializedDatabase:
|
|
11
|
+
"""Serialize all DB access with a single process-wide lock."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, inner: Any) -> None:
|
|
14
|
+
self._inner = inner
|
|
15
|
+
self._lock = threading.RLock()
|
|
16
|
+
self._closed = False
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def dialect(self) -> str:
|
|
20
|
+
return self._inner.dialect
|
|
21
|
+
|
|
22
|
+
def _ensure_open(self) -> None:
|
|
23
|
+
if self._closed:
|
|
24
|
+
raise DatabaseError("Database is closed")
|
|
25
|
+
|
|
26
|
+
def connect(self) -> None:
|
|
27
|
+
self._ensure_open()
|
|
28
|
+
with self._lock:
|
|
29
|
+
self._ensure_open()
|
|
30
|
+
self._inner.connect()
|
|
31
|
+
|
|
32
|
+
def close(self) -> None:
|
|
33
|
+
with self._lock:
|
|
34
|
+
if not self._closed:
|
|
35
|
+
self._inner.close()
|
|
36
|
+
self._closed = True
|
|
37
|
+
|
|
38
|
+
def is_connected(self) -> bool:
|
|
39
|
+
if self._closed:
|
|
40
|
+
return False
|
|
41
|
+
with self._lock:
|
|
42
|
+
return self._inner.is_connected()
|
|
43
|
+
|
|
44
|
+
@contextmanager
|
|
45
|
+
def transaction(self, isolation_level: str | None = None) -> Iterator[None]:
|
|
46
|
+
self._ensure_open()
|
|
47
|
+
with self._lock:
|
|
48
|
+
self._ensure_open()
|
|
49
|
+
with self._inner.transaction(isolation_level):
|
|
50
|
+
yield
|
|
51
|
+
|
|
52
|
+
def __getattr__(self, name: str) -> Any:
|
|
53
|
+
if name.startswith("_"):
|
|
54
|
+
raise AttributeError(name)
|
|
55
|
+
attr = getattr(self._inner, name)
|
|
56
|
+
if not callable(attr):
|
|
57
|
+
return attr
|
|
58
|
+
|
|
59
|
+
def _wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
60
|
+
self._ensure_open()
|
|
61
|
+
with self._lock:
|
|
62
|
+
self._ensure_open()
|
|
63
|
+
return attr(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
return _wrapped
|