Prevent redirects on payment renewals and refreshes; stop funding on 3xx
Maintainer thường phản hồi trong vòng 2 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 45/100
Hướng nghiên cứu
Start with src/livepeer_gateway/http.py and src/livepeer_gateway/remote_signer.py to trace renewal and refresh requests through the HTTP helpers and funding loop. Add regression tests for redirect handling, terminal funding behavior, successful requests, and unchanged redirect behavior for other callers; update mock expectations as described. Run the SDK test suite and confirm 3xx responses do not reach redirect targets or trigger further ticket signing.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Payment renewals currently follow redirects through _post_empty, and payment-parameter refreshes follow redirects through post_json. This proposal moves the fix to the SDK that owns those requests, following investigation in https://github.com/NE-Build-Track/livepeer-builder-core/issues/72
Problem
At 2017a80c740af5fcb0fef4ee84659af9b07fb90c, LivePaymentSession.send_payment() posts Livepeer-Payment and Livepeer-Segment through a non-stream HTTP path that follows redirects. Those custom headers can travel to the redirect target, and a downstream 2xx is treated as a successful renewal. The funding loop then requests another ticket on the next interval.
_refresh_payment_params() also follows redirects. A redirect target can supply the payment_params that the session uses for subsequent signing requests.
Both paths should refuse redirects, and a 3xx should stop funding instead of minting additional tickets. This can be implemented directly in the SDK's existing helpers and loop.
Proposal
This proposal addresses builder-core issue #72 in livepeer-python-gateway, against commit 2017a80.
The production patch adds 19 lines and removes 2 across two files, plus regression tests still to be added. It changes the existing HTTP helpers and payment loop directly.
Behavior
- Renewal and refresh requests pass
allow_redirects=False. - Any 3xx response on those requests raises
LivepeerHTTPErrorimmediately after the headers arrive, before reading the response body. - The existing
run_payments()loop stops on 3xx, preventing additional tickets from being minted against a redirecting endpoint. - Existing handling of 404, other permanent 4xx, 408, 429, server errors, transport failures and cancellation is preserved.
- Other callers retain their current redirect behavior. Existing stream redirect protection remains in effect.
Checking the status before reading the body prevents a truncated or stalled redirect body from turning a terminal redirect into a retryable transport error. Existing exception cleanup closes the response and session.
Changes
In remote_signer.py, disable redirects for send_payment() and _refresh_payment_params(). Extend the existing funding loop's terminal-status range from 400 <= status < 500 to 300 <= status < 500, retaining the exceptions for 408 and 429.
In http.py, add an optional allow_redirects=True argument and forward it through the existing paths:
_post_empty → _request_body → _open_request
post_json → request_json → _request_body → _open_request
In _open_request(), apply allow_redirects=False when requested and reject 3xx before reading the body. Only set the option when false so existing stream-specific redirect restrictions are preserved.
Complete production patch
The complete proposed production patch is embedded below.
--- a/src/livepeer_gateway/http.py
+++ b/src/livepeer_gateway/http.py
@@ -146,6 +146,7 @@
payload: RunnerPayload | _StreamState = None,
headers: dict[str, str] | None = None,
timeout: float = 5.0,
+ allow_redirects: bool = True,
) -> tuple[bytes, str]:
"""
Make an HTTP request and return the raw response body.
@@ -161,6 +162,7 @@
session, resp = await _open_request(
url, method=method, payload=payload, headers=headers,
timeout=aiohttp.ClientTimeout(total=timeout), handshake_timeout=timeout,
+ allow_redirects=allow_redirects,
)
async with session:
try:
@@ -205,6 +207,7 @@
payload: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
timeout: float = 5.0,
+ allow_redirects: bool = True,
) -> Any:
"""
Make an async JSON HTTP request and parse the JSON response.
@@ -219,6 +222,7 @@
payload=payload,
headers=headers,
timeout=timeout,
+ allow_redirects=allow_redirects,
)
try:
return json.loads(raw)
@@ -264,15 +268,20 @@
headers: dict[str, str] | None,
timeout: aiohttp.ClientTimeout,
handshake_timeout: float,
+ allow_redirects: bool = True,
) -> tuple[aiohttp.ClientSession, aiohttp.ClientResponse]:
method, headers, body = _request_parts(url, method=method, payload=payload, headers=headers)
session, options = _request_session(payload, timeout, handshake_timeout)
+ if not allow_redirects:
+ options["allow_redirects"] = False
if payload is None:
# aiohttp otherwise adds application/octet-stream even for an empty POST.
options["skip_auto_headers"] = {"Content-Type"}
resp = None
try:
resp = await session.request(method, url, data=body, headers=headers, **options)
+ if not allow_redirects and 300 <= resp.status < 400:
+ _raise_http_json_error(resp.status, url)
if _request_failed(resp.status, payload):
# Read before cancelling the writer: aiohttp 3.13 closes the socket
# on cancellation, which can discard a fragmented payment challenge.
@@ -296,6 +305,7 @@
*,
headers: dict[str, str] | None = None,
timeout: float = 5.0,
+ allow_redirects: bool = True,
) -> dict[str, Any]:
"""
POST JSON to `url` and parse a JSON object response.
@@ -305,6 +315,7 @@
payload=payload,
headers=headers,
timeout=timeout,
+ allow_redirects=allow_redirects,
)
return _ensure_json_object(data, url=url)
@@ -326,6 +337,7 @@
*,
headers: dict[str, str] | None = None,
timeout: float = 5.0,
+ allow_redirects: bool = True,
) -> None:
"""POST an empty body to ``url`` and discard the response."""
await _request_body(
@@ -333,6 +345,7 @@
method="POST",
headers=headers,
timeout=timeout,
+ allow_redirects=allow_redirects,
)
--- a/src/livepeer_gateway/remote_signer.py
+++ b/src/livepeer_gateway/remote_signer.py
@@ -170,7 +170,9 @@
"Livepeer-Payment": payment.payment,
"Livepeer-Segment": payment.seg_creds,
}
- await _post_empty(self._challenge.payment_url, headers=headers, timeout=5.0)
+ await _post_empty(
+ self._challenge.payment_url, headers=headers, timeout=5.0, allow_redirects=False,
+ )
async def run_payments(self) -> bool:
"""Keep a metered session funded until cancelled or the session ends.
@@ -186,10 +188,11 @@
except SkipPaymentCycle as e:
_LOG.debug("Payment loop skipped cycle: %s", e)
except LivepeerHTTPError as e:
+ # A redirect or permanent 4xx stops funding.
# A 4xx will not change on a retry (404 gone, 409 fixed price,
# 403 mismatch), so stop rather than mint tickets nobody will
# honour. 408 and 429 are the two that do ask to be retried.
- if 400 <= e.status_code < 500 and e.status_code not in (408, 429):
+ if 300 <= e.status_code < 500 and e.status_code not in (408, 429):
_LOG.info("Payment loop stopping (HTTP %d): %s", e.status_code, e)
return e.status_code == 404
_LOG.warning("Payment failed; retrying next cycle: %s", e)
@@ -249,6 +252,7 @@
"sender": signer.address,
"manifest_id": self._challenge.manifest_id,
},
+ allow_redirects=False,
)
payment_params = data.get("payment_params")
if not isinstance(payment_params, str) or not payment_params:
Validation and remaining tests
The proposed code was validated in memory with 22 loopback checks, including renewal and refresh redirects, a truncated 307 response, successful refreshes, retry behavior and the default redirect behavior of other callers. The patch passes git apply --check against 2017a80.
Before merging, add permanent regression tests and run the full SDK suite. Update existing mock expectations to include allow_redirects=False on payment requests. Coverage should verify:
- 301, 302, 303, 307 and 308 renewals reach no redirect target and stop further signing.
- Redirected refreshes reach no target and never replace the original payment parameters.
- A truncated or stalled 3xx body cannot trigger another payment cycle.
- Successful renewals and refreshes still work.
- Existing terminal-status, retry and cancellation behavior is preserved.
- Unrelated HTTP requests retain their existing redirect behavior.
No repository source files were changed during validation. This is a proposed production patch, not a complete, tested upstream PR.
Effect on builder-core
Once builder-core consumes an SDK release or commit containing this fix, it can retain its original SDK funding path instead of a new payment module and replacement funding loop. Refresh hardening and terminal redirect handling would already be implemented in the SDK.
Consuming this upstream fix requires updating builder-core's SDK pin. Changing upstream source cannot alter the behavior of the currently pinned commit.
- Ngôn ngữ chính
- Python
- Star
- 1
- Fork
- 10
- Merge trung bình
- 1 ngày 9 giờ
- Pull request đã merge (30 ngày)
- 7
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của livepeer/livepeer-python-gateway
-
call_runner rejects a top-level JSON array, so a runner cannot pass one throughCó thể đã có người làm @rickstaa đã nhận 74 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
livepeer/livepeer-python-gateway#64 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Align pricing terminology: pixels_per_unit -> pricing_unit_size (track go-livepeer #3942)Có thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mởImprovement
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 68/100
livepeer/livepeer-python-gateway#27 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
livepeer/livepeer-python-gateway#21 ·
Maintainer thường phản hồi trong vòng 2 ngày
-
trickle_publisher: `_resolve_next_seq` fallback drops first video segment + logs noisy warningCó thể làm lại được Pull request cho issue này đã bị đóng mà không được merge. Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 70/100
livepeer/livepeer-python-gateway#12 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 62/100
livepeer/livepeer-python-gateway#88 ·
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của livepeer/livepeer-python-gateway
Issue tương tự
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
NousResearch/hermes-agent#147104 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
premises-missing
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 88/100
WingedGuardian/GENesis-AGI#3801 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[oblt-aw][autodoc] Document new required `shared-org-key` input for `obs-aw-dependency-review`Có thể đã có người làm @github-actions đã nhận hôm nay. Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Maintainer thường phản hồi trong vòng 1 ngày
-
bot-triaged module: inductor oncall: pt2 topic: fuzzer triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 4 ngày