-
Notifications
You must be signed in to change notification settings - Fork 428
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
Implement error forwarding #641
Comments
I agree this proposal, and I think this should design a protocol between Currently, if the ink! return error, and return the error code, the value recept in match sandbox_result {
// No traps were generated. Proceed normally.
Ok(_) => {
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() })
} And under this protocol, any other contract framework (include ink!, Ask!, Solang and so on) should obey this convention |
@cmichi Does this means that If an ink! message returns a Result::Error, it emits ExtrinsicFailed event which contains errors defined by a contract? |
In a meeting @cmichi and me concluded to put forward with the following design for ink!:
This design allows for an efficient design that ink! and An example follows: /// It is still possible to create normal non-Result messages or constructors.
#[ink(message)]
fn foo(&self) -> i32 { ... }
/// Long-form notation, mandates revert upon returning `Result::Err`.
#[ink(message, revert_on_err: true)]
fn try_foo_1(&self) -> Result<i32, MyError> { ... }
/// Short-form notation, mandates revert upon returning `Result::Err`.
///
/// Absolutely the same as `try_foo_1`.
#[ink(message, revert_on_err)]
fn try_foo_2(&self) -> Result<i32, MyError> { ... }
/// Long-form notation, does NOT revert upon returning `Result::Err`.
#[ink(message, revert_on_err: false)]
fn foo_2(&self) -> Result<i32, MyError> { ... }
/// This is invalid ink! code and will yield a compile error via Rust compiler
/// since the message is not returning a Result type.
#[ink(message, revert_on_err: true)]
fn foo_3(&self) -> i32 { ... }
/// This is invalid ink! code and will yield an error via cargo-contract when being compiled
/// since the mandatory revert_on_err for Result type returns is missing.
#[ink(message)]
fn foo_4(&self) -> Result<i32, MyError> { ... } |
I don't think this solve this problem... If ink! chooses revert_on_err when Result return Err, why not chooses using In fact, all thing is:
|
I agree with @atenjin . The behavior of revert_on_err is similar to So the most simple way is to use I think we still can define errors in the contract and return them via assert. For example |
This is a very good point! So a contract should ideally behave differently in this regard depending on if it was called by another contract (to allow for error handling) or an origin user (to properly forward the error). |
Yes, if you plan to add support of it into |
No need to implement on contracts pallet level. We should be able to fully control this in ink! itself. |
How do you want to distinguish the child's contract call vs initial contract call? |
ink! smart contracts are compiled in different levels whether they are at root or not. So if you are compiling your ink! smart contract normally you will get the behavior that acts as if it can only be called as root. If you compile it via |
Okay, if it is already possible(to understand where is root call where is not), it is cool. Seems I have ideas on how it can be done in the current scope of codegen. During the generation of code for cross calling, we can ignore Another actual question. Do we need to allow to handle errors in business logic, or better to do always 'assert' or 'panic'? Because it really adds a lot of additional code to handle/pass/work with Result and Error. |
Contract pallet will just do what ink! commands it to.
Depends on your use case. For Rust recoverable errors are returned via |
Yes, but I meant that we have already deployed another contract(with feature
You are right. I'm thinking about the standardization of tokens and other useful stuff. And the question do we need to return errors in case of Erc* or better to assert=) |
The ink! version you are using does not yet properly support revert on error. So it will just do not revert on
This is not decided by ink! but purely decided by how you want people to operate on your API. So if you want them to handle errors go for |
I know that. I meant let's imagine, that we already implemented that=) And the question is what will do
Okey, we will define that during PSP for standard tokens=) |
It will revert. |
Hello again=) We thought about how this feature can be implemented. It is clear how it must work in the case of single contract execution. But we faced several issues which don't allow (we think so) to implement Firstly, we have one restriction which must be covered during cross-contract calls. If the child contract decided to revert the transaction, the contract pallet SHOULD revert it. It is needed because during contract execution we can flush into storage, but later we can decide to revert the execution. We shouldn't force the user to work in the paradigm "check everything before modification of the storage". !ink and contract pallet must be agnostic in this case. But at the moment it's impossible with the current design of the cross calls in the contract pallet. In the current implementation, the parent contract must decide how to process the
Points 1 and 2 mean that no reason to return a result from the function, which is marked with BTW, the second point is related to error propagation from child contract to parent and to end-user. We need to decide what we want to return in |
Changes applied by the child contract are reverted on failure (setting the |
Just because it isn't stored in the block, doesn't mean it can't be done. The return buffer can be re-computed, maybe through an RPC which reruns the wasm with the right input state? |
I can't imagine that=D But the developer still can return a result with
I see. If you are using that errors to store information about the execution of the call in the blockchain, then it makes sense.
Yes, because the developer can return something in the
To compute the output you need to have a state of the whole blockchain(or only for contracts that can be affected during contract call executed) at block X before execution of this transaction in this block. It is not a trivial task. + in contracts you can call some methods of pallets via
Returning only of the status of the execution similar to method So in this case we will have the same behavior as for off-chain RPC call. And if someone will ask the blockchain about the status of the transaction, he will get only the result from the block without buffer and additional stuff. |
That makes sense.
I didn't understand this at all. Sorry. Maybe this helps: No matter whether a
It is not for emitting data to off chain observers. This is what events are for. Yes it can be annoying that this is different from ethereum where any output of a contract is automatically put into the permanent record.
This would only work on an archive node because it would need historical state. I don't think we should depend on that for essential functionality. If depending on archive nodes is OK you could just emit an event for any returned data which can be queried from archive nodes at historical blocks (I think). |
I understand that. I'll try to describe what I meant=) And here we can add some additional features like: "Hey, blockchain. I sent you a transaction and I'm waiting for confirmation. Could you include the output of this transaction also in the response, please? I know that you can do that because you will verify the block which contains this transaction and you will compute the output during verification. So, you can add this output to the response, but not include it into the block." It will be only behavior for the case if someone sent the transaction to the node, this node will return the full response back. But if someone asks for information about the result of execution of the transaction separately, it will get only information available in extrinsic(So behavior only for the initiator of the transaction). But it is one idea. Another is more general, to add a configuration to the pallet to store the output of execution in some storage(off-chain data), and support additional RPC requests to get this data. So, if some node owner decided to provide more info(not only extrinsic information stored on-chain but also additional information), he can enable it in the config of runtime. In this case, common block producers/verifiers don't need to enable it and provide this info. But some API nodes can enable it and provide more information from the box. It is untrusted information, anyone can modify it, but at the moment it is only information for debugging.
BTW, does an archive node also store state for each pallet? If yes, it is cool, but seems it will take a lot of space=D |
In theory this would be possible. In practice an extrinsic is not allowed to return arbitrary data and therefore this isn't possible on polkadot. One reason I can imagine why this is is because this would not work with light clients which do not execute imported blocks. Polkadot tries really hard to not break light clients.
This a a lot of complexity for something for which the solution already exist: Just emit an event if you want to observe something happened on-chain. That works with light clients, too. If there is a off-chain solution I am strictly against adding complexity to the runtime. Because the runtime needs to be safe and audited and adding stuff there is highly expensive for that reason. Complexity should be in the off-chain tools. I know that sucks of you are the one developing those tools but is for the best. |
We are trying to resolve the question, how better to propagate the error to the end-user. If each developer(or ink!) uses events to do that, it will the same like store the error message on-chain. But this solution will be more complicated than variant to store output buffer inside of error. I prefer to avoid storing any data related to error propagation, on-chain(Like events or buffer in the body of the error). In the case of storing this data optionally off-chain allows us to use the same API for RPC calls and for "Send transaction" actions(As you mentioned before, we can return This variant allows us to not spam the network with events about errors. And still, handle it simply in UI/DApps. |
Events are generally not considered as storing data on-chain because they don't became part of the state. For the rest of your posting I can't quite follow. Are you suggesting to return the output buffer in case of an RPC call? |
But does the full node still store them somewhere?
Yes, |
No. It gets deleted from state after the block was executed it was part of. Only archive nodes hold events forever.
The |
During cross contract call |
This is just wrong. Even in case of |
I described in this comment the idea similar to the behavior of events - [1] if the node is an archive node, then it can store the output of the execution. And this comment described some simplification - [2] if the node is an archive node and stores the output of the execution, it can return this output as a result of Implementation of [1] will be enough to show the error on the UI side. If the The difference between [1] and events, is that UI will know how it can request a detailed description of the error(output). But maybe in UI, we can show all events of the execution and the user will be able to see the event related to the error. |
Do I derive correctly that this discussion should be held in the issue tracker for the |
Okey, so it must be implemented on ink! side. @Robbepop After cross contract call ink! receives Based on the comment from the @athei ink! should handle |
As can be seen here ink! always provides the result buffer to SEAL properly: |
Yes, it is right. The problem is on the side of the root contract. |
Ah I think I finally get what you mean. I will inspect it. |
I think we are mixing two separate concerns in this discussion:
I will comment on 2: It won't work because the transaction won't return anything. Even if if the dispatchable would return anything (when your client executes the transaction before submission to verify it) you can't be sure that you get the same result when a validator receives and executes the transaction later. With the exception of emitting stuff to storage (events are a lighter variant of this) of course which we have discussed is way to expensive. At that point this isn't any better than use the result of the dry-run RPC to present rich error messages to the user. In fact it is exactly what you are asking for. It happily returns you a debug buffer and the output buffer. The thing is that UIs today are not doing this. But it is possible and how we intent to provide error reporting. |
Yea, we have severals topics here=D Sorry, let me clarify. In this comment I agreed with you, that events can cover everything, that we need(and my idea to store separately output is useless). We only need a proper UI that will show all events generated by the transaction. And if the transaction is marked as So let me summarize. We have two ways to propagate the error to the user.
The question here we prefer 2.a or 2.b? And who will add events to UI?=) |
You are completely right about 1. This is already implemented. The UIs just don't make use of it right now. Events are something that are in the domain of the individual Dapps that build on ink!. A generic UI would just show all events generated by a contract. They can be decoded automatically by using the ABI file. Please keep in mind that the target audience of those generic UIs are contract developers and not users. For developers those the decoded events make sense.
Users will interact with contracts through custom Dapps that can make sense of those events and display them in a friendly manner. |
Yes, I'm only thinking about UI for developers=) I meant that we need to introduce the list of decoded events into #975 is merged so |
Please open an issue where we can discuss the specifics of this feature request. Thank you for your help today with the |
If an ink! message returns a
Result::Err
, this error should be forwarded and result in e.g. the Canvas UI eventually displayingExtrinsic Failed
(instead ofExtrinsic Success
, which it currently does).The text was updated successfully, but these errors were encountered: