github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/x/wasm/keeper/msg_dispatcher.go (about) 1 package keeper 2 3 import ( 4 "bytes" 5 "fmt" 6 "sort" 7 8 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/kv" 9 10 wasmvmtypes "github.com/CosmWasm/wasmvm/types" 11 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 12 sdkerrors "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/errors" 13 "github.com/fibonacci-chain/fbc/x/wasm/types" 14 ) 15 16 // Messenger is an extension point for custom wasmd message handling 17 type Messenger interface { 18 // DispatchMsg encodes the wasmVM message and dispatches it. 19 DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) 20 } 21 22 // replyer is a subset of keeper that can handle replies to submessages 23 type replyer interface { 24 reply(ctx sdk.Context, contractAddress sdk.AccAddress, reply wasmvmtypes.Reply) ([]byte, error) 25 } 26 27 // MessageDispatcher coordinates message sending and submessage reply/ state commits 28 type MessageDispatcher struct { 29 messenger Messenger 30 keeper replyer 31 } 32 33 // NewMessageDispatcher constructor 34 func NewMessageDispatcher(messenger Messenger, keeper replyer) *MessageDispatcher { 35 return &MessageDispatcher{messenger: messenger, keeper: keeper} 36 } 37 38 // DispatchMessages sends all messages. 39 func (d MessageDispatcher) DispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.CosmosMsg) error { 40 for _, msg := range msgs { 41 events, _, err := d.messenger.DispatchMsg(ctx, contractAddr, ibcPort, msg) 42 if err != nil { 43 return err 44 } 45 // redispatch all events, (type sdk.EventTypeMessage will be filtered out in the handler) 46 ctx.EventManager().EmitEvents(events) 47 } 48 return nil 49 } 50 51 // dispatchMsgWithGasLimit sends a message with gas limit applied 52 func (d MessageDispatcher) dispatchMsgWithGasLimit(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msg wasmvmtypes.CosmosMsg, gasLimit uint64) (events []sdk.Event, data [][]byte, err error) { 53 limitedMeter := sdk.NewGasMeter(gasLimit) 54 subCtx := ctx 55 subCtx.SetGasMeter(limitedMeter) 56 57 // catch out of gas panic and just charge the entire gas limit 58 defer func() { 59 if r := recover(); r != nil { 60 // if it's not an OutOfGas error, raise it again 61 if _, ok := r.(sdk.ErrorOutOfGas); !ok { 62 // log it to get the original stack trace somewhere (as panic(r) keeps message but stacktrace to here 63 moduleLogger(ctx).Info("SubMsg rethrowing panic: %#v", r) 64 panic(r) 65 } 66 ctx.GasMeter().ConsumeGas(gasLimit, "Sub-Message OutOfGas panic") 67 err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "SubMsg hit gas limit") 68 } 69 }() 70 events, data, err = d.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg) 71 72 // make sure we charge the parent what was spent 73 spent := subCtx.GasMeter().GasConsumed() 74 ctx.GasMeter().ConsumeGas(spent, "From limited Sub-Message") 75 76 return events, data, err 77 } 78 79 // DispatchSubmessages builds a sandbox to execute these messages and returns the execution result to the contract 80 // that dispatched them, both on success as well as failure 81 func (d MessageDispatcher) DispatchSubmessages(ctx sdk.Context, contractAddr sdk.AccAddress, ibcPort string, msgs []wasmvmtypes.SubMsg) ([]byte, error) { 82 var rsp []byte 83 for _, msg := range msgs { 84 switch msg.ReplyOn { 85 case wasmvmtypes.ReplySuccess, wasmvmtypes.ReplyError, wasmvmtypes.ReplyAlways, wasmvmtypes.ReplyNever: 86 default: 87 return nil, sdkerrors.Wrap(types.ErrInvalid, "replyOn value") 88 } 89 // first, we build a sub-context which we can use inside the submessages 90 subCtx, commit := ctx.CacheContext() 91 em := sdk.NewEventManager() 92 subCtx.SetEventManager(em) 93 94 // check how much gas left locally, optionally wrap the gas meter 95 gasRemaining := ctx.GasMeter().Limit() - ctx.GasMeter().GasConsumed() 96 limitGas := msg.GasLimit != nil && (*msg.GasLimit < gasRemaining) 97 98 var err error 99 var events []sdk.Event 100 var data [][]byte 101 if limitGas { 102 events, data, err = d.dispatchMsgWithGasLimit(subCtx, contractAddr, ibcPort, msg.Msg, *msg.GasLimit) 103 } else { 104 events, data, err = d.messenger.DispatchMsg(subCtx, contractAddr, ibcPort, msg.Msg) 105 } 106 107 // if it succeeds, commit state changes from submessage, and pass on events to Event Manager 108 var filteredEvents []sdk.Event 109 if err == nil { 110 commit() 111 filteredEvents = filterEvents(append(em.Events(), events...)) 112 ctx.EventManager().EmitEvents(filteredEvents) 113 if msg.Msg.Wasm == nil { 114 filteredEvents = []sdk.Event{} 115 } else { 116 for _, e := range filteredEvents { 117 attributes := e.Attributes 118 sort.SliceStable(attributes, func(i, j int) bool { 119 return bytes.Compare(attributes[i].Key, attributes[j].Key) < 0 120 }) 121 } 122 } 123 } // on failure, revert state from sandbox, and ignore events (just skip doing the above) 124 125 // we only callback if requested. Short-circuit here the cases we don't want to 126 if (msg.ReplyOn == wasmvmtypes.ReplySuccess || msg.ReplyOn == wasmvmtypes.ReplyNever) && err != nil { 127 return nil, err 128 } 129 if msg.ReplyOn == wasmvmtypes.ReplyNever || (msg.ReplyOn == wasmvmtypes.ReplyError && err == nil) { 130 continue 131 } 132 133 // otherwise, we create a SubMsgResult and pass it into the calling contract 134 var result wasmvmtypes.SubMsgResult 135 if err == nil { 136 // just take the first one for now if there are multiple sub-sdk messages 137 // and safely return nothing if no data 138 var responseData []byte 139 if len(data) > 0 { 140 responseData = data[0] 141 } 142 result = wasmvmtypes.SubMsgResult{ 143 Ok: &wasmvmtypes.SubMsgResponse{ 144 Events: sdkEventsToWasmVMEvents(filteredEvents), 145 Data: responseData, 146 }, 147 } 148 } else { 149 // Issue #759 - we don't return error string for worries of non-determinism 150 moduleLogger(ctx).Info("Redacting submessage error", "cause", err) 151 result = wasmvmtypes.SubMsgResult{ 152 Err: redactError(err).Error(), 153 } 154 } 155 156 // now handle the reply, we use the parent context, and abort on error 157 reply := wasmvmtypes.Reply{ 158 ID: msg.ID, 159 Result: result, 160 } 161 162 // we can ignore any result returned as there is nothing to do with the data 163 // and the events are already in the ctx.EventManager() 164 rspData, err := d.keeper.reply(ctx, contractAddr, reply) 165 switch { 166 case err != nil: 167 return nil, sdkerrors.Wrap(err, "reply") 168 case rspData != nil: 169 rsp = rspData 170 } 171 } 172 return rsp, nil 173 } 174 175 // Issue #759 - we don't return error string for worries of non-determinism 176 func redactError(err error) error { 177 // Do not redact system errors 178 // SystemErrors must be created in x/wasm and we can ensure determinism 179 if wasmvmtypes.ToSystemError(err) != nil { 180 return err 181 } 182 183 // FIXME: do we want to hardcode some constant string mappings here as well? 184 // Or better document them? (SDK error string may change on a patch release to fix wording) 185 // sdk/11 is out of gas 186 // sdk/5 is insufficient funds (on bank send) 187 // (we can theoretically redact less in the future, but this is a first step to safety) 188 codespace, code, _ := sdkerrors.ABCIInfo(err, false) 189 return fmt.Errorf("codespace: %s, code: %d", codespace, code) 190 } 191 192 func filterEvents(events []sdk.Event) []sdk.Event { 193 // pre-allocate space for efficiency 194 res := make([]sdk.Event, 0, len(events)) 195 for _, ev := range events { 196 if ev.Type != "message" { 197 res = append(res, ev) 198 } 199 } 200 return res 201 } 202 203 func sdkEventsToWasmVMEvents(events []sdk.Event) []wasmvmtypes.Event { 204 res := make([]wasmvmtypes.Event, len(events)) 205 for i, ev := range events { 206 res[i] = wasmvmtypes.Event{ 207 Type: ev.Type, 208 Attributes: sdkAttributesToWasmVMAttributes(ev.Attributes), 209 } 210 } 211 return res 212 } 213 214 func sdkAttributesToWasmVMAttributes(attrs []kv.Pair) []wasmvmtypes.EventAttribute { 215 res := make([]wasmvmtypes.EventAttribute, len(attrs)) 216 for i, attr := range attrs { 217 res[i] = wasmvmtypes.EventAttribute{ 218 Key: string(attr.Key), 219 Value: string(attr.Value), 220 } 221 } 222 return res 223 }