File size: 8,813 Bytes
da8c484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/env python3
"""
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:  # noqa: BLE001
        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"]

    # Exactly the calls a send makes, with the same cache keys.
    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),
        # Not cacheable in the same sense — a blockhash has a real
        # expiry — but hot_state serves it behind a short TTL, so the
        # caller should not be paying the round trip either.
        ("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 the WARM column, because that is the number a trade pays.
        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

    # A live send also pays two quotes and two swap-instruction builds.
    # Those are NOT cacheable — they are the market — so /bench is the
    # right measure for them and its figure is used here rather than
    # re-measuring it badly.
    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()

    # The freeze stage specifically, because it was 51% of the pipeline and
    # is the thing the operator asked to see proven.
    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()))