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/alerts/events.py
CHANGED
|
@@ -17,11 +17,15 @@ from brawny.alerts.errors import (
|
|
|
17
17
|
EventNotFoundError,
|
|
18
18
|
ReceiptRequiredError,
|
|
19
19
|
)
|
|
20
|
+
from brawny.logging import get_logger
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from brawny.jobs.base import TxReceipt
|
|
23
24
|
|
|
24
25
|
|
|
26
|
+
logger = get_logger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
class AttributeDict(dict):
|
|
26
30
|
"""Dictionary that allows attribute-style access to values.
|
|
27
31
|
|
|
@@ -145,7 +149,16 @@ class EventAccessor:
|
|
|
145
149
|
log = LogEntry.from_dict(log_dict)
|
|
146
150
|
|
|
147
151
|
# Filter by contract address
|
|
148
|
-
|
|
152
|
+
try:
|
|
153
|
+
log_address = to_checksum_address(log.address)
|
|
154
|
+
except ValueError:
|
|
155
|
+
logger.debug(
|
|
156
|
+
"events.invalid_log_address",
|
|
157
|
+
address=log.address,
|
|
158
|
+
tx_hash=log.transaction_hash,
|
|
159
|
+
)
|
|
160
|
+
continue
|
|
161
|
+
if log_address != self._address:
|
|
149
162
|
continue
|
|
150
163
|
|
|
151
164
|
# Skip if no topics (anonymous event)
|
|
@@ -168,8 +181,14 @@ class EventAccessor:
|
|
|
168
181
|
if event_name not in result:
|
|
169
182
|
result[event_name] = []
|
|
170
183
|
result[event_name].append(decoded)
|
|
171
|
-
except Exception:
|
|
172
|
-
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.debug(
|
|
186
|
+
"events.decode_log_failed",
|
|
187
|
+
event_name=event_name,
|
|
188
|
+
tx_hash=log.transaction_hash,
|
|
189
|
+
log_index=log.log_index,
|
|
190
|
+
error=str(e)[:200],
|
|
191
|
+
)
|
|
173
192
|
continue
|
|
174
193
|
break
|
|
175
194
|
|
|
@@ -213,7 +232,14 @@ class EventAccessor:
|
|
|
213
232
|
types = [i["type"] for i in non_indexed_inputs]
|
|
214
233
|
try:
|
|
215
234
|
non_indexed_values = list(abi_decode(types, data_bytes))
|
|
216
|
-
except Exception:
|
|
235
|
+
except Exception as e:
|
|
236
|
+
logger.debug(
|
|
237
|
+
"events.decode_non_indexed_failed",
|
|
238
|
+
event_name=event_name,
|
|
239
|
+
tx_hash=log.transaction_hash,
|
|
240
|
+
log_index=log.log_index,
|
|
241
|
+
error=str(e)[:200],
|
|
242
|
+
)
|
|
217
243
|
non_indexed_values = [None] * len(non_indexed_inputs)
|
|
218
244
|
|
|
219
245
|
# Build args dict
|
|
@@ -607,7 +633,14 @@ def decode_logs(
|
|
|
607
633
|
decoded["_address"] = log.get("address", "")
|
|
608
634
|
decoded["_name"] = event_abi["name"]
|
|
609
635
|
decoded_events.append(decoded)
|
|
610
|
-
except Exception:
|
|
636
|
+
except Exception as e:
|
|
637
|
+
logger.debug(
|
|
638
|
+
"events.decode_event_failed",
|
|
639
|
+
event_name=event_abi.get("name"),
|
|
640
|
+
tx_hash=log.get("transactionHash"),
|
|
641
|
+
log_index=log.get("logIndex"),
|
|
642
|
+
error=str(e)[:200],
|
|
643
|
+
)
|
|
611
644
|
continue
|
|
612
645
|
|
|
613
646
|
return EventDict(decoded_events)
|
brawny/alerts/health.py
CHANGED
|
@@ -7,6 +7,8 @@ import threading
|
|
|
7
7
|
from datetime import datetime, timedelta
|
|
8
8
|
from typing import Any, Callable, Literal
|
|
9
9
|
|
|
10
|
+
from cachetools import TTLCache
|
|
11
|
+
|
|
10
12
|
from brawny.logging import get_logger
|
|
11
13
|
|
|
12
14
|
logger = get_logger(__name__)
|
|
@@ -14,9 +16,11 @@ logger = get_logger(__name__)
|
|
|
14
16
|
DEFAULT_COOLDOWN_SECONDS = 1800
|
|
15
17
|
MAX_FIELD_LEN = 200
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
# Multi-threaded access - protected by _lock
|
|
20
|
+
# Medium cardinality keys (fingerprint hashes): maxsize=10K, ttl=1h
|
|
21
|
+
_last_fired: TTLCache[str, datetime] = TTLCache(maxsize=10_000, ttl=3600)
|
|
22
|
+
_first_seen: TTLCache[str, datetime] = TTLCache(maxsize=10_000, ttl=3600)
|
|
23
|
+
_suppressed_count: TTLCache[str, int] = TTLCache(maxsize=10_000, ttl=3600)
|
|
20
24
|
_lock = threading.Lock()
|
|
21
25
|
|
|
22
26
|
|
|
@@ -172,14 +176,16 @@ def health_alert(
|
|
|
172
176
|
|
|
173
177
|
|
|
174
178
|
def cleanup_stale_fingerprints(cooldown_seconds: int = DEFAULT_COOLDOWN_SECONDS) -> int:
|
|
175
|
-
"""Remove fingerprints older than 2x cooldown. Returns count removed.
|
|
176
|
-
|
|
177
|
-
|
|
179
|
+
"""Remove fingerprints older than 2x cooldown. Returns count removed.
|
|
180
|
+
|
|
181
|
+
Note: With TTLCache, stale entries are automatically evicted. This function
|
|
182
|
+
now triggers cache expiration and returns 0 (actual eviction count is not
|
|
183
|
+
tracked by TTLCache). Kept for API compatibility.
|
|
184
|
+
"""
|
|
178
185
|
with _lock:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return removed
|
|
186
|
+
# TTLCache automatically evicts expired entries on access
|
|
187
|
+
# Trigger expiration by calling expire() if available, or just access
|
|
188
|
+
_last_fired.expire()
|
|
189
|
+
_first_seen.expire()
|
|
190
|
+
_suppressed_count.expire()
|
|
191
|
+
return 0 # TTLCache doesn't track eviction count
|