github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/neorpc/result/invoke.go (about) 1 package result 2 3 import ( 4 "encoding/json" 5 "fmt" 6 7 "github.com/google/uuid" 8 "github.com/nspcc-dev/neo-go/pkg/core/state" 9 "github.com/nspcc-dev/neo-go/pkg/core/storage/dboper" 10 "github.com/nspcc-dev/neo-go/pkg/core/transaction" 11 "github.com/nspcc-dev/neo-go/pkg/vm/invocations" 12 "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" 13 ) 14 15 // Invoke represents a code invocation result and is used by several RPC calls 16 // that invoke functions, scripts and generic bytecode. 17 type Invoke struct { 18 State string 19 GasConsumed int64 20 Script []byte 21 Stack []stackitem.Item 22 FaultException string 23 Notifications []state.NotificationEvent 24 Transaction *transaction.Transaction 25 Diagnostics *InvokeDiag 26 Session uuid.UUID 27 } 28 29 // InvokeDiag is an additional diagnostic data for invocation. 30 type InvokeDiag struct { 31 Changes []dboper.Operation `json:"storagechanges"` 32 Invocations []*invocations.Tree `json:"invokedcontracts"` 33 } 34 35 type invokeAux struct { 36 State string `json:"state"` 37 GasConsumed int64 `json:"gasconsumed,string"` 38 Script []byte `json:"script"` 39 Stack json.RawMessage `json:"stack"` 40 FaultException *string `json:"exception"` 41 Notifications []state.NotificationEvent `json:"notifications"` 42 Transaction []byte `json:"tx,omitempty"` 43 Diagnostics *InvokeDiag `json:"diagnostics,omitempty"` 44 Session string `json:"session,omitempty"` 45 } 46 47 // iteratorInterfaceName is a string used to mark Iterator inside the InteropInterface. 48 const iteratorInterfaceName = "IIterator" 49 50 type iteratorAux struct { 51 Type string `json:"type"` 52 Interface string `json:"interface,omitempty"` 53 ID string `json:"id,omitempty"` 54 Value []json.RawMessage `json:"iterator,omitempty"` 55 Truncated bool `json:"truncated,omitempty"` 56 } 57 58 // Iterator represents VM iterator identifier. It either has ID set (for those JSON-RPC servers 59 // that support sessions) or non-nil Values and Truncated set (for those JSON-RPC servers that 60 // doesn't support sessions but perform in-place iterator traversing) or doesn't have ID, Values 61 // and Truncated set at all (for those JSON-RPC servers that doesn't support iterator sessions 62 // and doesn't perform in-place iterator traversing). 63 type Iterator struct { 64 // ID represents iterator ID. It is non-nil iff JSON-RPC server support session mechanism. 65 ID *uuid.UUID 66 67 // Values contains deserialized VM iterator values with a truncated flag. It is non-nil 68 // iff JSON-RPC server does not support sessions mechanism and able to traverse iterator. 69 Values []stackitem.Item 70 Truncated bool 71 } 72 73 // MarshalJSON implements the json.Marshaler. 74 func (r Iterator) MarshalJSON() ([]byte, error) { 75 var iaux iteratorAux 76 iaux.Type = stackitem.InteropT.String() 77 if r.ID != nil { 78 iaux.Interface = iteratorInterfaceName 79 iaux.ID = r.ID.String() 80 } else { 81 value := make([]json.RawMessage, len(r.Values)) 82 for i := range r.Values { 83 var err error 84 value[i], err = stackitem.ToJSONWithTypes(r.Values[i]) 85 if err != nil { 86 return nil, err 87 } 88 } 89 iaux.Value = value 90 iaux.Truncated = r.Truncated 91 } 92 return json.Marshal(iaux) 93 } 94 95 // UnmarshalJSON implements the json.Unmarshaler. 96 func (r *Iterator) UnmarshalJSON(data []byte) error { 97 iteratorAux := new(iteratorAux) 98 err := json.Unmarshal(data, iteratorAux) 99 if err != nil { 100 return err 101 } 102 if len(iteratorAux.Interface) != 0 { 103 if iteratorAux.Interface != iteratorInterfaceName { 104 return fmt.Errorf("unknown InteropInterface: %s", iteratorAux.Interface) 105 } 106 var iID uuid.UUID 107 iID, err = uuid.Parse(iteratorAux.ID) 108 if err != nil { 109 return fmt.Errorf("failed to unmarshal iterator ID: %w", err) 110 } 111 r.ID = &iID 112 } else { 113 r.Values = make([]stackitem.Item, len(iteratorAux.Value)) 114 for j := range r.Values { 115 r.Values[j], err = stackitem.FromJSONWithTypes(iteratorAux.Value[j]) 116 if err != nil { 117 return fmt.Errorf("failed to unmarshal iterator values: %w", err) 118 } 119 } 120 r.Truncated = iteratorAux.Truncated 121 } 122 return nil 123 } 124 125 // MarshalJSON implements the json.Marshaler. 126 func (r Invoke) MarshalJSON() ([]byte, error) { 127 var ( 128 st json.RawMessage 129 err error 130 faultSep string 131 arr = make([]json.RawMessage, len(r.Stack)) 132 ) 133 if len(r.FaultException) != 0 { 134 faultSep = " / " 135 } 136 for i := range arr { 137 var data []byte 138 139 iter, ok := r.Stack[i].Value().(Iterator) 140 if (r.Stack[i].Type() == stackitem.InteropT) && ok { 141 data, err = json.Marshal(iter) 142 } else { 143 data, err = stackitem.ToJSONWithTypes(r.Stack[i]) 144 } 145 if err != nil { 146 r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err) 147 break 148 } 149 arr[i] = data 150 } 151 152 if err == nil { 153 st, err = json.Marshal(arr) 154 if err != nil { 155 return nil, err 156 } 157 } 158 var txbytes []byte 159 if r.Transaction != nil { 160 txbytes = r.Transaction.Bytes() 161 } 162 var sessionID string 163 if r.Session != (uuid.UUID{}) { 164 sessionID = r.Session.String() 165 } 166 aux := &invokeAux{ 167 GasConsumed: r.GasConsumed, 168 Script: r.Script, 169 State: r.State, 170 Stack: st, 171 Notifications: r.Notifications, 172 Transaction: txbytes, 173 Diagnostics: r.Diagnostics, 174 Session: sessionID, 175 } 176 if len(r.FaultException) != 0 { 177 aux.FaultException = &r.FaultException 178 } 179 return json.Marshal(aux) 180 } 181 182 // UnmarshalJSON implements the json.Unmarshaler. 183 func (r *Invoke) UnmarshalJSON(data []byte) error { 184 var err error 185 aux := new(invokeAux) 186 if err = json.Unmarshal(data, aux); err != nil { 187 return err 188 } 189 if len(aux.Session) != 0 { 190 r.Session, err = uuid.Parse(aux.Session) 191 if err != nil { 192 return fmt.Errorf("failed to parse session ID: %w", err) 193 } 194 } 195 var arr []json.RawMessage 196 if err = json.Unmarshal(aux.Stack, &arr); err == nil { 197 st := make([]stackitem.Item, len(arr)) 198 for i := range arr { 199 st[i], err = stackitem.FromJSONWithTypes(arr[i]) 200 if err != nil { 201 break 202 } 203 if st[i].Type() == stackitem.InteropT { 204 var iter = Iterator{} 205 err = json.Unmarshal(arr[i], &iter) 206 if err != nil { 207 break 208 } 209 st[i] = stackitem.NewInterop(iter) 210 } 211 } 212 if err != nil { 213 return fmt.Errorf("failed to unmarshal stack: %w", err) 214 } 215 r.Stack = st 216 } 217 var tx *transaction.Transaction 218 if len(aux.Transaction) != 0 { 219 tx, err = transaction.NewTransactionFromBytes(aux.Transaction) 220 if err != nil { 221 return err 222 } 223 } 224 r.GasConsumed = aux.GasConsumed 225 r.Script = aux.Script 226 r.State = aux.State 227 if aux.FaultException != nil { 228 r.FaultException = *aux.FaultException 229 } 230 r.Notifications = aux.Notifications 231 r.Transaction = tx 232 r.Diagnostics = aux.Diagnostics 233 return nil 234 } 235 236 // AppExecToInvocation converts state.AppExecResult to result.Invoke and can be used 237 // as a wrapper for actor.Wait. The result of AppExecToInvocation doesn't have all fields 238 // properly filled, it's limited by State, GasConsumed, Stack, FaultException and Notifications. 239 // The result of AppExecToInvocation can be passed to unwrap package helpers. 240 func AppExecToInvocation(aer *state.AppExecResult, err error) (*Invoke, error) { 241 if err != nil { 242 return nil, err 243 } 244 return &Invoke{ 245 State: aer.VMState.String(), 246 GasConsumed: aer.GasConsumed, 247 Stack: aer.Stack, 248 FaultException: aer.FaultException, 249 Notifications: aer.Events, 250 }, nil 251 }