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/cli/commands/contract.py
CHANGED
|
@@ -52,7 +52,7 @@ def contract_call(
|
|
|
52
52
|
from brawny.alerts.contracts import ContractSystem
|
|
53
53
|
from brawny.logging import get_logger, setup_logging
|
|
54
54
|
from brawny.model.enums import LogFormat
|
|
55
|
-
from brawny._rpc import
|
|
55
|
+
from brawny._rpc.clients import ReadClient
|
|
56
56
|
|
|
57
57
|
if not config_path or not os.path.exists(config_path):
|
|
58
58
|
click.echo(
|
|
@@ -74,7 +74,7 @@ def contract_call(
|
|
|
74
74
|
config=config.redacted_dict(),
|
|
75
75
|
)
|
|
76
76
|
|
|
77
|
-
rpc =
|
|
77
|
+
rpc = ReadClient.from_config(config)
|
|
78
78
|
# ContractSystem uses global ABI cache at ~/.brawny/abi_cache.db
|
|
79
79
|
contract_system = ContractSystem(rpc, config)
|
|
80
80
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Runtime controls commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def controls() -> None:
|
|
12
|
+
"""Manage runtime controls."""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@controls.command("list")
|
|
17
|
+
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
18
|
+
def controls_list(config_path: str | None) -> None:
|
|
19
|
+
"""List runtime controls."""
|
|
20
|
+
from brawny.cli.helpers import get_db
|
|
21
|
+
|
|
22
|
+
db = get_db(config_path)
|
|
23
|
+
try:
|
|
24
|
+
controls_list = db.list_runtime_controls()
|
|
25
|
+
if not controls_list:
|
|
26
|
+
click.echo("No runtime controls set.")
|
|
27
|
+
return
|
|
28
|
+
click.echo()
|
|
29
|
+
for rc in controls_list:
|
|
30
|
+
status = "active" if rc.active else "inactive"
|
|
31
|
+
expires_at = rc.expires_at.isoformat() if rc.expires_at else "none"
|
|
32
|
+
click.echo(
|
|
33
|
+
f" {rc.control}: {status} expires_at={expires_at} mode={rc.mode}"
|
|
34
|
+
)
|
|
35
|
+
if rc.reason:
|
|
36
|
+
click.echo(f" reason: {rc.reason}")
|
|
37
|
+
if rc.actor:
|
|
38
|
+
click.echo(f" actor: {rc.actor}")
|
|
39
|
+
click.echo()
|
|
40
|
+
finally:
|
|
41
|
+
db.close()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@controls.command("activate")
|
|
45
|
+
@click.argument("control")
|
|
46
|
+
@click.option(
|
|
47
|
+
"--ttl-seconds",
|
|
48
|
+
type=int,
|
|
49
|
+
default=900,
|
|
50
|
+
show_default=True,
|
|
51
|
+
help="TTL in seconds (ignored if --forever)",
|
|
52
|
+
)
|
|
53
|
+
@click.option("--forever", is_flag=True, help="Set without expiration")
|
|
54
|
+
@click.option("--reason", "reason", default=None, help="Reason for activation")
|
|
55
|
+
@click.option("--actor", "actor", default="cli", help="Actor label for audit")
|
|
56
|
+
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
57
|
+
def controls_activate(
|
|
58
|
+
control: str,
|
|
59
|
+
ttl_seconds: int,
|
|
60
|
+
forever: bool,
|
|
61
|
+
reason: str | None,
|
|
62
|
+
actor: str,
|
|
63
|
+
config_path: str | None,
|
|
64
|
+
) -> None:
|
|
65
|
+
"""Activate a runtime control."""
|
|
66
|
+
from brawny.cli.helpers import get_db
|
|
67
|
+
|
|
68
|
+
expires_at = None
|
|
69
|
+
if not forever:
|
|
70
|
+
expires_at = datetime.utcnow() + timedelta(seconds=ttl_seconds)
|
|
71
|
+
|
|
72
|
+
db = get_db(config_path)
|
|
73
|
+
try:
|
|
74
|
+
db.set_runtime_control(
|
|
75
|
+
control=control,
|
|
76
|
+
active=True,
|
|
77
|
+
expires_at=expires_at,
|
|
78
|
+
reason=reason,
|
|
79
|
+
actor=actor,
|
|
80
|
+
mode="manual",
|
|
81
|
+
)
|
|
82
|
+
if expires_at:
|
|
83
|
+
click.echo(f"Control '{control}' activated until {expires_at.isoformat()}.")
|
|
84
|
+
else:
|
|
85
|
+
click.echo(f"Control '{control}' activated with no expiration.")
|
|
86
|
+
finally:
|
|
87
|
+
db.close()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@controls.command("deactivate")
|
|
91
|
+
@click.argument("control")
|
|
92
|
+
@click.option("--reason", "reason", default=None, help="Reason for deactivation")
|
|
93
|
+
@click.option("--actor", "actor", default="cli", help="Actor label for audit")
|
|
94
|
+
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
95
|
+
def controls_deactivate(
|
|
96
|
+
control: str,
|
|
97
|
+
reason: str | None,
|
|
98
|
+
actor: str,
|
|
99
|
+
config_path: str | None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Deactivate a runtime control."""
|
|
102
|
+
from brawny.cli.helpers import get_db
|
|
103
|
+
|
|
104
|
+
db = get_db(config_path)
|
|
105
|
+
try:
|
|
106
|
+
db.set_runtime_control(
|
|
107
|
+
control=control,
|
|
108
|
+
active=False,
|
|
109
|
+
expires_at=None,
|
|
110
|
+
reason=reason,
|
|
111
|
+
actor=actor,
|
|
112
|
+
mode="manual",
|
|
113
|
+
)
|
|
114
|
+
click.echo(f"Control '{control}' deactivated.")
|
|
115
|
+
finally:
|
|
116
|
+
db.close()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def register(main) -> None:
|
|
120
|
+
"""Register runtime controls commands."""
|
|
121
|
+
main.add_command(controls)
|
brawny/cli/commands/health.py
CHANGED
|
@@ -17,7 +17,7 @@ from brawny.cli.helpers import get_db, print_json
|
|
|
17
17
|
def health(fmt: str, config_path: str | None) -> None:
|
|
18
18
|
"""Health check endpoint."""
|
|
19
19
|
from brawny.config import Config, get_config
|
|
20
|
-
from brawny._rpc import
|
|
20
|
+
from brawny._rpc.clients import ReadClient
|
|
21
21
|
|
|
22
22
|
try:
|
|
23
23
|
if config_path:
|
|
@@ -47,7 +47,7 @@ def health(fmt: str, config_path: str | None) -> None:
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
try:
|
|
50
|
-
rpc =
|
|
50
|
+
rpc = ReadClient.from_config(config)
|
|
51
51
|
rpc_health = rpc.get_health()
|
|
52
52
|
result["components"]["rpc"] = {
|
|
53
53
|
"status": "ok" if rpc_health["healthy_endpoints"] > 0 else "degraded",
|
brawny/cli/commands/job_dev.py
CHANGED
|
@@ -48,7 +48,7 @@ def job_run(
|
|
|
48
48
|
from brawny.logging import get_logger, setup_logging
|
|
49
49
|
from brawny.model.enums import LogFormat
|
|
50
50
|
from brawny.model.types import BlockInfo, JobContext, idempotency_key
|
|
51
|
-
from brawny._rpc import
|
|
51
|
+
from brawny._rpc.clients import ReadClient
|
|
52
52
|
from brawny.scripting import set_job_context
|
|
53
53
|
|
|
54
54
|
if not config_path or not os.path.exists(config_path):
|
|
@@ -73,16 +73,13 @@ def job_run(
|
|
|
73
73
|
|
|
74
74
|
db = create_database(
|
|
75
75
|
config.database_url,
|
|
76
|
-
pool_size=config.database_pool_size,
|
|
77
|
-
pool_max_overflow=config.database_pool_max_overflow,
|
|
78
|
-
pool_timeout=config.database_pool_timeout_seconds,
|
|
79
76
|
circuit_breaker_failures=config.db_circuit_breaker_failures,
|
|
80
77
|
circuit_breaker_seconds=config.db_circuit_breaker_seconds,
|
|
81
78
|
)
|
|
82
79
|
db.connect()
|
|
83
80
|
|
|
84
81
|
try:
|
|
85
|
-
rpc =
|
|
82
|
+
rpc = ReadClient.from_config(config)
|
|
86
83
|
contract_system = ContractSystem(rpc, config)
|
|
87
84
|
|
|
88
85
|
modules = list(jobs_modules)
|
|
@@ -128,11 +125,15 @@ def job_run(
|
|
|
128
125
|
err=True,
|
|
129
126
|
)
|
|
130
127
|
|
|
128
|
+
base_fee = 0
|
|
129
|
+
if block_data:
|
|
130
|
+
base_fee = block_data.get("baseFeePerGas", 0)
|
|
131
131
|
block = BlockInfo(
|
|
132
132
|
chain_id=config.chain_id,
|
|
133
133
|
block_number=block_number,
|
|
134
134
|
block_hash=block_hash,
|
|
135
135
|
timestamp=timestamp,
|
|
136
|
+
base_fee=base_fee,
|
|
136
137
|
)
|
|
137
138
|
|
|
138
139
|
job_config = db.get_job(job_id)
|
brawny/cli/commands/jobs.py
CHANGED
|
@@ -23,6 +23,8 @@ def jobs_list(config_path: str | None) -> None:
|
|
|
23
23
|
|
|
24
24
|
suppress_logging()
|
|
25
25
|
|
|
26
|
+
from datetime import datetime
|
|
27
|
+
|
|
26
28
|
from brawny.cli.helpers import discover_jobs_for_cli, get_config, get_db
|
|
27
29
|
from brawny.jobs.registry import get_registry
|
|
28
30
|
|
|
@@ -52,6 +54,18 @@ def jobs_list(config_path: str | None) -> None:
|
|
|
52
54
|
return
|
|
53
55
|
|
|
54
56
|
click.echo()
|
|
57
|
+
def _parse_drain_until(value):
|
|
58
|
+
if value is None:
|
|
59
|
+
return None
|
|
60
|
+
if isinstance(value, datetime):
|
|
61
|
+
return value
|
|
62
|
+
try:
|
|
63
|
+
return datetime.fromisoformat(value)
|
|
64
|
+
except ValueError:
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
now = datetime.utcnow()
|
|
68
|
+
|
|
55
69
|
for job_id in sorted(code_jobs.keys()):
|
|
56
70
|
job = code_jobs[job_id]
|
|
57
71
|
db_job = db_jobs.get(job_id)
|
|
@@ -62,13 +76,21 @@ def jobs_list(config_path: str | None) -> None:
|
|
|
62
76
|
# Get enabled status from DB, default to True for new jobs
|
|
63
77
|
enabled = db_job.enabled if db_job else True
|
|
64
78
|
|
|
79
|
+
drain_until = _parse_drain_until(db_job.drain_until) if db_job else None
|
|
80
|
+
draining = drain_until is not None and drain_until > now
|
|
81
|
+
|
|
65
82
|
# Status indicator
|
|
66
|
-
if
|
|
83
|
+
if draining:
|
|
84
|
+
status = click.style("! draining", fg="yellow")
|
|
85
|
+
elif enabled:
|
|
67
86
|
status = click.style("✓ enabled ", fg="green")
|
|
68
87
|
else:
|
|
69
88
|
status = click.style("✗ disabled", fg="red")
|
|
70
89
|
|
|
71
|
-
|
|
90
|
+
line = f" {status} {job_id} {click.style(f'every {interval} blocks', dim=True)}"
|
|
91
|
+
if draining:
|
|
92
|
+
line += click.style(f" (until {drain_until.isoformat()})", dim=True)
|
|
93
|
+
click.echo(line)
|
|
72
94
|
|
|
73
95
|
click.echo()
|
|
74
96
|
|
|
@@ -206,6 +228,81 @@ def jobs_disable(job_id: str, config_path: str | None) -> None:
|
|
|
206
228
|
db.close()
|
|
207
229
|
|
|
208
230
|
|
|
231
|
+
@jobs.command("drain")
|
|
232
|
+
@click.argument("job_id")
|
|
233
|
+
@click.option(
|
|
234
|
+
"--ttl-seconds",
|
|
235
|
+
type=int,
|
|
236
|
+
default=3600,
|
|
237
|
+
show_default=True,
|
|
238
|
+
help="Drain duration in seconds (ignored if --until is set)",
|
|
239
|
+
)
|
|
240
|
+
@click.option(
|
|
241
|
+
"--until",
|
|
242
|
+
"until_iso",
|
|
243
|
+
default=None,
|
|
244
|
+
help="Drain until ISO timestamp (e.g. 2025-01-01T00:00:00)",
|
|
245
|
+
)
|
|
246
|
+
@click.option("--reason", "-r", default=None, help="Reason for drain")
|
|
247
|
+
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
248
|
+
def jobs_drain(
|
|
249
|
+
job_id: str,
|
|
250
|
+
ttl_seconds: int,
|
|
251
|
+
until_iso: str | None,
|
|
252
|
+
reason: str | None,
|
|
253
|
+
config_path: str | None,
|
|
254
|
+
) -> None:
|
|
255
|
+
"""Drain a job (pause new intents) until a timestamp."""
|
|
256
|
+
from datetime import datetime, timedelta
|
|
257
|
+
|
|
258
|
+
from brawny.cli.helpers import get_db
|
|
259
|
+
|
|
260
|
+
if until_iso:
|
|
261
|
+
try:
|
|
262
|
+
drain_until = datetime.fromisoformat(until_iso)
|
|
263
|
+
except ValueError:
|
|
264
|
+
click.echo("Invalid --until format, expected ISO timestamp.", err=True)
|
|
265
|
+
raise SystemExit(1)
|
|
266
|
+
else:
|
|
267
|
+
drain_until = datetime.utcnow() + timedelta(seconds=ttl_seconds)
|
|
268
|
+
|
|
269
|
+
db = get_db(config_path)
|
|
270
|
+
try:
|
|
271
|
+
updated = db.set_job_drain(
|
|
272
|
+
job_id,
|
|
273
|
+
drain_until=drain_until,
|
|
274
|
+
reason=reason,
|
|
275
|
+
actor="cli",
|
|
276
|
+
source="cli",
|
|
277
|
+
)
|
|
278
|
+
if updated:
|
|
279
|
+
click.echo(
|
|
280
|
+
f"Job '{job_id}' drained until {drain_until.isoformat()}."
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
click.echo(f"Job '{job_id}' not found.", err=True)
|
|
284
|
+
finally:
|
|
285
|
+
db.close()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@jobs.command("undrain")
|
|
289
|
+
@click.argument("job_id")
|
|
290
|
+
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
291
|
+
def jobs_undrain(job_id: str, config_path: str | None) -> None:
|
|
292
|
+
"""Clear job drain."""
|
|
293
|
+
from brawny.cli.helpers import get_db
|
|
294
|
+
|
|
295
|
+
db = get_db(config_path)
|
|
296
|
+
try:
|
|
297
|
+
updated = db.clear_job_drain(job_id, actor="cli", source="cli")
|
|
298
|
+
if updated:
|
|
299
|
+
click.echo(f"Job '{job_id}' undrained.")
|
|
300
|
+
else:
|
|
301
|
+
click.echo(f"Job '{job_id}' not found.", err=True)
|
|
302
|
+
finally:
|
|
303
|
+
db.close()
|
|
304
|
+
|
|
305
|
+
|
|
209
306
|
@jobs.command("remove")
|
|
210
307
|
@click.argument("job_id")
|
|
211
308
|
@click.option("--config", "config_path", default=None, help="Path to config.yaml")
|
|
@@ -12,7 +12,7 @@ def reconcile() -> None:
|
|
|
12
12
|
"""Run nonce reconciliation."""
|
|
13
13
|
from brawny.config import get_config
|
|
14
14
|
from brawny.db import create_database
|
|
15
|
-
from brawny._rpc import
|
|
15
|
+
from brawny._rpc.clients import ReadClient
|
|
16
16
|
from brawny.tx.nonce import NonceManager
|
|
17
17
|
|
|
18
18
|
click.echo("Running nonce reconciliation...")
|
|
@@ -20,16 +20,13 @@ def reconcile() -> None:
|
|
|
20
20
|
config = get_config()
|
|
21
21
|
db = create_database(
|
|
22
22
|
config.database_url,
|
|
23
|
-
pool_size=config.database_pool_size,
|
|
24
|
-
pool_max_overflow=config.database_pool_max_overflow,
|
|
25
|
-
pool_timeout=config.database_pool_timeout_seconds,
|
|
26
23
|
circuit_breaker_failures=config.db_circuit_breaker_failures,
|
|
27
24
|
circuit_breaker_seconds=config.db_circuit_breaker_seconds,
|
|
28
25
|
)
|
|
29
26
|
db.connect()
|
|
30
27
|
|
|
31
28
|
try:
|
|
32
|
-
rpc =
|
|
29
|
+
rpc = ReadClient.from_config(config)
|
|
33
30
|
nonce_manager = NonceManager(db, rpc, config.chain_id)
|
|
34
31
|
nonce_manager.reconcile()
|
|
35
32
|
click.echo("Nonce reconciliation complete.")
|
|
@@ -124,30 +121,17 @@ def repair_claims(
|
|
|
124
121
|
config = get_config(config_path)
|
|
125
122
|
db = get_db(config_path)
|
|
126
123
|
try:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
stuck = db.execute_returning(query, (config.chain_id, -older_than, limit))
|
|
139
|
-
else:
|
|
140
|
-
query = """
|
|
141
|
-
SELECT i.intent_id, i.job_id, i.claimed_at
|
|
142
|
-
FROM tx_intents i
|
|
143
|
-
WHERE i.chain_id = %s
|
|
144
|
-
AND i.status = 'claimed'
|
|
145
|
-
AND (i.claimed_at IS NULL OR i.claimed_at < NOW() - make_interval(mins => %s))
|
|
146
|
-
AND NOT EXISTS (SELECT 1 FROM tx_attempts a WHERE a.intent_id = i.intent_id)
|
|
147
|
-
ORDER BY i.claimed_at ASC NULLS FIRST
|
|
148
|
-
LIMIT %s
|
|
149
|
-
"""
|
|
150
|
-
stuck = db.execute_returning(query, (config.chain_id, older_than, limit))
|
|
124
|
+
query = """
|
|
125
|
+
SELECT i.intent_id, i.job_id, i.claimed_at
|
|
126
|
+
FROM tx_intents i
|
|
127
|
+
WHERE i.chain_id = ?
|
|
128
|
+
AND i.status = 'claimed'
|
|
129
|
+
AND (i.claimed_at IS NULL OR datetime(i.claimed_at) < datetime('now', ? || ' minutes'))
|
|
130
|
+
AND NOT EXISTS (SELECT 1 FROM tx_attempts a WHERE a.intent_id = i.intent_id)
|
|
131
|
+
ORDER BY (i.claimed_at IS NOT NULL), i.claimed_at ASC
|
|
132
|
+
LIMIT ?
|
|
133
|
+
"""
|
|
134
|
+
stuck = db.execute_returning(query, (config.chain_id, -older_than, limit))
|
|
151
135
|
|
|
152
136
|
if not stuck:
|
|
153
137
|
click.echo("No stuck claims found matching criteria.")
|
brawny/cli/commands/migrate.py
CHANGED
|
@@ -42,6 +42,7 @@ def migrate(status: bool, config_path: str | None) -> None:
|
|
|
42
42
|
applied = migrator.migrate()
|
|
43
43
|
for m in applied:
|
|
44
44
|
click.echo(f" Applied: {m.version} - {m.filename}")
|
|
45
|
+
verify_critical_schema(db)
|
|
45
46
|
click.echo(f"\nSuccessfully applied {len(applied)} migration(s).")
|
|
46
47
|
finally:
|
|
47
48
|
db.close()
|
brawny/cli/commands/run.py
CHANGED
|
@@ -103,9 +103,16 @@ def start(
|
|
|
103
103
|
|
|
104
104
|
# Show signers
|
|
105
105
|
if daemon.keystore:
|
|
106
|
-
|
|
107
|
-
if
|
|
108
|
-
|
|
106
|
+
signers_with_aliases = daemon.keystore.list_keys_with_aliases()
|
|
107
|
+
if signers_with_aliases:
|
|
108
|
+
# Format: "alias (0x123...)" or just "0x123..." if no alias
|
|
109
|
+
formatted = []
|
|
110
|
+
for addr, alias in signers_with_aliases:
|
|
111
|
+
if alias:
|
|
112
|
+
formatted.append(f"{alias} ({addr[:10]}...)")
|
|
113
|
+
else:
|
|
114
|
+
formatted.append(addr[:10] + "...")
|
|
115
|
+
click.echo(f" Signers: {len(signers_with_aliases)} ({', '.join(formatted)})")
|
|
109
116
|
|
|
110
117
|
# Show startup warnings/errors
|
|
111
118
|
for msg in startup_messages:
|
brawny/cli/commands/script.py
CHANGED
|
@@ -66,7 +66,7 @@ def run(
|
|
|
66
66
|
from brawny.alerts.contracts import ContractSystem
|
|
67
67
|
from brawny.config import Config
|
|
68
68
|
from brawny.keystore import create_keystore
|
|
69
|
-
from brawny._rpc import
|
|
69
|
+
from brawny._rpc.clients import BroadcastClient
|
|
70
70
|
from brawny.accounts import _init_accounts
|
|
71
71
|
from brawny.history import _init_history
|
|
72
72
|
from brawny.chain import _init_chain
|
|
@@ -103,11 +103,15 @@ def run(
|
|
|
103
103
|
rpc_endpoints = [local_url]
|
|
104
104
|
click.echo(f"Started Anvil fork at {local_url}")
|
|
105
105
|
|
|
106
|
-
# Create
|
|
107
|
-
|
|
106
|
+
# Create broadcast client
|
|
107
|
+
from brawny._rpc.retry_policy import broadcast_policy
|
|
108
|
+
|
|
109
|
+
rpc = BroadcastClient(
|
|
108
110
|
endpoints=rpc_endpoints,
|
|
109
111
|
timeout_seconds=config.rpc_timeout_seconds,
|
|
110
112
|
max_retries=config.rpc_max_retries,
|
|
113
|
+
retry_backoff_base=config.rpc_retry_backoff_base,
|
|
114
|
+
retry_policy=broadcast_policy(config),
|
|
111
115
|
)
|
|
112
116
|
_set_fallback_rpc(rpc)
|
|
113
117
|
|
|
@@ -134,6 +138,7 @@ def run(
|
|
|
134
138
|
block_number=block_data["number"],
|
|
135
139
|
block_hash=block_data["hash"],
|
|
136
140
|
timestamp=block_data["timestamp"],
|
|
141
|
+
base_fee=block_data.get("baseFeePerGas", 0),
|
|
137
142
|
)
|
|
138
143
|
ctx = JobContext(
|
|
139
144
|
block=block,
|