-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Update Handler Interface & Improve Error Usage #5072
Conversation
Blocked by #5006. We should wait till that's finalized and merged as both modify BaseApp. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice start, and this does bring up some issues on the interfaces. Especially with GasHandling. After some reflection on the code base, I start to believe that GasUsed/GasWanted don't belong in Result after all. This would allow a proper *Result, err
to be X, nil
or nil, X
as long as we pass the gas info some other way.
My reasoning is the following:
Result
type is focused on defining the information aHandler
returns. Not on the needs of baseapp.- The actual result values for GasWanted comes from the anteHandler and is not read from the
Handler
- While
runMsg
does setGasUsed
, it is always overridden in the last defer ofrunTx
: https://github.com/cosmos/cosmos-sdk/blob/d5ec8427e2254fdf6e4ceec1f7ad9503b04646f7/baseapp/baseapp.go#L548-L550
In fact, a Handler
can return any info in these two Gas*
fields and they will be ignored. So why expose it to them?
If we look at the end result in the abci interface:
return abci.ResponseDeliverTx{
Code: uint32(result.Code),
Codespace: string(result.Codespace),
Data: result.Data,
Log: result.Log,
GasWanted: int64(result.GasWanted), // TODO: Should type accept unsigned ints?
GasUsed: int64(result.GasUsed), // TODO: Should type accept unsigned ints?
Events: result.Events.ToABCIEvents(),
}
I see this info coming from various sources:
- Code/Codespace/Log represent info from
error
, as you are refactoring now - Data/Log/Events represent info from Result, set on successful execution
- GasUsed/GasWanted is metering info which is handled in the
runTx
wrapper and not in a lower-level component. I would add a separate type for this.
My suggestion is:
- Leave
runMsg
with(*Result, error)
return value, and set Result tonil
on error - Update
runTx
return value to(GasInfo, *Result, error)
where Result and error are as runMsg (plus some other cases), but GasInfo is always set (from the defer function).
type GasInfo struct {
GasWanted uint64
GasUsed uint64
}
This actually would leave a super-minimal Result
type, which is actually the only info a Handler can return on success:
type Result struct {
Log string
Data []byte
Events Events
}
What do you think? I know it adds a type, but it can be a private type and is only used to communicate between runTx
and CheckTx
/DeliverTx
, while further simplifying the Result
interface to the essential. Since much more development work is expected to happen producing implementations of the Handler
interface, than in the BaseApp
wrapper around them, it seems like a nice tradeoff to me to make more simplicity for the Handler developers, which is actually the main goal of separating out the error return value, to make error handling simpler (and with better logging).
@@ -17,7 +17,7 @@ type Msg interface { | |||
|
|||
// ValidateBasic does a simple validation check that | |||
// doesn't require access to any other information. | |||
ValidateBasic() Error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error
is a superset of error
, so all existing code will work.
But they will have to update their function signatures, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe so
type Handler func(ctx Context, msg Msg) Result | ||
// A reference to a Result type must be returned upon success and an error upon | ||
// failure. | ||
type Handler func(ctx Context, msg Msg) (*Result, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I would actually wonder if we want to give a grace period for people to upgrade their 3rd party modules.
Otherwise this PR will leave the entire SDK in a broken state for a while, no?
Like call the new Handler MsgHandler
and then have a wrapper function that converts Handler
-> MsgHandler
Otherwise, there is quite some work to clean up code before this can go into master.
With the incremental approach, you can split that into several prs (even if all done before a release)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems more plausible approach as well.
@@ -126,7 +126,7 @@ func ABCIError(codespace string, code uint32, log string) error { | |||
|
|||
// Error represents a root error. | |||
// | |||
// Weave framework is using root error to categorize issues. Each instance | |||
// The SDK uses root errors (see above) to categorize issues. Each instance |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, missed this... as you see much of this code was copied, with minor adaptations (like adding codespaces)
@@ -186,7 +195,7 @@ type Error interface { | |||
} | |||
|
|||
// NewError - create an error. | |||
func NewError(codespace CodespaceType, code CodeType, format string, args ...interface{}) Error { | |||
func NewErrorz(codespace CodespaceType, code CodeType, format string, args ...interface{}) Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why add the z?
If intentional, I guess the godoc should be updated also
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems more like a typo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is intentional; I wanted the go compiler that resulted in this to help updating code due to new APIs. This PR is still a DRAFT; ignore.
@@ -522,7 +523,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk | |||
|
|||
// only run the tx if there is block gas remaining | |||
if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { | |||
return sdk.ErrOutOfGas("no block gas left to run tx").Result() | |||
return nil, sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, looks good to me. I hope this format looks good for you also.
} | ||
|
||
// only update state if all messages pass | ||
if result.IsOK() { | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be if err == nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually can be combined with the above statment...
if err == nil && mode != runTxDeliver { ...Write() }
return result, err
log := fmt.Sprintf("recovered: %v\nstack:\n%v", r, string(debug.Stack())) | ||
result = sdk.ErrInternal(log).Result() | ||
err = sdkerrors.Wrap(sdkerrors.ErrPanic, log) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice use of ErrPanic
@@ -610,79 +615,68 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk | |||
// MultiStore in case message processing fails. At this point, the MultiStore | |||
// is doubly cached-wrapped. | |||
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes) | |||
result = app.runMsgs(runMsgCtx, msgs, mode) | |||
result, err = app.runMsgs(runMsgCtx, msgs, mode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so the reason we need result even if there is an error is to capture the gas consumed, right?
Even if the data, events, etc of result are meaningless in error, the gas usage still needs to be communicated, as that is handled above? (Trying to understand the flow)
// skip actual execution for CheckTx mode | ||
if mode != runTxModeCheck { | ||
msgResult = handler(ctx, msg) | ||
err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized message type: %s,", msgRoute) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not return nil, err
, which would be closest to the old behavior?
if err := validateBasicTxMsgs(msgs); err != nil { | ||
return err.Result() | ||
return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will panic... since the first defer block above sets result.GasWanted = gasWanted
, we can never safely return nil here.
I didn't realize this case, and it seems this function must either always return a valid Result (in which case make it Result
, not *Result
so at least the empty value is valid), or the logic on 548-549 needs to change (I am not sure how that has repercussions on the rest of the stack)
closes: #4844
/cc @ethanfrey @marbar3778
Targeted PR against correct branch (see CONTRIBUTING.md)
Linked to github-issue with discussion and accepted design OR link to spec that describes this work.
Wrote tests
Updated relevant documentation (
docs/
)Added a relevant changelog entry to the
Unreleased
section inCHANGELOG.md
Re-reviewed
Files changed
in the github PR explorerFor Admin Use: