| |
| """ |
| scripts/test_sendpath.py — prove the send path is inside a slot. Or that it isn't. |
| |
| venv/bin/python scripts/test_sendpath.py |
| |
| ──────────────────────────────────────────────────────────────────────────── |
| WHY THIS EXISTS, AND HOW IT DIFFERS FROM /bench |
| ──────────────────────────────────────────────────────────────────────────── |
| Operator: "how you improve the speed 400 ms — make test show us." |
| |
| Fair, and the honest answer was that /pipeline could not show it. A |
| pipeline trace is only written when a real signal reaches the executor, |
| and nothing has reached it since the fix — so /pipeline still displays |
| three attempts from four days ago at 1.19s, measured on code that no |
| longer exists. A screen that cannot be refreshed cannot be evidence. |
| |
| /bench does not answer it either. /bench times each stage in ISOLATION on |
| a fresh connection: it tells you the network is fine, which was never the |
| question. The question is whether the CACHES work, and an isolated timing |
| deliberately avoids them. |
| |
| So this walks the real thing. Every function called below is the exact |
| function solana_executor calls on a live send — `_cached_mint_safety`, |
| `_cached_priority_fee`, `_cached_reserve`, `_cached_alt_accounts`, |
| `_fresh_blockhash` — through the same hot_state cache, with the same keys. |
| |
| Each is timed TWICE: |
| |
| COLD the first call, cache empty, the network round trip |
| WARM the second call, which is what a real trade actually pays |
| |
| The gap between those two columns IS the fix. `freeze` was 758 ms and |
| 51% of the send path; if the warm column does not show it near zero, the |
| warm cache is not working and this prints that in as many words. |
| |
| ──────────────────────────────────────────────────────────────────────────── |
| WHAT IT NEVER DOES |
| ──────────────────────────────────────────────────────────────────────────── |
| No key is loaded. No transaction is built, signed, simulated or sent. No |
| bundle reaches Jito. It reads public state over RPC and times the reads. |
| Safe to run while the bot is live — it shares the same warm cache, so it |
| leaves the caches hotter than it found them. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import asyncio |
| import os |
| import sys |
| import time |
| from pathlib import Path |
| from typing import Any, Callable, Optional |
|
|
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) |
|
|
| from modules.env_file import load_env_file |
|
|
| load_env_file() |
|
|
| _G, _Y, _R, _B, _0 = "\033[32m", "\033[33m", "\033[31m", "\033[1m", "\033[0m" |
| _SLOT_MS = 400.0 |
|
|
|
|
| async def _time(fn: Callable[[], Any]) -> tuple[Optional[float], str]: |
| """One call, in milliseconds. A failure returns None, never a zero. |
| |
| A stage that errors and reports 0 ms is the single most misleading |
| number this script could print — it would look like the fastest stage |
| on the page. |
| """ |
| t0 = time.perf_counter() |
| try: |
| result = fn() |
| if asyncio.iscoroutine(result): |
| await result |
| except Exception as exc: |
| return None, str(exc)[:70] |
| return (time.perf_counter() - t0) * 1000.0, "" |
|
|
|
|
| async def main() -> int: |
| ap = argparse.ArgumentParser(description=__doc__) |
| ap.add_argument("--warm-runs", type=int, default=3, |
| help="warm samples per stage (default 3, the median is used)") |
| args = ap.parse_args() |
|
|
| from constants import SOLANA_TOKENS |
| from modules.solana_executor import ( |
| _cached_alt_accounts, _cached_mint_safety, _cached_priority_fee, |
| _cached_reserve, _fresh_blockhash, get_shared_client, |
| ) |
|
|
| rpc = (os.getenv("SOLANA_RPC_PRIMARY") or os.getenv("SOLANA_RPC_URL") or "").strip() |
| if not rpc: |
| print("\n no RPC configured (SOLANA_RPC_PRIMARY) — nothing to measure\n") |
| return 1 |
|
|
| client = get_shared_client() |
| sol, usdc = SOLANA_TOKENS["SOL"], SOLANA_TOKENS["USDC"] |
|
|
| |
| stages: list[tuple[str, Callable[[], Any], bool]] = [ |
| ("freeze SOL", lambda: _cached_mint_safety(client, rpc, "SOL", sol["mint"]), True), |
| ("freeze USDC", lambda: _cached_mint_safety(client, rpc, "USDC", usdc["mint"]), True), |
| ("priority fee", lambda: _cached_priority_fee(client, rpc), True), |
| ("reserve", lambda: _cached_reserve(client, "USDC", usdc["mint"], rpc), True), |
| ("alt tables", lambda: _cached_alt_accounts(client, rpc, []), True), |
| |
| |
| |
| ("blockhash", lambda: _fresh_blockhash(client, rpc), False), |
| ] |
|
|
| print() |
| print(f"{_B} ⏱ send-path test — the real cached calls, nothing signed{_0}") |
| print(" COLD = cache empty · WARM = what a live trade actually pays") |
| print(" " + "─" * 64) |
| print(f" {'stage':<16}{'COLD':>10}{'WARM':>10} saved") |
| print(" " + "─" * 64) |
|
|
| warm_total = 0.0 |
| cold_total = 0.0 |
| broken: list[str] = [] |
|
|
| for name, fn, cacheable in stages: |
| cold, err = await _time(fn) |
| if cold is None: |
| broken.append(f"{name}: {err}") |
| print(f" {name:<16}{_R}{'failed':>10}{_0} {err[:30]}") |
| continue |
|
|
| warms: list[float] = [] |
| for _ in range(max(1, args.warm_runs)): |
| w, werr = await _time(fn) |
| if w is not None: |
| warms.append(w) |
| if not warms: |
| broken.append(f"{name}: warm call failed") |
| continue |
| warm = sorted(warms)[len(warms) // 2] |
|
|
| cold_total += cold |
| warm_total += warm |
| saved = cold - warm |
| |
| colour = _G if warm < 20 else (_Y if warm < 100 else _R) |
| flag = "" if cacheable else " (short TTL by design)" |
| print(f" {name:<16}{cold:>9.0f}ms{colour}{warm:>9.0f}ms{_0}" |
| f" {saved:>6.0f}ms{flag}") |
|
|
| print(" " + "─" * 64) |
| print(f" {'TOTAL':<16}{cold_total:>9.0f}ms{warm_total:>9.0f}ms" |
| f" {cold_total - warm_total:>6.0f}ms") |
| print() |
|
|
| if broken: |
| print(f" {_Y}Some stages could not be measured:{_0}") |
| for b in broken: |
| print(f" {b}") |
| print(" The totals above exclude them, so they understate the real") |
| print(" pipeline. Fix these before reading this as a pass.") |
| print() |
| return 1 |
|
|
| |
| |
| |
| |
| quotes_ms = float(os.getenv("SENDPATH_QUOTE_BUDGET_MS", "120")) |
| projected = warm_total + quotes_ms |
|
|
| print(f" cached state, warm {warm_total:>7.0f} ms") |
| print(f" + quotes and swap-ix {quotes_ms:>7.0f} ms " |
| f"(uncacheable — /bench measures these)") |
| print(f" {_B}= signal to signed {projected:>7.0f} ms{_0}") |
| print(f" a Solana slot is {_SLOT_MS:>7.0f} ms") |
| print() |
|
|
| if projected <= _SLOT_MS: |
| print(f" {_G}✓ {projected:.0f} ms — inside one slot.{_0} The warm cache is") |
| print(" working. Latency is not what stops a trade; /observe for") |
| print(" whether the round trip is positive at all.") |
| else: |
| print(f" {_R}✗ {projected:.0f} ms — longer than a slot.{_0}") |
| print(" The warm column above names which stage is still paying a") |
| print(" round trip. A cached stage over 100 ms warm means the cache") |
| print(" is missing — check hot_state's TTL for it and that") |
| print(" warm_mint_safety ran at startup.") |
| print() |
|
|
| |
| |
| print(f" {_B}The one that mattered:{_0} `freeze` was 758 ms p50 and 51% of the") |
| print(" send path. Its WARM column above is what a trade pays now.") |
| print() |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| raise SystemExit(asyncio.run(main())) |
|
|