diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 3cc88aa7dfc..927d90f0304 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -43,6 +43,8 @@ type ( // particular, if a module changed the substore key name (or removed a substore) // between two versions of the software. StoreLoader func(ms storetypes.CommitMultiStore) error + + contextKeyT string ) const ( @@ -54,6 +56,8 @@ const ( execModeVoteExtension // Extend or verify a pre-commit vote execModeVerifyVoteExtension // Verify a vote extension execModeFinalize // Finalize a block proposal + + DoNotFailFastSendContextKey contextKeyT = "DoNotFailFast" ) var _ servertypes.ABCI = (*BaseApp)(nil) @@ -708,7 +712,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) error { if app.preBlocker != nil { - ctx := app.finalizeBlockState.Context() + ctx := app.finalizeBlockState.Context().WithValue(DoNotFailFastSendContextKey, struct{}{}) rsp, err := app.preBlocker(ctx, req) if err != nil { return err @@ -733,7 +737,8 @@ func (app *BaseApp) beginBlock(req *abci.RequestFinalizeBlock) (sdk.BeginBlock, ) if app.beginBlocker != nil { - resp, err = app.beginBlocker(app.finalizeBlockState.Context()) + ctx := app.finalizeBlockState.Context().WithValue(DoNotFailFastSendContextKey, struct{}{}) + resp, err = app.beginBlocker(ctx) if err != nil { return resp, err } @@ -746,7 +751,6 @@ func (app *BaseApp) beginBlock(req *abci.RequestFinalizeBlock) (sdk.BeginBlock, ) } - ctx := app.finalizeBlockState.ctx app.AddStreamEvents(ctx.BlockHeight(), ctx.BlockTime(), resp.Events, true) resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents) @@ -797,11 +801,12 @@ func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult { // endBlock is an application-defined function that is called after transactions // have been processed in FinalizeBlock. -func (app *BaseApp) endBlock(ctx context.Context) (sdk.EndBlock, error) { +func (app *BaseApp) endBlock(_ context.Context) (sdk.EndBlock, error) { var endblock sdk.EndBlock if app.endBlocker != nil { - eb, err := app.endBlocker(app.finalizeBlockState.Context()) + ctx := app.finalizeBlockState.Context().WithValue(DoNotFailFastSendContextKey, struct{}{}) + eb, err := app.endBlocker(ctx) if err != nil { return endblock, err } @@ -814,7 +819,6 @@ func (app *BaseApp) endBlock(ctx context.Context) (sdk.EndBlock, error) { ) } - ctx := app.finalizeBlockState.ctx app.AddStreamEvents(ctx.BlockHeight(), ctx.BlockTime(), eb.Events, true) eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents) diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 3435c4c6bc7..d1ed4dea547 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -157,22 +157,23 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, return err } - outAddresses := make([]sdk.AccAddress, len(outputs)) - for i, out := range outputs { + for _, out := range outputs { outAddress, err := k.ak.AddressCodec().StringToBytes(out.Address) if err != nil { return err } - outAddresses[i], err = k.sendRestriction.apply(ctx, inAddress, outAddress, out.Coins) - if err != nil { - return err - } - } + for _, coin := range out.Coins { + newOutAddress, err := k.sendRestriction.apply(ctx, inAddress, outAddress, coin) + if err != nil { + return err + } - err = k.subUnlockedCoins(ctx, inAddress, input.Coins, true) - if err != nil { - return err + err = k.sendCoin(ctx, inAddress, newOutAddress, coin) + if err != nil { + return err + } + } } sdkCtx := sdk.UnwrapSDKContext(ctx) @@ -183,30 +184,6 @@ func (k BaseSendKeeper) InputOutputCoins(ctx context.Context, input types.Input, ), ) - for i, out := range outputs { - if err := k.addCoins(ctx, outAddresses[i], out.Coins); err != nil { - return err - } - - sdkCtx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeTransfer, - sdk.NewAttribute(types.AttributeKeyRecipient, outAddresses[i].String()), - sdk.NewAttribute(sdk.AttributeKeyAmount, out.Coins.String()), - ), - ) - - // Create account if recipient does not exist. - // - // NOTE: This should ultimately be removed in favor a more flexible approach - // such as delegated fee messages. - accExists := k.ak.HasAccount(ctx, outAddresses[i]) - if !accExists { - defer telemetry.IncrCounter(1, "new", "account") - k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, outAddresses[i])) - } - } - return nil } @@ -217,17 +194,36 @@ func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccA return err } - toAddr, err := k.sendRestriction.apply(ctx, fromAddr, toAddr, amt) - if err != nil { - return err + for _, coin := range amt { + newToAddr, err := k.sendRestriction.apply(ctx, fromAddr, toAddr, coin) + if err != nil { + return err + } + + err = k.sendCoin(ctx, fromAddr, newToAddr, coin) + if err != nil { + return err + } } - err = k.subUnlockedCoins(ctx, fromAddr, amt, true) + sdkCtx := sdk.UnwrapSDKContext(ctx) + // bech32 encoding is expensive! Only do it once for fromAddr + fromAddrString := fromAddr.String() + sdkCtx.EventManager().EmitEvent(sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(types.AttributeKeySender, fromAddrString), + )) + + return nil +} + +func (k BaseSendKeeper) sendCoin(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coin) error { + err := k.subUnlockedCoins(ctx, fromAddr, sdk.NewCoins(amt), true) // only sub this coin if err != nil { return err } - err = k.addCoins(ctx, toAddr, amt) + err = k.addCoins(ctx, toAddr, sdk.NewCoins(amt)) if err != nil { return err } @@ -242,22 +238,15 @@ func (k BaseSendKeeper) SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccA k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr)) } - // bech32 encoding is expensive! Only do it once for fromAddr - fromAddrString := fromAddr.String() sdkCtx := sdk.UnwrapSDKContext(ctx) - sdkCtx.EventManager().EmitEvents(sdk.Events{ + sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeTransfer, sdk.NewAttribute(types.AttributeKeyRecipient, toAddr.String()), - sdk.NewAttribute(types.AttributeKeySender, fromAddrString), + sdk.NewAttribute(types.AttributeKeySender, fromAddr.String()), sdk.NewAttribute(sdk.AttributeKeyAmount, amt.String()), ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(types.AttributeKeySender, fromAddrString), - ), - }) - + ) return nil } @@ -529,7 +518,7 @@ func (r *sendRestriction) clear() { var _ types.SendRestrictionFn = (*sendRestriction)(nil).apply // apply applies the send restriction if there is one. If not, it's a no-op. -func (r *sendRestriction) apply(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { +func (r *sendRestriction) apply(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coin) (sdk.AccAddress, error) { if r == nil || r.fn == nil { return toAddr, nil } diff --git a/x/bank/types/restrictions.go b/x/bank/types/restrictions.go index 2ec4489a097..8cfd760d601 100644 --- a/x/bank/types/restrictions.go +++ b/x/bank/types/restrictions.go @@ -52,7 +52,7 @@ func ComposeMintingRestrictions(restrictions ...MintingRestrictionFn) MintingRes } // A SendRestrictionFn can restrict sends and/or provide a new receiver address. -type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (newToAddr sdk.AccAddress, err error) +type SendRestrictionFn func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coin) (newToAddr sdk.AccAddress, err error) // IsOnePerModuleType implements the depinject.OnePerModuleType interface. func (SendRestrictionFn) IsOnePerModuleType() {} @@ -60,7 +60,7 @@ func (SendRestrictionFn) IsOnePerModuleType() {} var _ SendRestrictionFn = NoOpSendRestrictionFn // NoOpSendRestrictionFn is a no-op SendRestrictionFn. -func NoOpSendRestrictionFn(_ context.Context, _, toAddr sdk.AccAddress, _ sdk.Coins) (sdk.AccAddress, error) { +func NoOpSendRestrictionFn(_ context.Context, _, toAddr sdk.AccAddress, _ sdk.Coin) (sdk.AccAddress, error) { return toAddr, nil } @@ -89,7 +89,7 @@ func ComposeSendRestrictions(restrictions ...SendRestrictionFn) SendRestrictionF case 1: return toRun[0] } - return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.AccAddress, error) { + return func(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coin) (sdk.AccAddress, error) { var err error for _, r := range toRun { toAddr, err = r(ctx, fromAddr, toAddr, amt) diff --git a/x/gov/abci.go b/x/gov/abci.go index 56ab8641bd1..e21337f1829 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -158,7 +158,8 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) error { // Messages may mutate state thus we use a cached context. If one of // the handlers fails, no state mutation is written and the error // message is logged. - cacheCtx, writeCache := ctx.CacheContext() + execCtx := ctx.WithValue(baseapp.DoNotFailFastSendContextKey, nil) // enable fail fast during msg handling + cacheCtx, writeCache := execCtx.CacheContext() messages, err := proposal.GetMsgs() if err != nil { proposal.Status = v1.StatusFailed