better error handling returns

This commit is contained in:
viraladmin 2026-08-01 09:13:24 -06:00
parent 956f788218
commit 8cbcc97aa8
3 changed files with 20 additions and 4 deletions

View File

@ -184,6 +184,10 @@ Transaction broadcasting accepts:
The API never creates or signs a transaction for the caller. It only forwards The API never creates or signs a transaction for the caller. It only forwards
the complete signed transaction through `Client::submitTransaction()`. the complete signed transaction through `Client::submitTransaction()`.
When the node rejects a transaction, the API returns its bounded, valid UTF-8
verification reason with HTTP `422`. Malformed, binary, and oversized upstream
errors remain generic. This allows wallets to distinguish problems such as an
insufficient balance, an unregistered participant, or a fee below the minimum.
Wallet registration accepts: Wallet registration accepts:

View File

@ -22,7 +22,7 @@ final class ExceptionResponder
SafeLogger::exception($error, RequestContext::requestId()); SafeLogger::exception($error, RequestContext::requestId());
if ($error instanceof UpstreamRejectedException) { if ($error instanceof UpstreamRejectedException) {
JsonResponse::error('The Contractless node rejected the request.', 422); JsonResponse::error($error->getMessage(), 422);
} }
if ($error instanceof TransportException) { if ($error instanceof TransportException) {
header('Retry-After: 5'); header('Retry-After: 5');

View File

@ -567,7 +567,7 @@ final class RpcReplyDecoder
/** @return array{accepted: bool, already_in_mempool: bool, message: string} */ /** @return array{accepted: bool, already_in_mempool: bool, message: string} */
public static function broadcast(string $reply): array public static function broadcast(string $reply): array
{ {
self::notNodeError($reply); self::notNodeError($reply, true);
$message = trim($reply); $message = trim($reply);
if ($message === 'successful_broadcast: true') { if ($message === 'successful_broadcast: true') {
return [ return [
@ -586,11 +586,23 @@ final class RpcReplyDecoder
throw new RuntimeException('The node returned an invalid broadcast response.'); throw new RuntimeException('The node returned an invalid broadcast response.');
} }
public static function notNodeError(string $reply): void public static function notNodeError(string $reply, bool $includeReason = false): void
{ {
if (preg_match('/^error:/i', ltrim($reply)) === 1) { if (preg_match('/^error:/i', ltrim($reply)) === 1) {
$message = 'The Contractless node rejected the request.';
if ($includeReason) {
$reason = trim((string) preg_replace('/^error:\s*/i', '', ltrim($reply), 1));
if (
$reason !== ''
&& strlen($reason) <= 500
&& preg_match('//u', $reason) === 1
&& preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', $reason) !== 1
) {
$message = $reason;
}
}
throw new UpstreamRejectedException( throw new UpstreamRejectedException(
'The Contractless node rejected the request.', $message,
); );
} }
} }