brawny 0.1.13__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 +106 -0
- brawny/_context.py +232 -0
- brawny/_rpc/__init__.py +38 -0
- brawny/_rpc/broadcast.py +172 -0
- brawny/_rpc/clients.py +98 -0
- brawny/_rpc/context.py +49 -0
- brawny/_rpc/errors.py +252 -0
- brawny/_rpc/gas.py +158 -0
- brawny/_rpc/manager.py +982 -0
- brawny/_rpc/selector.py +156 -0
- brawny/accounts.py +534 -0
- brawny/alerts/__init__.py +132 -0
- brawny/alerts/abi_resolver.py +530 -0
- brawny/alerts/base.py +152 -0
- brawny/alerts/context.py +271 -0
- brawny/alerts/contracts.py +635 -0
- brawny/alerts/encoded_call.py +201 -0
- brawny/alerts/errors.py +267 -0
- brawny/alerts/events.py +680 -0
- brawny/alerts/function_caller.py +364 -0
- brawny/alerts/health.py +185 -0
- brawny/alerts/routing.py +118 -0
- brawny/alerts/send.py +364 -0
- brawny/api.py +660 -0
- brawny/chain.py +93 -0
- brawny/cli/__init__.py +16 -0
- brawny/cli/app.py +17 -0
- brawny/cli/bootstrap.py +37 -0
- brawny/cli/commands/__init__.py +41 -0
- brawny/cli/commands/abi.py +93 -0
- brawny/cli/commands/accounts.py +632 -0
- brawny/cli/commands/console.py +495 -0
- brawny/cli/commands/contract.py +139 -0
- brawny/cli/commands/health.py +112 -0
- brawny/cli/commands/init_project.py +86 -0
- brawny/cli/commands/intents.py +130 -0
- brawny/cli/commands/job_dev.py +254 -0
- brawny/cli/commands/jobs.py +308 -0
- brawny/cli/commands/logs.py +87 -0
- brawny/cli/commands/maintenance.py +182 -0
- brawny/cli/commands/migrate.py +51 -0
- brawny/cli/commands/networks.py +253 -0
- brawny/cli/commands/run.py +249 -0
- brawny/cli/commands/script.py +209 -0
- brawny/cli/commands/signer.py +248 -0
- brawny/cli/helpers.py +265 -0
- brawny/cli_templates.py +1445 -0
- brawny/config/__init__.py +74 -0
- brawny/config/models.py +404 -0
- brawny/config/parser.py +633 -0
- brawny/config/routing.py +55 -0
- brawny/config/validation.py +246 -0
- brawny/daemon/__init__.py +14 -0
- brawny/daemon/context.py +69 -0
- brawny/daemon/core.py +702 -0
- brawny/daemon/loops.py +327 -0
- brawny/db/__init__.py +78 -0
- brawny/db/base.py +986 -0
- brawny/db/base_new.py +165 -0
- brawny/db/circuit_breaker.py +97 -0
- brawny/db/global_cache.py +298 -0
- brawny/db/mappers.py +182 -0
- brawny/db/migrate.py +349 -0
- brawny/db/migrations/001_init.sql +186 -0
- brawny/db/migrations/002_add_included_block.sql +7 -0
- brawny/db/migrations/003_add_broadcast_at.sql +10 -0
- brawny/db/migrations/004_broadcast_binding.sql +20 -0
- brawny/db/migrations/005_add_retry_after.sql +9 -0
- brawny/db/migrations/006_add_retry_count_column.sql +11 -0
- brawny/db/migrations/007_add_gap_tracking.sql +18 -0
- brawny/db/migrations/008_add_transactions.sql +72 -0
- brawny/db/migrations/009_add_intent_metadata.sql +5 -0
- brawny/db/migrations/010_add_nonce_gap_index.sql +9 -0
- brawny/db/migrations/011_add_job_logs.sql +24 -0
- brawny/db/migrations/012_add_claimed_by.sql +5 -0
- brawny/db/ops/__init__.py +29 -0
- brawny/db/ops/attempts.py +108 -0
- brawny/db/ops/blocks.py +83 -0
- brawny/db/ops/cache.py +93 -0
- brawny/db/ops/intents.py +296 -0
- brawny/db/ops/jobs.py +110 -0
- brawny/db/ops/logs.py +97 -0
- brawny/db/ops/nonces.py +322 -0
- brawny/db/postgres.py +2535 -0
- brawny/db/postgres_new.py +196 -0
- brawny/db/queries.py +584 -0
- brawny/db/sqlite.py +2733 -0
- brawny/db/sqlite_new.py +191 -0
- brawny/history.py +126 -0
- brawny/interfaces.py +136 -0
- brawny/invariants.py +155 -0
- brawny/jobs/__init__.py +26 -0
- brawny/jobs/base.py +287 -0
- brawny/jobs/discovery.py +233 -0
- brawny/jobs/job_validation.py +111 -0
- brawny/jobs/kv.py +125 -0
- brawny/jobs/registry.py +283 -0
- brawny/keystore.py +484 -0
- brawny/lifecycle.py +551 -0
- brawny/logging.py +290 -0
- brawny/metrics.py +594 -0
- brawny/model/__init__.py +53 -0
- brawny/model/contexts.py +319 -0
- brawny/model/enums.py +70 -0
- brawny/model/errors.py +194 -0
- brawny/model/events.py +93 -0
- brawny/model/startup.py +20 -0
- brawny/model/types.py +483 -0
- brawny/networks/__init__.py +96 -0
- brawny/networks/config.py +269 -0
- brawny/networks/manager.py +423 -0
- brawny/obs/__init__.py +67 -0
- brawny/obs/emit.py +158 -0
- brawny/obs/health.py +175 -0
- brawny/obs/heartbeat.py +133 -0
- brawny/reconciliation.py +108 -0
- brawny/scheduler/__init__.py +19 -0
- brawny/scheduler/poller.py +472 -0
- brawny/scheduler/reorg.py +632 -0
- brawny/scheduler/runner.py +708 -0
- brawny/scheduler/shutdown.py +371 -0
- brawny/script_tx.py +297 -0
- brawny/scripting.py +251 -0
- brawny/startup.py +76 -0
- brawny/telegram.py +393 -0
- brawny/testing.py +108 -0
- brawny/tx/__init__.py +41 -0
- brawny/tx/executor.py +1071 -0
- brawny/tx/fees.py +50 -0
- brawny/tx/intent.py +423 -0
- brawny/tx/monitor.py +628 -0
- brawny/tx/nonce.py +498 -0
- brawny/tx/replacement.py +456 -0
- brawny/tx/utils.py +26 -0
- brawny/utils.py +205 -0
- brawny/validation.py +69 -0
- brawny-0.1.13.dist-info/METADATA +156 -0
- brawny-0.1.13.dist-info/RECORD +141 -0
- brawny-0.1.13.dist-info/WHEEL +5 -0
- brawny-0.1.13.dist-info/entry_points.txt +2 -0
- brawny-0.1.13.dist-info/top_level.txt +1 -0
brawny/chain.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Chain information singleton.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from brawny import chain
|
|
5
|
+
|
|
6
|
+
chain.height # Current block number
|
|
7
|
+
chain[-1] # Most recent block
|
|
8
|
+
chain[0] # Genesis block
|
|
9
|
+
chain.id # Chain ID
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any, TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from brawny._rpc.manager import RPCManager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_chain: "Chain | None" = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Chain:
|
|
24
|
+
"""Brownie-compatible chain interface.
|
|
25
|
+
|
|
26
|
+
Provides access to block information and chain state.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, rpc: "RPCManager", chain_id: int) -> None:
|
|
30
|
+
self._rpc = rpc
|
|
31
|
+
self._chain_id = chain_id
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def height(self) -> int:
|
|
35
|
+
"""Current block number (same as brownie's chain.height)."""
|
|
36
|
+
return self._rpc.get_block_number()
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def id(self) -> int:
|
|
40
|
+
"""Chain ID."""
|
|
41
|
+
return self._chain_id
|
|
42
|
+
|
|
43
|
+
def __getitem__(self, index: int) -> dict[str, Any]:
|
|
44
|
+
"""Get block by number. Supports negative indexing like brownie.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> chain[-1] # most recent block
|
|
48
|
+
>>> chain[0] # genesis block
|
|
49
|
+
"""
|
|
50
|
+
if index < 0:
|
|
51
|
+
index = self.height + index + 1
|
|
52
|
+
return self._rpc.get_block(index)
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
return f"<Chain id={self._chain_id} height={self.height}>"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _init_chain(rpc: "RPCManager", chain_id: int) -> None:
|
|
59
|
+
"""Initialize global chain singleton."""
|
|
60
|
+
global _chain
|
|
61
|
+
_chain = Chain(rpc, chain_id)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_chain() -> Chain:
|
|
65
|
+
"""Get chain singleton."""
|
|
66
|
+
if _chain is None:
|
|
67
|
+
raise RuntimeError(
|
|
68
|
+
"Chain not initialized. Run within script context."
|
|
69
|
+
)
|
|
70
|
+
return _chain
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Proxy for import-time access
|
|
74
|
+
class _ChainProxy:
|
|
75
|
+
"""Proxy that delegates to chain singleton."""
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def height(self) -> int:
|
|
79
|
+
return _get_chain().height
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def id(self) -> int:
|
|
83
|
+
return _get_chain().id
|
|
84
|
+
|
|
85
|
+
def __getitem__(self, index: int) -> dict[str, Any]:
|
|
86
|
+
return _get_chain()[index]
|
|
87
|
+
|
|
88
|
+
def __repr__(self) -> str:
|
|
89
|
+
return repr(_get_chain())
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Global proxy instance
|
|
93
|
+
chain = _ChainProxy()
|
brawny/cli/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""CLI entrypoint for brawny."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from brawny.cli.app import main
|
|
6
|
+
from brawny.cli.commands import register_all
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
register_all(main)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["main"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
main()
|
brawny/cli/app.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""CLI entry group for brawny."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from brawny.cli.bootstrap import configure_bytecode_cache, load_env
|
|
8
|
+
|
|
9
|
+
load_env()
|
|
10
|
+
configure_bytecode_cache()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
@click.version_option(version="0.1.0", prog_name="brawny")
|
|
15
|
+
def main() -> None:
|
|
16
|
+
"""brawny: Block-driven Ethereum job/transaction execution framework."""
|
|
17
|
+
pass
|
brawny/cli/bootstrap.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CLI bootstrap utilities."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_env() -> None:
|
|
11
|
+
"""Load environment variables from .env in the current directory."""
|
|
12
|
+
dotenv_path = Path.cwd() / ".env"
|
|
13
|
+
if dotenv_path.exists():
|
|
14
|
+
load_dotenv(dotenv_path)
|
|
15
|
+
else:
|
|
16
|
+
load_dotenv()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def configure_bytecode_cache() -> None:
|
|
20
|
+
"""
|
|
21
|
+
Prevent __pycache__ from being created in the user's project by redirecting
|
|
22
|
+
pyc writes to a centralized cache directory.
|
|
23
|
+
|
|
24
|
+
Must run before importing/loading user job modules.
|
|
25
|
+
"""
|
|
26
|
+
# Always disable bytecode writes to avoid __pycache__ in project jobs.
|
|
27
|
+
os.environ["PYTHONDONTWRITEBYTECODE"] = "1"
|
|
28
|
+
sys.dont_write_bytecode = True
|
|
29
|
+
|
|
30
|
+
# If user already set a prefix, respect it.
|
|
31
|
+
if os.environ.get("PYTHONPYCACHEPREFIX"):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
cache_dir = Path.home() / ".cache" / "brawny" / "pycache"
|
|
35
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
os.environ["PYTHONPYCACHEPREFIX"] = str(cache_dir)
|
|
37
|
+
sys.pycache_prefix = str(cache_dir)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""CLI command registration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from brawny.cli.commands.abi import register as register_abi
|
|
6
|
+
from brawny.cli.commands.contract import register as register_contract
|
|
7
|
+
from brawny.cli.commands.health import register as register_health
|
|
8
|
+
from brawny.cli.commands.init_project import register as register_init
|
|
9
|
+
from brawny.cli.commands.intents import register as register_intents
|
|
10
|
+
from brawny.cli.commands.jobs import register as register_jobs
|
|
11
|
+
from brawny.cli.commands.logs import register as register_logs
|
|
12
|
+
from brawny.cli.commands.maintenance import register as register_maintenance
|
|
13
|
+
from brawny.cli.commands.migrate import register as register_migrate
|
|
14
|
+
from brawny.cli.commands.networks import register as register_networks
|
|
15
|
+
from brawny.cli.commands.run import register as register_start
|
|
16
|
+
from brawny.cli.commands.script import register as register_run
|
|
17
|
+
from brawny.cli.commands.accounts import register as register_accounts
|
|
18
|
+
from brawny.cli.commands.signer import register as register_signer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def register_all(main) -> None:
|
|
22
|
+
register_migrate(main)
|
|
23
|
+
register_start(main) # brawny start (daemon)
|
|
24
|
+
register_run(main) # brawny run (scripts, brownie-compatible)
|
|
25
|
+
register_init(main)
|
|
26
|
+
register_jobs(main) # includes jobs run
|
|
27
|
+
register_intents(main)
|
|
28
|
+
register_logs(main) # brawny logs list/cleanup
|
|
29
|
+
register_maintenance(main)
|
|
30
|
+
register_health(main)
|
|
31
|
+
register_abi(main)
|
|
32
|
+
register_contract(main)
|
|
33
|
+
register_accounts(main)
|
|
34
|
+
register_networks(main)
|
|
35
|
+
register_signer(main) # brawny signer force-reset, status
|
|
36
|
+
# Console has optional dependency (prompt_toolkit) - import lazily
|
|
37
|
+
try:
|
|
38
|
+
from brawny.cli.commands.console import register as register_console
|
|
39
|
+
register_console(main)
|
|
40
|
+
except ImportError:
|
|
41
|
+
pass # prompt_toolkit not installed
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""ABI cache commands.
|
|
2
|
+
|
|
3
|
+
Uses global ABI cache at ~/.brawny/abi_cache.db.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from brawny.db.global_cache import GlobalABICache
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def abi() -> None:
|
|
18
|
+
"""Manage ABI cache (stored in ~/.brawny/abi_cache.db)."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@abi.command("show")
|
|
23
|
+
@click.argument("address")
|
|
24
|
+
@click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
|
|
25
|
+
def abi_show(address: str, chain_id: int) -> None:
|
|
26
|
+
"""Show cached ABI for address."""
|
|
27
|
+
cache = GlobalABICache()
|
|
28
|
+
cached = cache.get_cached_abi(chain_id, address)
|
|
29
|
+
if not cached:
|
|
30
|
+
click.echo(f"No cached ABI for {address} on chain {chain_id}.")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
click.echo(f"\nCached ABI for {address}")
|
|
34
|
+
click.echo("-" * 60)
|
|
35
|
+
click.echo(f" Chain ID: {cached.chain_id}")
|
|
36
|
+
click.echo(f" Source: {cached.source}")
|
|
37
|
+
click.echo(f" Resolved: {cached.resolved_at}")
|
|
38
|
+
click.echo("\n ABI Preview:")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
abi_data = json.loads(cached.abi_json)
|
|
42
|
+
for item in abi_data[:10]:
|
|
43
|
+
if item.get("type") == "function":
|
|
44
|
+
name = item.get("name", "?")
|
|
45
|
+
inputs = ", ".join(i.get("type", "?") for i in item.get("inputs", []))
|
|
46
|
+
click.echo(f" - {name}({inputs})")
|
|
47
|
+
if len(abi_data) > 10:
|
|
48
|
+
click.echo(f" ... and {len(abi_data) - 10} more")
|
|
49
|
+
except json.JSONDecodeError:
|
|
50
|
+
click.echo(" (Invalid JSON)")
|
|
51
|
+
click.echo()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@abi.command("set")
|
|
55
|
+
@click.argument("address")
|
|
56
|
+
@click.option("--file", "abi_file", required=True, help="ABI JSON file")
|
|
57
|
+
@click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
|
|
58
|
+
def abi_set(address: str, abi_file: str, chain_id: int) -> None:
|
|
59
|
+
"""Set cached ABI from file."""
|
|
60
|
+
from pathlib import Path
|
|
61
|
+
|
|
62
|
+
path = Path(abi_file)
|
|
63
|
+
if not path.exists():
|
|
64
|
+
click.echo(f"File not found: {abi_file}", err=True)
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
|
|
67
|
+
abi_json = path.read_text()
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
json.loads(abi_json)
|
|
71
|
+
except json.JSONDecodeError as e:
|
|
72
|
+
click.echo(f"Invalid JSON: {e}", err=True)
|
|
73
|
+
sys.exit(1)
|
|
74
|
+
|
|
75
|
+
cache = GlobalABICache()
|
|
76
|
+
cache.set_cached_abi(chain_id, address, abi_json, "manual")
|
|
77
|
+
click.echo(f"ABI cached for {address} on chain {chain_id}.")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@abi.command("clear")
|
|
81
|
+
@click.argument("address")
|
|
82
|
+
@click.option("--chain-id", type=int, default=1, help="Chain ID (default: 1)")
|
|
83
|
+
def abi_clear(address: str, chain_id: int) -> None:
|
|
84
|
+
"""Clear cached ABI for address."""
|
|
85
|
+
cache = GlobalABICache()
|
|
86
|
+
if cache.clear_cached_abi(chain_id, address):
|
|
87
|
+
click.echo(f"ABI cache cleared for {address}.")
|
|
88
|
+
else:
|
|
89
|
+
click.echo(f"No cached ABI found for {address}.")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def register(main) -> None:
|
|
93
|
+
main.add_command(abi)
|