github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/state/notification_event.go (about) 1 package state 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 8 "github.com/nspcc-dev/neo-go/pkg/io" 9 "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" 10 "github.com/nspcc-dev/neo-go/pkg/util" 11 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 12 "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" 13 ) 14 15 // NotificationEvent is a tuple of the scripthash that has emitted the Item as a 16 // notification and the item itself. 17 type NotificationEvent struct { 18 ScriptHash util.Uint160 `json:"contract"` 19 Name string `json:"eventname"` 20 Item *stackitem.Array `json:"state"` 21 } 22 23 // AppExecResult represents the result of the script execution, gathering together 24 // all resulting notifications, state, stack and other metadata. 25 type AppExecResult struct { 26 Container util.Uint256 27 Execution 28 } 29 30 // ContainedNotificationEvent represents a wrapper for a notification from script execution. 31 type ContainedNotificationEvent struct { 32 // Container hash is the hash of script container which is either a block or a transaction. 33 Container util.Uint256 34 NotificationEvent 35 } 36 37 // EncodeBinary implements the Serializable interface. 38 func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) { 39 ne.EncodeBinaryWithContext(w, stackitem.NewSerializationContext()) 40 } 41 42 // EncodeBinaryWithContext is the same as EncodeBinary, but allows to efficiently reuse 43 // stack item serialization context. 44 func (ne *NotificationEvent) EncodeBinaryWithContext(w *io.BinWriter, sc *stackitem.SerializationContext) { 45 ne.ScriptHash.EncodeBinary(w) 46 w.WriteString(ne.Name) 47 b, err := sc.Serialize(ne.Item, false) 48 if err != nil { 49 w.Err = err 50 return 51 } 52 w.WriteBytes(b) 53 } 54 55 // DecodeBinary implements the Serializable interface. 56 func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { 57 ne.ScriptHash.DecodeBinary(r) 58 ne.Name = r.ReadString() 59 item := stackitem.DecodeBinary(r) 60 if r.Err != nil { 61 return 62 } 63 arr, ok := item.Value().([]stackitem.Item) 64 if !ok { 65 r.Err = errors.New("Array or Struct expected") 66 return 67 } 68 ne.Item = stackitem.NewArray(arr) 69 } 70 71 // EncodeBinary implements the Serializable interface. 72 func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { 73 aer.EncodeBinaryWithContext(w, stackitem.NewSerializationContext()) 74 } 75 76 // EncodeBinaryWithContext is the same as EncodeBinary, but allows to efficiently reuse 77 // stack item serialization context. 78 func (aer *AppExecResult) EncodeBinaryWithContext(w *io.BinWriter, sc *stackitem.SerializationContext) { 79 w.WriteBytes(aer.Container[:]) 80 w.WriteB(byte(aer.Trigger)) 81 w.WriteB(byte(aer.VMState)) 82 w.WriteU64LE(uint64(aer.GasConsumed)) 83 // Stack items are expected to be marshaled one by one. 84 w.WriteVarUint(uint64(len(aer.Stack))) 85 for _, it := range aer.Stack { 86 b, err := sc.Serialize(it, true) 87 if err != nil { 88 w.Err = err 89 return 90 } 91 w.WriteBytes(b) 92 } 93 w.WriteVarUint(uint64(len(aer.Events))) 94 for i := range aer.Events { 95 aer.Events[i].EncodeBinaryWithContext(w, sc) 96 } 97 w.WriteVarBytes([]byte(aer.FaultException)) 98 } 99 100 // DecodeBinary implements the Serializable interface. 101 func (aer *AppExecResult) DecodeBinary(r *io.BinReader) { 102 r.ReadBytes(aer.Container[:]) 103 aer.Trigger = trigger.Type(r.ReadB()) 104 aer.VMState = vmstate.State(r.ReadB()) 105 aer.GasConsumed = int64(r.ReadU64LE()) 106 sz := r.ReadVarUint() 107 if stackitem.MaxDeserialized < sz && r.Err == nil { 108 r.Err = errors.New("invalid format") 109 } 110 if r.Err != nil { 111 return 112 } 113 arr := make([]stackitem.Item, sz) 114 for i := 0; i < int(sz); i++ { 115 arr[i] = stackitem.DecodeBinaryProtected(r) 116 if r.Err != nil { 117 return 118 } 119 } 120 aer.Stack = arr 121 r.ReadArray(&aer.Events) 122 aer.FaultException = r.ReadString() 123 } 124 125 // notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling. 126 type notificationEventAux struct { 127 ScriptHash util.Uint160 `json:"contract"` 128 Name string `json:"eventname"` 129 Item json.RawMessage `json:"state"` 130 } 131 132 // MarshalJSON implements the json.Marshaler interface. 133 func (ne NotificationEvent) MarshalJSON() ([]byte, error) { 134 item, err := stackitem.ToJSONWithTypes(ne.Item) 135 if err != nil { 136 item = []byte(fmt.Sprintf(`"error: %v"`, err)) 137 } 138 return json.Marshal(¬ificationEventAux{ 139 ScriptHash: ne.ScriptHash, 140 Name: ne.Name, 141 Item: item, 142 }) 143 } 144 145 // UnmarshalJSON implements the json.Unmarshaler interface. 146 func (ne *NotificationEvent) UnmarshalJSON(data []byte) error { 147 aux := new(notificationEventAux) 148 if err := json.Unmarshal(data, aux); err != nil { 149 return err 150 } 151 item, err := stackitem.FromJSONWithTypes(aux.Item) 152 if err != nil { 153 return err 154 } 155 if t := item.Type(); t != stackitem.ArrayT { 156 return fmt.Errorf("failed to convert notification event state of type %s to array", t.String()) 157 } 158 ne.Item = item.(*stackitem.Array) 159 ne.Name = aux.Name 160 ne.ScriptHash = aux.ScriptHash 161 return nil 162 } 163 164 // appExecResultAux is an auxiliary struct for JSON marshalling. 165 type appExecResultAux struct { 166 Container util.Uint256 `json:"container"` 167 } 168 169 // MarshalJSON implements the json.Marshaler interface. 170 func (aer *AppExecResult) MarshalJSON() ([]byte, error) { 171 h, err := json.Marshal(&appExecResultAux{ 172 Container: aer.Container, 173 }) 174 if err != nil { 175 return nil, fmt.Errorf("failed to marshal hash: %w", err) 176 } 177 exec, err := json.Marshal(aer.Execution) 178 if err != nil { 179 return nil, fmt.Errorf("failed to marshal execution: %w", err) 180 } 181 182 if h[len(h)-1] != '}' || exec[0] != '{' { 183 return nil, errors.New("can't merge internal jsons") 184 } 185 h[len(h)-1] = ',' 186 h = append(h, exec[1:]...) 187 return h, nil 188 } 189 190 // UnmarshalJSON implements the json.Unmarshaler interface. 191 func (aer *AppExecResult) UnmarshalJSON(data []byte) error { 192 aux := new(appExecResultAux) 193 if err := json.Unmarshal(data, aux); err != nil { 194 return err 195 } 196 if err := json.Unmarshal(data, &aer.Execution); err != nil { 197 return err 198 } 199 aer.Container = aux.Container 200 return nil 201 } 202 203 // Execution represents the result of a single script execution, gathering together 204 // all resulting notifications, state, stack and other metadata. 205 type Execution struct { 206 Trigger trigger.Type 207 VMState vmstate.State 208 GasConsumed int64 209 Stack []stackitem.Item 210 Events []NotificationEvent 211 FaultException string 212 } 213 214 // executionAux represents an auxiliary struct for Execution JSON marshalling. 215 type executionAux struct { 216 Trigger string `json:"trigger"` 217 VMState string `json:"vmstate"` 218 GasConsumed int64 `json:"gasconsumed,string"` 219 Stack json.RawMessage `json:"stack"` 220 Events []NotificationEvent `json:"notifications"` 221 FaultException *string `json:"exception"` 222 } 223 224 // MarshalJSON implements the json.Marshaler interface. 225 func (e Execution) MarshalJSON() ([]byte, error) { 226 arr := make([]json.RawMessage, len(e.Stack)) 227 for i := range arr { 228 data, err := stackitem.ToJSONWithTypes(e.Stack[i]) 229 if err != nil { 230 data = []byte(fmt.Sprintf(`"error: %v"`, err)) 231 } 232 arr[i] = data 233 } 234 st, err := json.Marshal(arr) 235 if err != nil { 236 return nil, err 237 } 238 var exception *string 239 if e.FaultException != "" { 240 exception = &e.FaultException 241 } 242 return json.Marshal(&executionAux{ 243 Trigger: e.Trigger.String(), 244 VMState: e.VMState.String(), 245 GasConsumed: e.GasConsumed, 246 Stack: st, 247 Events: e.Events, 248 FaultException: exception, 249 }) 250 } 251 252 // UnmarshalJSON implements the json.Unmarshaler interface. 253 func (e *Execution) UnmarshalJSON(data []byte) error { 254 aux := new(executionAux) 255 if err := json.Unmarshal(data, aux); err != nil { 256 return err 257 } 258 var arr []json.RawMessage 259 if err := json.Unmarshal(aux.Stack, &arr); err == nil { 260 st := make([]stackitem.Item, len(arr)) 261 for i := range arr { 262 st[i], err = stackitem.FromJSONWithTypes(arr[i]) 263 if err != nil { 264 var s string 265 if json.Unmarshal(arr[i], &s) != nil { 266 break 267 } 268 err = nil 269 } 270 } 271 if err == nil { 272 e.Stack = st 273 } 274 } 275 trigger, err := trigger.FromString(aux.Trigger) 276 if err != nil { 277 return err 278 } 279 e.Trigger = trigger 280 state, err := vmstate.FromString(aux.VMState) 281 if err != nil { 282 return err 283 } 284 e.VMState = state 285 e.Events = aux.Events 286 e.GasConsumed = aux.GasConsumed 287 if aux.FaultException != nil { 288 e.FaultException = *aux.FaultException 289 } 290 return nil 291 } 292 293 // containedNotificationEventAux is an auxiliary struct for JSON marshalling. 294 type containedNotificationEventAux struct { 295 Container util.Uint256 `json:"container"` 296 } 297 298 // MarshalJSON implements the json.Marshaler interface. 299 func (ne *ContainedNotificationEvent) MarshalJSON() ([]byte, error) { 300 h, err := json.Marshal(&containedNotificationEventAux{ 301 Container: ne.Container, 302 }) 303 if err != nil { 304 return nil, fmt.Errorf("failed to marshal hash: %w", err) 305 } 306 exec, err := json.Marshal(ne.NotificationEvent) 307 if err != nil { 308 return nil, fmt.Errorf("failed to marshal execution: %w", err) 309 } 310 311 if h[len(h)-1] != '}' || exec[0] != '{' { 312 return nil, errors.New("can't merge internal jsons") 313 } 314 h[len(h)-1] = ',' 315 h = append(h, exec[1:]...) 316 return h, nil 317 } 318 319 // UnmarshalJSON implements the json.Unmarshaler interface. 320 func (ne *ContainedNotificationEvent) UnmarshalJSON(data []byte) error { 321 aux := new(containedNotificationEventAux) 322 if err := json.Unmarshal(data, aux); err != nil { 323 return err 324 } 325 if err := json.Unmarshal(data, &ne.NotificationEvent); err != nil { 326 return err 327 } 328 ne.Container = aux.Container 329 return nil 330 }