Skip to content
Open
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

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,50 +1,140 @@
package org.tron.core.services.jsonrpc;

import com.fasterxml.jackson.databind.JsonNode;
import com.googlecode.jsonrpc4j.ErrorData;
import com.googlecode.jsonrpc4j.ErrorResolver;
import com.googlecode.jsonrpc4j.JsonRpcError;
import com.googlecode.jsonrpc4j.JsonRpcErrors;
import com.googlecode.jsonrpc4j.ProxyUtil;
import com.googlecode.jsonrpc4j.ReflectionUtil;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.tron.core.exception.TronError;
import org.tron.core.exception.jsonrpc.JsonRpcException;

/**
* {@link ErrorResolver} that uses annotations.
*/
@Slf4j(topic = "API")
public enum JsonRpcErrorResolver implements ErrorResolver {
INSTANCE;

private static final Set<String> SEEN_FAILURES = ConcurrentHashMap.newKeySet();

/**
* {@inheritDoc}
*/
@Override
public JsonError resolveError(
Throwable thrownException, Method method, List<JsonNode> arguments) {
JsonRpcError resolver = getResolverForException(thrownException, method);
if (notFoundResolver(resolver)) {
return null;
Error fatal = findFatalCause(thrownException);
if (fatal != null) {
throw fatal;
}

JsonRpcError resolver = method == null
? null : getResolverForException(thrownException, method);
if (resolver == null) {
logUnhandledException(method, thrownException);
return new JsonError(JsonError.INTERNAL_ERROR.code, "Internal error", null);
}

String message = hasErrorMessage(resolver) ? resolver.message() : thrownException.getMessage();
if (StringUtils.isBlank(message)) {
message = defaultMessageFor(resolver.code());
}

// data priority: exception > annotation > default ErrorData
// data priority: exception > annotation
Object data = null;
if (thrownException instanceof JsonRpcException) {
JsonRpcException jsonRpcException = (JsonRpcException) thrownException;
data = jsonRpcException.getData();
}

if (data == null) {
data = hasErrorData(resolver)
? resolver.data()
: new ErrorData(resolver.exception().getName(), message);
if (data == null && hasErrorData(resolver)) {
data = resolver.data();
}

return new JsonError(resolver.code(), message, data);
}

static Error findFatalCause(Throwable throwable) {
// Avoid allocations when handling memory exhaustion, without truncating deep cause chains.
// Check both fast-pointer steps so cycle detection cannot skip a fatal cause inside a cycle.
Throwable slow = throwable;
Throwable fast = throwable;
while (fast != null) {
if (isFatal(fast)) {
return (Error) fast;
}
fast = fast.getCause();
if (fast == null) {
return null;
}
if (isFatal(fast)) {
return (Error) fast;
}
fast = fast.getCause();
slow = slow.getCause();
if (fast == slow) {
return null;
}
}
return null;
}

private static boolean isFatal(Throwable cause) {
return cause instanceof VirtualMachineError
|| cause instanceof ThreadDeath
|| cause instanceof LinkageError
|| cause instanceof TronError;
}

private static void logUnhandledException(Method method, Throwable thrownException) {
String methodName = rpcMethodName(method);
String exceptionName = thrownException.getClass().getName();
String key = methodName + '\0' + exceptionName;
if (SEEN_FAILURES.add(key)) {
// The first occurrence retains the Throwable for diagnosis. Repeated failures omit both
// the stack and exception message so a request loop cannot flood the WARN log.
logger.warn("Unhandled exception in JSON-RPC method {}", methodName, thrownException);
} else {
logger.debug("Repeated unhandled exception in JSON-RPC method {} ({})",
methodName, exceptionName);
}
}

static void clearSeenFailuresForTest() {
SEEN_FAILURES.clear();
}

private static String rpcMethodName(Method method) {
if (method == null) {
return "unknown";
}
try {
return ProxyUtil.getMethodName(method);
} catch (RuntimeException e) {
return method.getName();
}
}

private static String defaultMessageFor(int code) {
switch (code) {
case -32600:
return "Invalid Request";
case -32601:
return "Method not found";
case -32602:
return "Invalid params";
default:
return "Internal error";
}
}

private JsonRpcError getResolverForException(Throwable thrownException, Method method) {
JsonRpcErrors errors = ReflectionUtil.getAnnotation(method, JsonRpcErrors.class);
if (hasAnnotations(errors)) {
Expand All @@ -57,10 +147,6 @@ private JsonRpcError getResolverForException(Throwable thrownException, Method m
return null;
}

private boolean notFoundResolver(JsonRpcError resolver) {
return resolver == null;
}

private boolean hasErrorMessage(JsonRpcError em) {
// noinspection ConstantConditions
return em.message() != null && !em.message().trim().isEmpty();
Expand All @@ -78,4 +164,4 @@ private boolean hasAnnotations(JsonRpcErrors errors) {
private boolean isExceptionInstanceOfError(Throwable target, JsonRpcError em) {
return em.exception().isInstance(target);
}
}
}
Loading
Loading