rpc: allow to marshal Iterators for invoke* results

This commit is contained in:
Anna Shaleva 2021-04-28 17:46:34 +03:00
parent 4e55b1a9ed
commit 9eeebf481c
5 changed files with 108 additions and 21 deletions

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/rpc"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -49,6 +50,9 @@ func LoadFile(configPath string) (Config, error) {
ApplicationConfiguration: ApplicationConfiguration{ ApplicationConfiguration: ApplicationConfiguration{
PingInterval: 30, PingInterval: 30,
PingTimeout: 90, PingTimeout: 90,
RPC: rpc.Config{
MaxIteratorResultItems: 100,
},
}, },
} }

View file

@ -2,20 +2,35 @@ package result
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
// Invoke represents code invocation result and is used by several RPC calls // Invoke represents code invocation result and is used by several RPC calls
// that invoke functions, scripts and generic bytecode. // that invoke functions, scripts and generic bytecode.
type Invoke struct { type Invoke struct {
State string State string
GasConsumed int64 GasConsumed int64
Script []byte Script []byte
Stack []stackitem.Item Stack []stackitem.Item
FaultException string FaultException string
Transaction *transaction.Transaction Transaction *transaction.Transaction
maxIteratorResultItems int
}
// NewInvoke returns new Invoke structure with the given fields set.
func NewInvoke(vm *vm.VM, script []byte, faultException string, maxIteratorResultItems int) *Invoke {
return &Invoke{
State: vm.State().String(),
GasConsumed: vm.GasConsumed(),
Script: script,
Stack: vm.Estack().ToArray(),
FaultException: faultException,
maxIteratorResultItems: maxIteratorResultItems,
}
} }
type invokeAux struct { type invokeAux struct {
@ -27,15 +42,51 @@ type invokeAux struct {
Transaction []byte `json:"tx,omitempty"` Transaction []byte `json:"tx,omitempty"`
} }
type iteratorAux struct {
Type string `json:"type"`
Value []json.RawMessage `json:"iterator"`
Truncated bool `json:"truncated"`
}
// Iterator represents deserialized VM iterator values with truncated flag.
type Iterator struct {
Values []stackitem.Item
Truncated bool
}
// MarshalJSON implements json.Marshaler. // MarshalJSON implements json.Marshaler.
func (r Invoke) MarshalJSON() ([]byte, error) { func (r Invoke) MarshalJSON() ([]byte, error) {
var st json.RawMessage var st json.RawMessage
arr := make([]json.RawMessage, len(r.Stack)) arr := make([]json.RawMessage, len(r.Stack))
for i := range arr { for i := range arr {
data, err := stackitem.ToJSONWithTypes(r.Stack[i]) var (
if err != nil { data []byte
st = []byte(`"error: recursive reference"`) err error
break )
if (r.Stack[i].Type() == stackitem.InteropT) && vm.IsIterator(r.Stack[i]) {
iteratorValues, truncated := vm.IteratorValues(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 {
st = []byte(`"error: recursive reference"`)
break
}
}
data, err = json.Marshal(iteratorAux{
Type: stackitem.InteropT.String(),
Value: value,
Truncated: truncated,
})
if err != nil {
return nil, fmt.Errorf("failed to marshal iterator: %w", err)
}
} else {
data, err = stackitem.ToJSONWithTypes(r.Stack[i])
if err != nil {
st = []byte(`"error: recursive reference"`)
break
}
} }
arr[i] = data arr[i] = data
} }
@ -76,6 +127,26 @@ func (r *Invoke) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
break break
} }
if st[i].Type() == stackitem.InteropT {
iteratorAux := new(iteratorAux)
if json.Unmarshal(arr[i], iteratorAux) == nil {
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 of the VM, thus let's replace it with a special structure.
st[i] = stackitem.NewInterop(Iterator{
Values: iteratorValues,
Truncated: iteratorAux.Truncated,
})
}
}
} }
if err == nil { if err == nil {
r.Stack = st r.Stack = st

View file

@ -12,9 +12,10 @@ type (
EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"` EnableCORSWorkaround bool `yaml:"EnableCORSWorkaround"`
// MaxGasInvoke is a maximum amount of gas which // MaxGasInvoke is a maximum amount of gas which
// can be spent during RPC call. // can be spent during RPC call.
MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"` MaxGasInvoke fixedn.Fixed8 `yaml:"MaxGasInvoke"`
Port uint16 `yaml:"Port"` MaxIteratorResultItems int `yaml:"MaxIteratorResultItems"`
TLSConfig TLSConfig `yaml:"TLSConfig"` Port uint16 `yaml:"Port"`
TLSConfig TLSConfig `yaml:"TLSConfig"`
} }
// TLSConfig describes SSL/TLS configuration. // TLSConfig describes SSL/TLS configuration.

View file

@ -1320,14 +1320,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
if err != nil { if err != nil {
faultException = err.Error() faultException = err.Error()
} }
result := &result.Invoke{ return result.NewInvoke(vm, script, faultException, s.config.MaxIteratorResultItems), nil
State: vm.State().String(),
GasConsumed: vm.GasConsumed(),
Script: script,
Stack: vm.Estack().ToArray(),
FaultException: faultException,
}
return result, nil
} }
// submitBlock broadcasts a raw block over the NEO network. // submitBlock broadcasts a raw block over the NEO network.

View file

@ -71,6 +71,12 @@ func init() {
}) })
} }
// IsIterator returns whether stackitem implements iterator interface.
func IsIterator(item stackitem.Item) bool {
_, ok := item.Value().(iterator)
return ok
}
// IteratorNext handles syscall System.Enumerator.Next. // IteratorNext handles syscall System.Enumerator.Next.
func IteratorNext(v *VM) error { func IteratorNext(v *VM) error {
iop := v.Estack().Pop().Interop() iop := v.Estack().Pop().Interop()
@ -89,6 +95,18 @@ func IteratorValue(v *VM) error {
return nil return nil
} }
// IteratorValues returns an array of up to `max` iterator values. The second
// return parameter denotes whether iterator is truncated.
func IteratorValues(item stackitem.Item, max int) ([]stackitem.Item, bool) {
var result []stackitem.Item
arr := item.Value().(iterator)
for arr.Next() && max > 0 {
result = append(result, arr.Value())
max--
}
return result, arr.Next()
}
// NewIterator creates new iterator from the provided stack item. // NewIterator creates new iterator from the provided stack item.
func NewIterator(item stackitem.Item) (stackitem.Item, error) { func NewIterator(item stackitem.Item) (stackitem.Item, error) {
switch t := item.(type) { switch t := item.(type) {