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  }