Skip to content

Commit

Permalink
rpc: use state.AppExecResult for ApplicationLog marshalling
Browse files Browse the repository at this point in the history
Closes #1371
  • Loading branch information
AnnaShaleva committed Sep 7, 2020
1 parent 34df5d5 commit acacac1
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 136 deletions.
124 changes: 121 additions & 3 deletions pkg/core/state/notification_event.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package state

import (
"encoding/json"
"errors"
"fmt"

"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
Expand All @@ -13,9 +15,9 @@ import (
// NotificationEvent is a tuple of scripthash that emitted the Item as a
// notification and that item itself.
type NotificationEvent struct {
ScriptHash util.Uint160
Name string
Item *stackitem.Array
ScriptHash util.Uint160 `json:"contract"`
Name string `json:"eventname"`
Item *stackitem.Array `json:"state"`
}

// AppExecResult represent the result of the script execution, gathering together
Expand Down Expand Up @@ -79,3 +81,119 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
}
r.ReadArray(&aer.Events)
}

// notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling.
type notificationEventAux struct {
ScriptHash util.Uint160 `json:"contract"`
Name string `json:"eventname"`
Item json.RawMessage `json:"state"`
}

// MarshalJSON implements implements json.Marshaler interface.
func (ne *NotificationEvent) MarshalJSON() ([]byte, error) {
item, err := stackitem.ToJSONWithTypes(ne.Item)
if err != nil {
item = []byte(`"error: recursive reference"`)
}
return json.Marshal(&notificationEventAux{
ScriptHash: ne.ScriptHash,
Name: ne.Name,
Item: item,
})
}

// UnmarshalJSON implements json.Unmarshaler interface.
func (ne *NotificationEvent) UnmarshalJSON(data []byte) error {
aux := new(notificationEventAux)
if err := json.Unmarshal(data, aux); err != nil {
return err
}
item, err := stackitem.FromJSONWithTypes(aux.Item)
if err != nil {
return err
}
if t := item.Type(); t != stackitem.ArrayT {
return fmt.Errorf("failed to convert notification event state of type %s to array", t.String())
}
ne.Item = item.(*stackitem.Array)
ne.Name = aux.Name
ne.ScriptHash = aux.ScriptHash
return nil
}

// appExecResultAux is an auxiliary struct for JSON marshalling
type appExecResultAux struct {
TxHash util.Uint256 `json:"txid"`
Trigger string `json:"trigger"`
VMState string `json:"vmstate"`
GasConsumed int64 `json:"gasconsumed,string"`
Stack json.RawMessage `json:"stack"`
Events []NotificationEvent `json:"notifications"`
}

// MarshalJSON implements implements json.Marshaler interface.
func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
var st json.RawMessage
arr := make([]json.RawMessage, len(aer.Stack))
for i := range arr {
data, err := stackitem.ToJSONWithTypes(aer.Stack[i])
if err != nil {
st = []byte(`"error: recursive reference"`)
break
}
arr[i] = data
}

var err error
if st == nil {
st, err = json.Marshal(arr)
if err != nil {
return nil, err
}
}
return json.Marshal(&appExecResultAux{
TxHash: aer.TxHash,
Trigger: aer.Trigger.String(),
VMState: aer.VMState.String(),
GasConsumed: aer.GasConsumed,
Stack: st,
Events: aer.Events,
})
}

// UnmarshalJSON implements implements json.Unmarshaler interface.
func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
aux := new(appExecResultAux)
if err := json.Unmarshal(data, aux); err != nil {
return 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 err == nil {
aer.Stack = st
}
}

trigger, err := trigger.FromString(aux.Trigger)
if err != nil {
return err
}
aer.Trigger = trigger
aer.TxHash = aux.TxHash
state, err := vm.StateFromString(aux.VMState)
if err != nil {
return err
}
aer.VMState = state
aer.Events = aux.Events
aer.GasConsumed = aux.GasConsumed

return nil
}
91 changes: 91 additions & 0 deletions pkg/core/state/notification_event_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package state

import (
"encoding/json"
"testing"

"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)

func TestEncodeDecodeNotificationEvent(t *testing.T) {
Expand All @@ -31,3 +33,92 @@ func TestEncodeDecodeAppExecResult(t *testing.T) {

testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
}

func TestMarshalUnmarshalJSONNotificationEvent(t *testing.T) {
t.Run("positive", func(t *testing.T) {
ne := &NotificationEvent{
ScriptHash: random.Uint160(),
Name: "my_ne",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewBool(true),
}),
}
testserdes.MarshalUnmarshalJSON(t, ne, new(NotificationEvent))
})

t.Run("MarshalJSON recursive reference", func(t *testing.T) {
i := make([]stackitem.Item, 1)
recursive := stackitem.NewArray(i)
i[0] = recursive
ne := &NotificationEvent{
Item: recursive,
}
_, err := json.Marshal(ne)
require.NoError(t, err)
})

t.Run("UnmarshalJSON error", func(t *testing.T) {
errorCases := []string{
`{"contract":"0xBadHash","eventname":"my_ne","state":{"type":"Array","value":[{"type":"Boolean","value":true}]}}`,
`{"contract":"0xab2f820e2aa7cca1e081283c58a7d7943c33a2f1","eventname":"my_ne","state":{"type":"Array","value":[{"type":"BadType","value":true}]}}`,
`{"contract":"0xab2f820e2aa7cca1e081283c58a7d7943c33a2f1","eventname":"my_ne","state":{"type":"Boolean", "value":true}}`,
}
for _, errCase := range errorCases {
err := json.Unmarshal([]byte(errCase), new(NotificationEvent))
require.Error(t, err)
}

})
}

func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
t.Run("positive", func(t *testing.T) {
appExecResult := &AppExecResult{
TxHash: random.Uint256(),
Trigger: 1,
VMState: vm.HaltState,
GasConsumed: 10,
Stack: []stackitem.Item{},
Events: []NotificationEvent{},
}
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
})

t.Run("MarshalJSON recursive reference", func(t *testing.T) {
i := make([]stackitem.Item, 1)
recursive := stackitem.NewArray(i)
i[0] = recursive
errorCases := []*AppExecResult{
{
Stack: i,
},
}
for _, errCase := range errorCases {
_, err := json.Marshal(errCase)
require.NoError(t, err)
}
})

t.Run("UnmarshalJSON error", func(t *testing.T) {
nilStackCases := []string{
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"WrongType","value":"1"}],"notifications":[]}`,
}
for _, str := range nilStackCases {
actual := new(AppExecResult)
err := json.Unmarshal([]byte(str), actual)
require.NoError(t, err)
require.Nil(t, actual.Stack)
}

errorCases := []string{
`{"txid":"0xBadHash","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"BadState","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"BadTrigger","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
}
for _, str := range errorCases {
actual := new(AppExecResult)
err := json.Unmarshal([]byte(str), actual)
require.Error(t, err)
}
})
}
4 changes: 2 additions & 2 deletions pkg/rpc/client/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
)

// GetApplicationLog returns the contract log based on the specified txid.
func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) {
func (c *Client) GetApplicationLog(hash util.Uint256) (*state.AppExecResult, error) {
var (
params = request.NewRawParams(hash.StringLE())
resp = &result.ApplicationLog{}
resp = new(state.AppExecResult)
)
if err := c.performRequest("getapplicationlog", params, resp); err != nil {
return nil, err
Expand Down
10 changes: 6 additions & 4 deletions pkg/rpc/client/rpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -113,13 +115,13 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
if err != nil {
panic(err)
}
return &result.ApplicationLog{
return &state.AppExecResult{
TxHash: txHash,
Trigger: "Application",
VMState: "HALT",
Trigger: trigger.Application,
VMState: vm.HaltState,
GasConsumed: 1,
Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))},
Events: []result.NotificationEvent{},
Events: []state.NotificationEvent{},
}
},
},
Expand Down
6 changes: 3 additions & 3 deletions pkg/rpc/client/wsclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (

"github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/util"
)

Expand Down Expand Up @@ -141,9 +141,9 @@ readloop:
case response.TransactionEventID:
val = &transaction.Transaction{Network: c.opts.Network}
case response.NotificationEventID:
val = new(result.NotificationEvent)
val = new(state.NotificationEvent)
case response.ExecutionEventID:
val = new(result.ApplicationLog)
val = new(state.AppExecResult)
case response.MissedEventID:
// No value.
default:
Expand Down
Loading

0 comments on commit acacac1

Please sign in to comment.