forked from TrueCloudLab/neoneo-go
10a0716217
Close #2704.
251 lines
7.3 KiB
Go
251 lines
7.3 KiB
Go
package result
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/invocations"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// Invoke represents a code invocation result and is used by several RPC calls
|
|
// that invoke functions, scripts and generic bytecode.
|
|
type Invoke struct {
|
|
State string
|
|
GasConsumed int64
|
|
Script []byte
|
|
Stack []stackitem.Item
|
|
FaultException string
|
|
Notifications []state.NotificationEvent
|
|
Transaction *transaction.Transaction
|
|
Diagnostics *InvokeDiag
|
|
Session uuid.UUID
|
|
}
|
|
|
|
// InvokeDiag is an additional diagnostic data for invocation.
|
|
type InvokeDiag struct {
|
|
Changes []dboper.Operation `json:"storagechanges"`
|
|
Invocations []*invocations.Tree `json:"invokedcontracts"`
|
|
}
|
|
|
|
type invokeAux struct {
|
|
State string `json:"state"`
|
|
GasConsumed int64 `json:"gasconsumed,string"`
|
|
Script []byte `json:"script"`
|
|
Stack json.RawMessage `json:"stack"`
|
|
FaultException *string `json:"exception"`
|
|
Notifications []state.NotificationEvent `json:"notifications"`
|
|
Transaction []byte `json:"tx,omitempty"`
|
|
Diagnostics *InvokeDiag `json:"diagnostics,omitempty"`
|
|
Session string `json:"session,omitempty"`
|
|
}
|
|
|
|
// iteratorInterfaceName is a string used to mark Iterator inside the InteropInterface.
|
|
const iteratorInterfaceName = "IIterator"
|
|
|
|
type iteratorAux struct {
|
|
Type string `json:"type"`
|
|
Interface string `json:"interface,omitempty"`
|
|
ID string `json:"id,omitempty"`
|
|
Value []json.RawMessage `json:"iterator,omitempty"`
|
|
Truncated bool `json:"truncated,omitempty"`
|
|
}
|
|
|
|
// Iterator represents VM iterator identifier. It either has ID set (for those JSON-RPC servers
|
|
// that support sessions) or non-nil Values and Truncated set (for those JSON-RPC servers that
|
|
// doesn't support sessions but perform in-place iterator traversing) or doesn't have ID, Values
|
|
// and Truncated set at all (for those JSON-RPC servers that doesn't support iterator sessions
|
|
// and doesn't perform in-place iterator traversing).
|
|
type Iterator struct {
|
|
// ID represents iterator ID. It is non-nil iff JSON-RPC server support session mechanism.
|
|
ID *uuid.UUID
|
|
|
|
// Values contains deserialized VM iterator values with a truncated flag. It is non-nil
|
|
// iff JSON-RPC server does not support sessions mechanism and able to traverse iterator.
|
|
Values []stackitem.Item
|
|
Truncated bool
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler.
|
|
func (r Iterator) MarshalJSON() ([]byte, error) {
|
|
var iaux iteratorAux
|
|
iaux.Type = stackitem.InteropT.String()
|
|
if r.ID != nil {
|
|
iaux.Interface = iteratorInterfaceName
|
|
iaux.ID = r.ID.String()
|
|
} else {
|
|
value := make([]json.RawMessage, len(r.Values))
|
|
for i := range r.Values {
|
|
var err error
|
|
value[i], err = stackitem.ToJSONWithTypes(r.Values[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
iaux.Value = value
|
|
iaux.Truncated = r.Truncated
|
|
}
|
|
return json.Marshal(iaux)
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler.
|
|
func (r *Iterator) UnmarshalJSON(data []byte) error {
|
|
iteratorAux := new(iteratorAux)
|
|
err := json.Unmarshal(data, iteratorAux)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(iteratorAux.Interface) != 0 {
|
|
if iteratorAux.Interface != iteratorInterfaceName {
|
|
return fmt.Errorf("unknown InteropInterface: %s", iteratorAux.Interface)
|
|
}
|
|
var iID uuid.UUID
|
|
iID, err = uuid.Parse(iteratorAux.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal iterator ID: %w", err)
|
|
}
|
|
r.ID = &iID
|
|
} else {
|
|
r.Values = make([]stackitem.Item, len(iteratorAux.Value))
|
|
for j := range r.Values {
|
|
r.Values[j], err = stackitem.FromJSONWithTypes(iteratorAux.Value[j])
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal iterator values: %w", err)
|
|
}
|
|
}
|
|
r.Truncated = iteratorAux.Truncated
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler.
|
|
func (r Invoke) MarshalJSON() ([]byte, error) {
|
|
var (
|
|
st json.RawMessage
|
|
err error
|
|
faultSep string
|
|
arr = make([]json.RawMessage, len(r.Stack))
|
|
)
|
|
if len(r.FaultException) != 0 {
|
|
faultSep = " / "
|
|
}
|
|
for i := range arr {
|
|
var data []byte
|
|
|
|
iter, ok := r.Stack[i].Value().(Iterator)
|
|
if (r.Stack[i].Type() == stackitem.InteropT) && ok {
|
|
data, err = json.Marshal(iter)
|
|
} else {
|
|
data, err = stackitem.ToJSONWithTypes(r.Stack[i])
|
|
}
|
|
if err != nil {
|
|
r.FaultException += fmt.Sprintf("%sjson error: %v", faultSep, err)
|
|
break
|
|
}
|
|
arr[i] = data
|
|
}
|
|
|
|
if err == nil {
|
|
st, err = json.Marshal(arr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var txbytes []byte
|
|
if r.Transaction != nil {
|
|
txbytes = r.Transaction.Bytes()
|
|
}
|
|
var sessionID string
|
|
if r.Session != (uuid.UUID{}) {
|
|
sessionID = r.Session.String()
|
|
}
|
|
aux := &invokeAux{
|
|
GasConsumed: r.GasConsumed,
|
|
Script: r.Script,
|
|
State: r.State,
|
|
Stack: st,
|
|
Notifications: r.Notifications,
|
|
Transaction: txbytes,
|
|
Diagnostics: r.Diagnostics,
|
|
Session: sessionID,
|
|
}
|
|
if len(r.FaultException) != 0 {
|
|
aux.FaultException = &r.FaultException
|
|
}
|
|
return json.Marshal(aux)
|
|
}
|
|
|
|
// UnmarshalJSON implements the json.Unmarshaler.
|
|
func (r *Invoke) UnmarshalJSON(data []byte) error {
|
|
var err error
|
|
aux := new(invokeAux)
|
|
if err = json.Unmarshal(data, aux); err != nil {
|
|
return err
|
|
}
|
|
if len(aux.Session) != 0 {
|
|
r.Session, err = uuid.Parse(aux.Session)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse session ID: %w", err)
|
|
}
|
|
}
|
|
var arr []json.RawMessage
|
|
if err = json.Unmarshal(aux.Stack, &arr); err == nil {
|
|
st := make([]stackitem.Item, len(arr))
|
|
for i := range arr {
|
|
st[i], err = stackitem.FromJSONWithTypes(arr[i])
|
|
if err != nil {
|
|
break
|
|
}
|
|
if st[i].Type() == stackitem.InteropT {
|
|
var iter = Iterator{}
|
|
err = json.Unmarshal(arr[i], &iter)
|
|
if err != nil {
|
|
break
|
|
}
|
|
st[i] = stackitem.NewInterop(iter)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal stack: %w", err)
|
|
}
|
|
r.Stack = st
|
|
}
|
|
var tx *transaction.Transaction
|
|
if len(aux.Transaction) != 0 {
|
|
tx, err = transaction.NewTransactionFromBytes(aux.Transaction)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
r.GasConsumed = aux.GasConsumed
|
|
r.Script = aux.Script
|
|
r.State = aux.State
|
|
if aux.FaultException != nil {
|
|
r.FaultException = *aux.FaultException
|
|
}
|
|
r.Notifications = aux.Notifications
|
|
r.Transaction = tx
|
|
r.Diagnostics = aux.Diagnostics
|
|
return nil
|
|
}
|
|
|
|
// AppExecToInvocation converts state.AppExecResult to result.Invoke and can be used
|
|
// as a wrapper for actor.Wait. The result of AppExecToInvocation doesn't have all fields
|
|
// properly filled, it's limited by State, GasConsumed, Stack, FaultException and Notifications.
|
|
// The result of AppExecToInvocation can be passed to unwrap package helpers.
|
|
func AppExecToInvocation(aer *state.AppExecResult, err error) (*Invoke, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Invoke{
|
|
State: aer.VMState.String(),
|
|
GasConsumed: aer.GasConsumed,
|
|
Stack: aer.Stack,
|
|
FaultException: aer.FaultException,
|
|
Notifications: aer.Events,
|
|
}, nil
|
|
}
|