Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions blockrun_llm/solana_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ class SolanaLLMClient:
# to refresh an expired blockhash, and every fresh signature is validated
# against the original payment terms before use.
MEDIA_POLL_MAX_RESIGNS = 2
# Proactively re-sign the settlement authorization every N seconds during the
# poll loop so its recent-blockhash never ages out. The gateway settles only
# when upstream flips to "completed", and slow/flaky-status models (1080p
# Seedance) can bounce completed<->in_progress for minutes — long enough that
# a signature made earlier goes stale (blockhash lifetime ~60-90s) before the
# settling poll lands. 25s keeps every signature comfortably fresh.
MEDIA_RESIGN_FRESH_SECONDS = 25.0

# Media generation defaults (mirror the Base MusicClient/SpeechClient).
MUSIC_DEFAULT_MODEL = "minimax/music-2.5+"
Expand Down Expand Up @@ -1567,10 +1574,34 @@ def _request_image_with_payment(
deadline = _time.monotonic() + budget
last_status = submit_data.get("status", "queued")
resigns_left = max_resigns
last_resign_at = _time.monotonic()

while _time.monotonic() < deadline:
_time.sleep(interval)

# Keep the settlement blockhash fresh (poll-based media path only,
# gated on max_resigns). Re-sign the ORIGINAL challenge — same amount/pay_to,
# only a freshly-fetched blockhash — so that whenever upstream flips to
# "completed" the signature is <MEDIA_RESIGN_FRESH_SECONDS old and
# settlement can't hit a stale-blockhash transaction_simulation_failed.
# Only the completed poll actually settles; in-progress polls ignore
# the header, so re-signing here never double-charges.
if (
max_resigns > 0
and _time.monotonic() - last_resign_at >= self.MEDIA_RESIGN_FRESH_SECONDS
):
try:
fresh_payload = self._sign_payment(payment_required)
poll_headers["PAYMENT-SIGNATURE"] = encode_payment_signature_header(
fresh_payload
)
last_resign_at = _time.monotonic()
except Exception:
# Best-effort only: a failed proactive re-sign (RPC hiccup,
# SolanaRpcException, etc.) must never abort the poll loop —
# we simply keep the prior signature (pre-fix behaviour).
pass

poll_resp = self._client.get(poll_url, headers=poll_headers, timeout=eff_timeout)
try:
poll_data = poll_resp.json()
Expand Down Expand Up @@ -4094,10 +4125,30 @@ async def _request_image_with_payment(
deadline = _time.monotonic() + budget
last_status = submit_data.get("status", "queued")
resigns_left = max_resigns
last_resign_at = _time.monotonic()

while _time.monotonic() < deadline:
await asyncio.sleep(interval)

# Keep the settlement blockhash fresh (poll-based media path only,
# gated on max_resigns) — mirror of the sync helper. Re-sign the
# ORIGINAL challenge (same amount/
# pay_to, fresh blockhash) every MEDIA_RESIGN_FRESH_SECONDS so a slow /
# flaky-status model (1080p Seedance) can't age the signature out
# before the settling "completed" poll lands. Only completed settles.
if (
max_resigns > 0
and _time.monotonic() - last_resign_at >= SolanaLLMClient.MEDIA_RESIGN_FRESH_SECONDS
):
try:
fresh_payload = await self._sign_payment(payment_required)
poll_headers["PAYMENT-SIGNATURE"] = encode_payment_signature_header(
fresh_payload
)
last_resign_at = _time.monotonic()
except Exception:
pass

poll_resp = await self._client.get(poll_url, headers=poll_headers, timeout=eff_timeout)
try:
poll_data = poll_resp.json()
Expand Down
151 changes: 151 additions & 0 deletions tests/unit/test_solana_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,154 @@ async def test_async_resign_reprice_propagates(self, monkeypatch: pytest.MonkeyP
)
finally:
await client._client.aclose()


# ---------------------------------------------------------------------------
# Proactive per-poll re-sign — keeps the settlement blockhash fresh even when
# NO poll ever 402s (the 1080p Seedance case: upstream status flaps
# completed<->in_progress for minutes and would otherwise settle a stale
# signature). Distinct from the on-402 re-sign guard tested above.
# ---------------------------------------------------------------------------


def _fresh_sig_handler(n_in_progress: int, poll_sigs: List[str]):
"""Video job that NEVER 402s on a poll: n_in_progress in-progress polls,
then completed. Records the PAYMENT-SIGNATURE seen on every signed poll so a
test can assert the proactive re-sign refreshed it each time."""
completed = {
"status": "completed",
"created": 1,
"model": "xai/grok-imagine-video",
"data": [{"url": "https://cdn/v.mp4"}],
}
state = {"i": 0}

def handler(request: httpx.Request) -> httpx.Response:
if request.method == "POST":
if "PAYMENT-SIGNATURE" not in request.headers:
return httpx.Response(
402,
headers={"content-type": "application/json", "payment-required": "stub"},
json={"error": "Payment Required"},
)
return httpx.Response(
202,
json={
"id": "JOB",
"poll_url": "/api/v1/videos/generations/JOB",
"status": "queued",
},
)
poll_sigs.append(request.headers.get("PAYMENT-SIGNATURE"))
state["i"] += 1
if state["i"] <= n_in_progress:
return httpx.Response(
202, json={"status": "in_progress"}, headers={"content-type": "application/json"}
)
return httpx.Response(200, json=completed, headers={"content-type": "application/json"})

return handler


class TestProactiveResign:
def test_sync_refreshes_signature_every_poll(self, monkeypatch: pytest.MonkeyPatch) -> None:
import itertools

counter = itertools.count()
monkeypatch.setattr(
"blockrun_llm.solana_client.encode_payment_signature_header",
lambda payload: f"sig-{next(counter)}",
)
# Fire the proactive re-sign on every poll (0s freshness window).
monkeypatch.setattr(SolanaLLMClient, "MEDIA_RESIGN_FRESH_SECONDS", 0.0)

poll_sigs: List[str] = []
client = _make_client(_fresh_sig_handler(3, poll_sigs))
data = client._request_image_with_payment(
"/v1/videos/generations", dict(_VIDEO_BODY), **_HELPER_KW
)
assert data["data"][0]["url"] == "https://cdn/v.mp4"
# 3 in-progress + 1 completed, and every signed poll carried a DISTINCT
# (freshly re-signed) signature — the completed poll never reused the
# stale submit-time one.
assert len(poll_sigs) == 4
assert len(set(poll_sigs)) == 4, poll_sigs

@pytest.mark.asyncio
async def test_async_refreshes_signature_every_poll(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
import itertools

counter = itertools.count()
monkeypatch.setattr(
"blockrun_llm.solana_client.encode_payment_signature_header",
lambda payload: f"sig-{next(counter)}",
)
monkeypatch.setattr(SolanaLLMClient, "MEDIA_RESIGN_FRESH_SECONDS", 0.0)

poll_sigs: List[str] = []
client = _make_async_client(_fresh_sig_handler(3, poll_sigs))
try:
data = await client._request_image_with_payment(
"/v1/videos/generations", dict(_VIDEO_BODY), **_HELPER_KW
)
assert data["data"][0]["url"] == "https://cdn/v.mp4"
assert len(poll_sigs) == 4
assert len(set(poll_sigs)) == 4, poll_sigs
finally:
await client._client.aclose()

# max_resigns == 0 (the image path) must NOT proactively re-sign, even with a
# 0s freshness window: every poll reuses the single submit-time signature so
# the image flow is provably untouched by the video-only fix.
_IMAGE_KW: Dict[str, Any] = {
"poll_budget_seconds": 5.0,
"poll_interval_seconds": 0.001,
"max_resigns": 0,
"label": "Image generation",
}

def test_sync_image_path_never_resigns(self, monkeypatch: pytest.MonkeyPatch) -> None:
import itertools

counter = itertools.count()
monkeypatch.setattr(
"blockrun_llm.solana_client.encode_payment_signature_header",
lambda payload: f"sig-{next(counter)}",
)
monkeypatch.setattr(SolanaLLMClient, "MEDIA_RESIGN_FRESH_SECONDS", 0.0)

poll_sigs: List[str] = []
client = _make_client(_fresh_sig_handler(3, poll_sigs))
data = client._request_image_with_payment(
"/v1/images/generations", dict(_VIDEO_BODY), **self._IMAGE_KW
)
assert data["data"][0]["url"] == "https://cdn/v.mp4"
# 3 in-progress + 1 completed, every poll carrying the SAME submit-time
# signature — the proactive re-sign never fired for max_resigns == 0.
assert len(poll_sigs) == 4
assert len(set(poll_sigs)) == 1, poll_sigs

@pytest.mark.asyncio
async def test_async_image_path_never_resigns(self, monkeypatch: pytest.MonkeyPatch) -> None:
import itertools

counter = itertools.count()
monkeypatch.setattr(
"blockrun_llm.solana_client.encode_payment_signature_header",
lambda payload: f"sig-{next(counter)}",
)
monkeypatch.setattr(SolanaLLMClient, "MEDIA_RESIGN_FRESH_SECONDS", 0.0)

poll_sigs: List[str] = []
client = _make_async_client(_fresh_sig_handler(3, poll_sigs))
try:
data = await client._request_image_with_payment(
"/v1/images/generations", dict(_VIDEO_BODY), **self._IMAGE_KW
)
assert data["data"][0]["url"] == "https://cdn/v.mp4"
assert len(poll_sigs) == 4
assert len(set(poll_sigs)) == 1, poll_sigs
finally:
await client._client.aclose()
Loading