-
Notifications
You must be signed in to change notification settings - Fork 629
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
nearcore doesn't return structured errors for most use cases of API #2976
Comments
Another example is that Timeout errors are of unclear structure: |
@evgenykuzyakov Vlad Frolov is currently busy with P0 Rosetta. Would you be able to take a look at it? |
Consistent error-handling is important for devx:
|
Assigning 1 day since most of the work is going to be done in #2536 |
It is going to be the follow-up issue after #3517 is finally done. The plan is to expose the structure errors side-by-side with the currently awkward text errors. Once #3517 is done, it will be just a matter of the design of the structured errors in the RPC response. There has been some discussion happening in #3204, where I initially proposed to expose the very detailed view-client error as is, but on the second thought, we should better minimize the scope of the possible errors exposed through JSON RPC as otherwise, we won't be able to reason about the possible return values just as we cannot reason about them right now with the text messages (see the very first paragraph of this issue description). $ http post https://rpc.testnet.near.org jsonrpc=2.0 id=dontcare method=block 'params:=[0]'
{
"error": {
"code": -32000, // don't touch it
"data": "DB Not Found Error: BLOCK HEIGHT: 0 \n Cause: Unknown", // don't touch it
"message": "Server error", // don't touch it
"cause": {
"kind": "QUERYING_ERROR",
"cause": {
"kind": "NOT_FOUND",
"details": {
"kind": "BLOCK_BY_HEIGHT_QUERY_ERROR",
"data": {
"block_height": 0
}
}
}
}
},
"id": "dontcare",
"jsonrpc": "2.0"
} Having the view-client error structure exposed to the public leads us into an unfortunate situation of not being able to guarantee that our API is stable as any change in the underlying structure will lead us into breaking changes. This means that we want to have RPC-specific enums representing possible RPC errors (there is Just to include the relevant parts of the conversation here: @vgrichina suggested (#3204 (comment)):
Well, I think we need to draw a line between what we want to have flat and what nested. I think it is not helpful to introduce a custom error code schema when we can just use JSON for that. However, if users don't care about whether it was a host error -> guest panic, and only care about it being a function call error with the panic message, that should be enough, I guess. It is yet to find the exact level of detail we want, but here is my suggestion: {
"error": {
"kind": "ACTION_ERROR",
"details": {
"action_index": 0,
},
"cause": {
"kind": "FUNCTION_CALL_ERROR",
"details": {
"panic_msg": "Attempt to call transfer on tokens belonging to another account."
}
}
}
} Various ConcernsThe errors structure should be expressed with the format structure (not a custom encoding format)I don't see any point in introducing a custom error format (e.g. The errors should not be tightly coupled with the implementation of nearcoreIt is very hard for the client to reason about the nearcore internal structure. The public API errors should reflect a reasonable level of detail while being generic enough. The enumerations should be taggedIt is cumbersome to work with the following structure: {
"ACTION_ERROR": {
"details": {
"action_index": 0,
},
"cause": {
"FUNCTION_CALL_ERROR": {
"details": {
"panic_msg": "Attempt to call transfer on tokens belonging to another account."
}
}
}
}
} This structure is hard to define and also it does not forbid to express something we clearly don't want to express (several types of errors mixed on the same level): {
"ACTION_ERROR": {
"details": {
"action_index": 0,
},
"cause": {
"QQ_ERROR": {},
"FUNCTION_CALL_ERROR": {
"details": {
"panic_msg": "Attempt to call transfer on tokens belonging to another account."
}
}
}
},
"SERVER_ERROR": {}
} |
@frol no idea what do you mean by introducing custom error schema, may you elaborate Can you also give some examples of real-world situations when developing frontend (e.g. explorer) where nested structure would be necessary and/or helpful? I've made examples of how it is actively harmful here: #3204 (comment) I hope this time we don't ignore frontend preferences on error format (for easier DevX/UX) and follow YAGNI as well. As you noticed – we'll have to support all these details as stable API but still need to have flexibility internally. Simpler format likely will be easier to maintain as an API. |
@vgrichina Let's consider a View Function Call method. The errors that can happen there end-to-end (not all of them are relevant to the RPC implementation) ordered based on the request processing order:
Transaction execution with a FunctionCall as one of the action will need to have basically the same error, but it should also include the Action index in the error structure to be helpful. |
We have finished the internal refactorings necessary to finally expose the structured errors in a systematic way. I suggest we take the client implementation to design the errors. As I mentioned above, there are tons of different levels where things may go wrong. Let's take
When a network connectivity error is faced, it makes sense to retry the request, while validation errors won't get auto-resolved, and handler errors require some decision to be made. I want to focus on validation errors and handler errors since network connectivity errors are not reported via JSON RPC, and they are mostly out of the discussion here. Currently, we report all the errors the same way through obscure "id": "dontcare",
"jsonrpc": "2.0",
"error": {
"message": "Server error",
"data": "Method not found"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"message": "Server error",
"data": "Block not found: Block #1 is unknown"
}
} NOTE: We are going to keep the old structure no matter what. If we mix validation and handler errors into the same structure, we will get: {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "METHOD_NOT_FOUND",
"error_details": {
"unknown_method_name": "xxx"
},
"message": "Server error",
"data": "Method not found"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "UNKNOWN_BLOCK",
"error_details": {
"block_height": 1
},
"message": "Server error",
"data": "Block not found: Block #1 is unknown"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "CONTRACT_EXECUTION_ERROR",
"error_details": {
"vm_error": "VM error message goes here",
"block_height": 1,
"block_hash": "AMABLOCKHASHHAHAHAHAHAHAHAMABLOCKHASHHAHAHAHAHAHAHHAHAHAHA",
},
"message": "Server error",
"data": "Function call returned an error: VM error message goes here"
}
} Alternatively, we may split validation errors and handler errors by introducing 1 layer of nesting: {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "VALIDATION_ERROR",
"error_details": {
"error_kind": "METHOD_NOT_FOUND",
"error_details": {
"unknown_method_name": "xxx"
},
},
"message": "Server error",
"data": "Method not found"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "HANDLER_ERROR",
"error_details": {
"error_kind": "UNKNOWN_BLOCK",
"error_details": {
"block_height": 1
},
},
"message": "Server error",
"data": "Block not found: Block #1 is unknown"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "HANDLER_ERROR",
"error_details": {
"error_kind": "CONTRACT_EXECUTION_ERROR",
"error_details": {
"vm_error": "VM error message goes here",
"block_height": 1,
"block_hash": "AMABLOCKHASHHAHAHAHAHAHAHAMABLOCKHASHHAHAHAHAHAHAHHAHAHAHA",
}
},
"message": "Server error",
"data": "Function call returned an error: VM error message goes here"
}
} Notice the first level of error can only be
@volovyk-s @vgrichina If we mix validation and handler errors into the same namespace, I believe we mess with the client side handling. I have heard your concerns about nesting structures before. I still strongly believe that it is better to have clear namespaces that are easy to handle than a flat structure that is as useful as a string error message due to mix of various reasons as to why something went wrong. What do you think about this 1 level nesting? |
Are we adding new fields to the existing JSON RPC endpoints, or we will introduce new endpoints? I'm wondering if we really need to have old fields If the depth of this nested structure is not going to be changed, then I don't understand why do we need to have it. Why can not we just add one additional field, so it would look like this: {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_type": "HANDLER_ERROR" // we can think of a better field name
"error_kind": "CONTRACT_EXECUTION_ERROR",
"error_details": {
"vm_error": "VM error message goes here",
"block_height": 1,
"block_hash": "AMABLOCKHASHHAHAHAHAHAHAHAMABLOCKHASHHAHAHAHAHAHAHHAHAHAHA",
},
"message": "Server error",
"data": "Function call returned an error: VM error message goes here"
}
} I can not come up with an example where 2 levels nested structure will be better. Please, elaborate. 2 level nested structure is not that bad, but in reality, people will always do something like |
@volovyk-s Nice! I have not thought about flattening the nesting and keeping both "type" and "kind". Let's brainstorm on the naming a bit:
|
@frol Agree, let's stay away from |
There was another idea, if we want to avoid 2 levels. Having I propose to mix them. As a developer I'm interested in error and details. So:
Looks more easier to me to handle. At the same time
What do you think? |
@khorolets do yo want to move {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "CONTRACT_EXECUTION_ERROR",
"error_details": {
"error_group": "HANDLER_ERROR"
"vm_error": "VM error message goes here",
"block_height": 1,
"block_hash": "AMABLOCKHASHHAHAHAHAHAHAHAMABLOCKHASHHAHAHAHAHAHAHHAHAHAHA",
},
"message": "Server error",
"data": "Function call returned an error: VM error message goes here"
}
} |
@khorolets how is that different from the first variant I proposed above? {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"error_kind": "UNKNOWN_BLOCK",
"error_details": {
"block_height": 1
},
"message": "Server error",
"data": "Block not found: Block #1 is unknown"
}
} |
@frol no difference, yep. @volovyk-s I suggest to avoid a group at all and to mix them together. To leave only one kind of errors and not split them. Or to use 2-levels as the most representative way. |
See my other comment in the thread for some context. I want to have a summary. So far we have 4 variants:
Pros and cons1. Simple and flatPros: Cons: 2. NestedPros: Cons: NOTE: All the pros might be worked around by the users by just using 3. Flattened nested variantPros: Cons: NOTE: Explicitness might be worked around by the users by just using 4. Flat with custom-encoded
|
@frol thank you for such a great summary! The third option is not that confusing, actually. It could be also improved with better wording if we have an option to change it. After 5 minutes of thinking I have
It's still not perfect, but we could think more. Option 2 looks better if we will ever need exhaustive error handling. If we can create meaningful examples where we need nested error types (general one and specific one, and also the cause itself), I'd go with option 2. |
Frol, this is the best GitHub comment ever. |
I am also between (2) and (3). If we go with (3), we need to brainstorm the naming. I came up with the following (I feel neutral about some of them, but I still want to list as many as I can)
I am not a huge fan of I find P.S. I checked Ethereum JSON RPC, and it was quite a challenge to find any documentation, and I only found something useful here. They went with numerical error codes, where they encoded special meaning into various ranges of the numbers. I find this approach the least developers-friendly. |
I think that to match up with the JSONRPC spec, we should change From a NodeJS developer perspective for de-serializing these errors, we have If we have decided that this problem is as simple as "add a field to the error that allows us to classify each error as one of two classes of errors -- either However, if we do see value in having truly nested errors, I would really want to see each nested layer be a complete error, with its own I think if we want actual nested errors, they should be able to be a chain n levels deep of causes where each one is its own error entirely, with its own e.g.: {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"name": "VALIDATION_ERROR",
"message": "Invalid parameter provided",
"data": {
"parameter_name": "method_name"
},
"cause": {
"name": "METHOD_NOT_FOUND",
"message": "Method not found",
"data": {
"unknown_method_name": "xxx"
},
"cause": {...}
}
}
} If I re-constructed this using a chain of
It's true that I would need to construct the chain of errors from the inside-out, and finding a specific type of error would require doing some work (find any If there is only an 'error family' to be added to each error, then I like the idea of it just being a property directly on the error; trying to model it as 'kinda sort of' a nested error feels dangerous, I would expect it to inevitably clash with any API we created for 'real' nested errors/cause chains when/if we build that. Note that if we don't see value in fully nested errors and we decide to go with # 3 for now, we could still add fully nested errors later by adding a |
I would also vote for №1 ('error_family' + |
For backward compatibility reasons, we don’t want to touch the existing I like the proposal of having Examples:
{
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"name": "VALIDATION_ERROR",
"cause": {
"name": "METHOD_NOT_FOUND",
"info": {
"unknown_method_name": "xxx"
},
},
"message": "Server error",
"data": "Method not found"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"name": "HANDLER_ERROR",
"cause": {
"name": "UNKNOWN_BLOCK",
"info": {
"block_height": 1
},
},
"message": "Server error",
"data": "Block not found: Block #1 is unknown"
}
} {
"id": "dontcare",
"jsonrpc": "2.0",
"error": {
"name": "HANDLER_ERROR",
"cause": {
"name": "CONTRACT_EXECUTION_ERROR",
"info": {
"vm_error": "VM error message goes here",
"block_height": 1,
"block_hash": "AMABLOCKHASHHAHAHAHAHAHAHAMABLOCKHASHHAHAHAHAHAHAHHAHAHAHA",
}
},
"message": "Server error",
"data": "Function call returned an error: VM error message goes here"
}
} My personal scoreboard is: TOP-1: VError-like nested errors ( |
@frol's
+1 P.S. I'm fine with top-2 as well |
I set the final comment period until 2021-06-20 (this Sunday). The current decision is to go with VError style and naming (see the comment above) |
@frol did you consult with any users of API besides wallet team? Looks like the proposed grouping is extremely implementation centric. E.g. grouping by the module where error happened doesn’t seem as useful as understanding whether given error is permanent or temporary (i.e. client should retry the same query). Maybe I’m missing some context though. |
@vgrichina I invited to discuss this problem in public channels and then invited individual people to chip it.
I would treat "validation error" as permanent errors, and "handler error" will require making some judgment on the application layer, as API cannot tell if an application needs to retry fetching the block which does not exist now. If it is an error during the request processing (in my examples that is "handler error", but I am happy to hear more ideas here as well), you get a limited number of possible errors, such as "unknown block", "contract execution error". Can you provide an example of how to make them less implementation-centric than that? |
sorry, looks I misunderstood the context examples in #2976 (comment) make sense to me |
@frol can we consider this discussion as closed in favor of |
The final decision is to go with VError style and naming (see the comment above) Thanks everyone for your review and suggestions! @MaximusHaximus Thanks for suggesting VError! The next step is to expose the internal structured errors in this VError format. @khorolets is going to keep it rolling. |
@volovyk-s @vgrichina @mikedotexe @amgando FYI, NEAR documentation covers all possible JSON RPC errors under each individual method, e.g. https://docs.near.org/docs/api/rpc/access-keys#what-could-go-wrong Kudos to @khorolets for implementing the structured errors and documenting those! |
Structured errors work (#1839) is not complete and it's hard to know what to expect.
Example of error from nearcore:
Note that even actual error message
Other Error: block DtdfWgnt3QDEjoeARUK25vg8qh31kc3Lrg6qREsoJsST is ahead of head block 34eT8dRWfDPhZBiRGYvkHDj65ThaXCBTrnM4LQjn2Zab
doesn't go into proper place inmessage
.This effectively makes current error message strings part of API, which is very suboptimal.
Here's how we have to handle errors because of this:
near-api-js
near-wallet
Note that we also handle errors explicitly only in few cases (degrading user experience) because we want to avoid depending on error messages in this way. This problem also degrades quality of every app built on NEAR, as everybody has same problem.
The text was updated successfully, but these errors were encountered: