2020-01-13 09:27:34 +00:00
package result
2019-10-29 15:31:39 +00:00
import (
2020-07-31 12:26:28 +00:00
"encoding/json"
2021-04-28 14:46:34 +00:00
"fmt"
2020-07-31 12:26:28 +00:00
2022-06-15 18:23:29 +00:00
"github.com/google/uuid"
2022-01-18 19:35:44 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop"
2021-04-29 14:19:30 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
2022-01-18 19:35:44 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/state"
2022-01-18 21:02:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/storage"
2020-09-22 09:03:19 +00:00
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
2021-04-28 14:46:34 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm"
2020-07-31 12:26:28 +00:00
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
2019-10-29 15:31:39 +00:00
)
2022-04-20 18:30:09 +00:00
// Invoke represents a code invocation result and is used by several RPC calls
2021-03-25 16:18:01 +00:00
// that invoke functions, scripts and generic bytecode.
2020-01-13 09:27:34 +00:00
type Invoke struct {
2021-04-28 14:46:34 +00:00
State string
GasConsumed int64
Script [ ] byte
Stack [ ] stackitem . Item
FaultException string
2022-01-18 19:35:44 +00:00
Notifications [ ] state . NotificationEvent
2021-04-28 14:46:34 +00:00
Transaction * transaction . Transaction
2021-11-20 18:55:55 +00:00
Diagnostics * InvokeDiag
2021-04-28 14:46:34 +00:00
maxIteratorResultItems int
2022-06-15 18:23:29 +00:00
Session uuid . UUID
2021-10-07 11:27:55 +00:00
finalize func ( )
2022-07-07 19:03:11 +00:00
registerIterator RegisterIterator
2021-04-28 14:46:34 +00:00
}
2022-07-07 19:03:11 +00:00
// RegisterIterator is a callback used to register new iterator on the server side.
2022-07-08 12:42:27 +00:00
type RegisterIterator func ( sessionID string , item stackitem . Item , id int , finalize func ( ) ) ( uuid . UUID , error )
2022-06-15 18:23:29 +00:00
2021-11-20 18:55:55 +00:00
// InvokeDiag is an additional diagnostic data for invocation.
type InvokeDiag struct {
2022-01-18 21:02:19 +00:00
Changes [ ] storage . Operation ` json:"storagechanges" `
2021-11-20 18:55:55 +00:00
Invocations [ ] * vm . InvocationTree ` json:"invokedcontracts" `
}
2022-04-20 18:30:09 +00:00
// NewInvoke returns a new Invoke structure with the given fields set.
2022-07-07 19:03:11 +00:00
func NewInvoke ( ic * interop . Context , script [ ] byte , faultException string , registerIterator RegisterIterator , maxIteratorResultItems int ) * Invoke {
2021-11-20 18:55:55 +00:00
var diag * InvokeDiag
2022-01-18 19:35:44 +00:00
tree := ic . VM . GetInvocationTree ( )
2021-11-20 18:55:55 +00:00
if tree != nil {
2022-01-18 21:02:19 +00:00
diag = & InvokeDiag {
Invocations : tree . Calls ,
Changes : storage . BatchToOperations ( ic . DAO . GetBatch ( ) ) ,
}
2021-11-20 18:55:55 +00:00
}
2022-01-18 19:35:44 +00:00
notifications := ic . Notifications
if notifications == nil {
notifications = make ( [ ] state . NotificationEvent , 0 )
}
2021-04-28 14:46:34 +00:00
return & Invoke {
2022-01-18 19:35:44 +00:00
State : ic . VM . State ( ) . String ( ) ,
GasConsumed : ic . VM . GasConsumed ( ) ,
2021-04-28 14:46:34 +00:00
Script : script ,
2022-01-18 19:35:44 +00:00
Stack : ic . VM . Estack ( ) . ToArray ( ) ,
2021-04-28 14:46:34 +00:00
FaultException : faultException ,
2022-01-18 19:35:44 +00:00
Notifications : notifications ,
2021-11-20 18:55:55 +00:00
Diagnostics : diag ,
2022-01-18 19:35:44 +00:00
finalize : ic . Finalize ,
2022-06-15 18:23:29 +00:00
maxIteratorResultItems : maxIteratorResultItems ,
2022-07-07 19:03:11 +00:00
registerIterator : registerIterator ,
2021-04-28 14:46:34 +00:00
}
2020-07-31 12:26:28 +00:00
}
type invokeAux struct {
2022-01-18 19:35:44 +00:00
State string ` json:"state" `
GasConsumed int64 ` json:"gasconsumed,string" `
Script [ ] byte ` json:"script" `
Stack json . RawMessage ` json:"stack" `
2022-05-25 07:48:58 +00:00
FaultException * string ` json:"exception" `
2022-01-18 19:35:44 +00:00
Notifications [ ] state . NotificationEvent ` json:"notifications" `
Transaction [ ] byte ` json:"tx,omitempty" `
Diagnostics * InvokeDiag ` json:"diagnostics,omitempty" `
2022-06-15 18:23:29 +00:00
Session string ` json:"session,omitempty" `
2020-07-31 12:26:28 +00:00
}
2022-06-15 18:23:29 +00:00
// iteratorInterfaceName is a string used to mark Iterator inside the InteropInterface.
const iteratorInterfaceName = "IIterator"
2021-04-28 14:46:34 +00:00
type iteratorAux struct {
Type string ` json:"type" `
2022-06-15 18:23:29 +00:00
Interface string ` json:"interface,omitempty" `
ID string ` json:"id,omitempty" `
Value [ ] json . RawMessage ` json:"iterator,omitempty" `
Truncated bool ` json:"truncated,omitempty" `
2021-04-28 14:46:34 +00:00
}
2022-06-15 18:23:29 +00:00
// 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).
2021-04-28 14:46:34 +00:00
type Iterator struct {
2022-06-15 18:23:29 +00:00
// 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.
2021-04-28 14:46:34 +00:00
Values [ ] stackitem . Item
Truncated bool
}
2021-10-07 11:27:55 +00:00
// Finalize releases resources occupied by Iterators created at the script invocation.
2022-06-15 18:23:29 +00:00
// This method will be called automatically on Invoke marshalling or by the Server's
// sessions handler.
2021-10-07 11:27:55 +00:00
func ( r * Invoke ) Finalize ( ) {
if r . finalize != nil {
r . finalize ( )
}
}
2022-04-20 18:30:09 +00:00
// MarshalJSON implements the json.Marshaler.
2020-07-31 12:26:28 +00:00
func ( r Invoke ) MarshalJSON ( ) ( [ ] byte , error ) {
2022-05-04 10:13:21 +00:00
var (
2022-06-15 18:23:29 +00:00
st json . RawMessage
err error
faultSep string
arr = make ( [ ] json . RawMessage , len ( r . Stack ) )
2022-07-07 19:03:11 +00:00
sessionsEnabled = r . registerIterator != nil
2022-06-15 18:23:29 +00:00
sessionID string
2022-05-04 10:13:21 +00:00
)
if len ( r . FaultException ) != 0 {
faultSep = " / "
}
arrloop :
2020-07-31 12:26:28 +00:00
for i := range arr {
2022-05-04 10:13:21 +00:00
var data [ ] byte
2021-04-29 14:19:30 +00:00
if ( r . Stack [ i ] . Type ( ) == stackitem . InteropT ) && iterator . IsIterator ( r . Stack [ i ] ) {
2022-06-15 18:23:29 +00:00
if sessionsEnabled {
2022-07-07 19:03:11 +00:00
if sessionID == "" {
sessionID = uuid . NewString ( )
}
2022-07-08 12:42:27 +00:00
iteratorID , err := r . registerIterator ( sessionID , r . Stack [ i ] , i , r . finalize )
if err != nil {
// Call finalizer immediately, there can't be race between server and marshaller because session wasn't added to server's session pool.
r . Finalize ( )
return nil , fmt . Errorf ( "failed to register iterator session: %w" , err )
}
2022-06-15 18:23:29 +00:00
data , err = json . Marshal ( iteratorAux {
Type : stackitem . InteropT . String ( ) ,
Interface : iteratorInterfaceName ,
2022-07-07 19:03:11 +00:00
ID : iteratorID . String ( ) ,
2022-06-15 18:23:29 +00:00
} )
if err != nil {
r . FaultException += fmt . Sprintf ( "%sjson error: failed to marshal iterator: %v" , faultSep , err )
break
}
} else {
iteratorValues , truncated := iterator . ValuesTruncated ( r . Stack [ i ] , r . maxIteratorResultItems )
value := make ( [ ] json . RawMessage , len ( iteratorValues ) )
for j := range iteratorValues {
value [ j ] , err = stackitem . ToJSONWithTypes ( iteratorValues [ j ] )
if err != nil {
r . FaultException += fmt . Sprintf ( "%sjson error: %v" , faultSep , err )
break arrloop
}
}
data , err = json . Marshal ( iteratorAux {
Type : stackitem . InteropT . String ( ) ,
Value : value ,
Truncated : truncated ,
} )
2021-04-28 14:46:34 +00:00
if err != nil {
2022-05-04 10:13:21 +00:00
r . FaultException += fmt . Sprintf ( "%sjson error: %v" , faultSep , err )
2022-06-15 18:23:29 +00:00
break
2021-04-28 14:46:34 +00:00
}
}
} else {
data , err = stackitem . ToJSONWithTypes ( r . Stack [ i ] )
if err != nil {
2022-05-04 10:13:21 +00:00
r . FaultException += fmt . Sprintf ( "%sjson error: %v" , faultSep , err )
2021-04-28 14:46:34 +00:00
break
}
2020-07-31 12:26:28 +00:00
}
arr [ i ] = data
}
2022-07-07 19:03:11 +00:00
if ! sessionsEnabled || sessionID == "" {
// Call finalizer manually if iterators are disabled or there's no unnested iterators on estack.
2022-06-15 18:23:29 +00:00
defer r . Finalize ( )
}
2022-05-04 10:13:21 +00:00
if err == nil {
2020-07-31 12:26:28 +00:00
st , err = json . Marshal ( arr )
if err != nil {
return nil , err
}
}
2021-03-25 16:18:01 +00:00
var txbytes [ ] byte
if r . Transaction != nil {
txbytes = r . Transaction . Bytes ( )
}
2022-05-25 07:48:58 +00:00
aux := & invokeAux {
GasConsumed : r . GasConsumed ,
Script : r . Script ,
State : r . State ,
Stack : st ,
Notifications : r . Notifications ,
Transaction : txbytes ,
Diagnostics : r . Diagnostics ,
2022-06-15 18:23:29 +00:00
Session : sessionID ,
2022-05-25 07:48:58 +00:00
}
if len ( r . FaultException ) != 0 {
aux . FaultException = & r . FaultException
}
return json . Marshal ( aux )
2020-07-31 12:26:28 +00:00
}
2022-04-20 18:30:09 +00:00
// UnmarshalJSON implements the json.Unmarshaler.
2020-07-31 12:26:28 +00:00
func ( r * Invoke ) UnmarshalJSON ( data [ ] byte ) error {
2021-03-25 16:18:01 +00:00
var err error
2020-07-31 12:26:28 +00:00
aux := new ( invokeAux )
2021-03-25 16:18:01 +00:00
if err = json . Unmarshal ( data , aux ) ; err != nil {
2020-07-31 12:26:28 +00:00
return err
}
2022-06-15 18:23:29 +00:00
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 )
}
}
2020-07-31 12:26:28 +00:00
var arr [ ] json . RawMessage
2021-03-25 16:18:01 +00:00
if err = json . Unmarshal ( aux . Stack , & arr ) ; err == nil {
2020-07-31 12:26:28 +00:00
st := make ( [ ] stackitem . Item , len ( arr ) )
for i := range arr {
st [ i ] , err = stackitem . FromJSONWithTypes ( arr [ i ] )
if err != nil {
break
}
2021-04-28 14:46:34 +00:00
if st [ i ] . Type ( ) == stackitem . InteropT {
iteratorAux := new ( iteratorAux )
if json . Unmarshal ( arr [ i ] , iteratorAux ) == nil {
2022-06-15 18:23:29 +00:00
if len ( iteratorAux . Interface ) != 0 {
if iteratorAux . Interface != iteratorInterfaceName {
err = fmt . Errorf ( "unknown InteropInterface: %s" , iteratorAux . Interface )
break
}
var iID uuid . UUID
iID , err = uuid . Parse ( iteratorAux . ID ) // iteratorAux.ID is always non-empty, see https://github.com/neo-project/neo-modules/pull/715#discussion_r897635424.
2021-04-28 14:46:34 +00:00
if err != nil {
2022-06-15 18:23:29 +00:00
err = fmt . Errorf ( "failed to unmarshal iterator ID: %w" , err )
2021-04-28 14:46:34 +00:00
break
}
2022-06-15 18:23:29 +00:00
// It's impossible to restore initial iterator type; also iterator is almost
// useless outside the VM, thus let's replace it with a special structure.
st [ i ] = stackitem . NewInterop ( Iterator {
ID : & iID ,
} )
} else {
iteratorValues := make ( [ ] stackitem . Item , len ( iteratorAux . Value ) )
for j := range iteratorValues {
iteratorValues [ j ] , err = stackitem . FromJSONWithTypes ( iteratorAux . Value [ j ] )
if err != nil {
err = fmt . Errorf ( "failed to unmarshal iterator values: %w" , err )
break
}
}
// It's impossible to restore initial iterator type; also iterator is almost
// useless outside the VM, thus let's replace it with a special structure.
st [ i ] = stackitem . NewInterop ( Iterator {
Values : iteratorValues ,
Truncated : iteratorAux . Truncated ,
} )
2021-04-28 14:46:34 +00:00
}
}
}
2020-07-31 12:26:28 +00:00
}
if err == nil {
r . Stack = st
}
}
2021-03-25 16:18:01 +00:00
var tx * transaction . Transaction
if len ( aux . Transaction ) != 0 {
tx , err = transaction . NewTransactionFromBytes ( aux . Transaction )
if err != nil {
return err
}
}
2021-02-09 08:16:18 +00:00
r . GasConsumed = aux . GasConsumed
2020-07-31 12:26:28 +00:00
r . Script = aux . Script
r . State = aux . State
2022-05-25 07:48:58 +00:00
if aux . FaultException != nil {
r . FaultException = * aux . FaultException
}
2022-01-18 19:35:44 +00:00
r . Notifications = aux . Notifications
2021-03-25 16:18:01 +00:00
r . Transaction = tx
2021-11-20 18:55:55 +00:00
r . Diagnostics = aux . Diagnostics
2020-07-31 12:26:28 +00:00
return nil
2019-10-29 15:31:39 +00:00
}