rpc: allow to marshal Iterators for invoke* results
This commit is contained in:
parent
4e55b1a9ed
commit
9eeebf481c
5 changed files with 108 additions and 21 deletions
|
@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue