*: make Notify interop accept event name

This commit is contained in:
Evgenii Stratonikov 2020-06-29 11:25:32 +03:00
parent 1154e180fa
commit 3d7fa9de93
17 changed files with 84 additions and 43 deletions

View file

@ -7,16 +7,16 @@ import (
// Main is that famous Main() function, you know. // Main is that famous Main() function, you know.
func Main() bool { func Main() bool {
tx := runtime.GetScriptContainer() tx := runtime.GetScriptContainer()
runtime.Notify(tx.Hash) runtime.Notify("Tx", tx.Hash)
callingScriptHash := runtime.GetCallingScriptHash() callingScriptHash := runtime.GetCallingScriptHash()
runtime.Notify(callingScriptHash) runtime.Notify("Calling", callingScriptHash)
execScriptHash := runtime.GetExecutingScriptHash() execScriptHash := runtime.GetExecutingScriptHash()
runtime.Notify(execScriptHash) runtime.Notify("Executing", execScriptHash)
entryScriptHash := runtime.GetEntryScriptHash() entryScriptHash := runtime.GetEntryScriptHash()
runtime.Notify(entryScriptHash) runtime.Notify("Entry", entryScriptHash)
return true return true
} }

View file

@ -54,6 +54,6 @@ func Log(args []interface{}) bool {
// Notify notifies about given message // Notify notifies about given message
func Notify(args []interface{}) bool { func Notify(args []interface{}) bool {
runtime.Notify(args[0]) runtime.Notify("Event", args[0])
return true return true
} }

View file

@ -30,8 +30,7 @@ func TestNotify(t *testing.T) {
src := `package foo src := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
func Main(arg int) { func Main(arg int) {
runtime.Notify(arg, "sum", arg+1) runtime.Notify("Event1", arg, "sum", arg+1)
runtime.Notify()
runtime.Notify("single") runtime.Notify("single")
}` }`
@ -39,10 +38,11 @@ func TestNotify(t *testing.T) {
v.Estack().PushVal(11) v.Estack().PushVal(11)
require.NoError(t, v.Run()) require.NoError(t, v.Run())
require.Equal(t, 3, len(s.events)) require.Equal(t, 2, len(s.events))
exp0 := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(11)), stackitem.NewByteArray([]byte("sum")), stackitem.NewBigInteger(big.NewInt(12))} exp0 := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(11)), stackitem.NewByteArray([]byte("sum")), stackitem.NewBigInteger(big.NewInt(12))}
assert.Equal(t, exp0, s.events[0].Value()) assert.Equal(t, "Event1", s.events[0].Name)
assert.Equal(t, []stackitem.Item{}, s.events[1].Value()) assert.Equal(t, exp0, s.events[0].Item.Value())
assert.Equal(t, []stackitem.Item{stackitem.NewByteArray([]byte("single"))}, s.events[2].Value()) assert.Equal(t, "single", s.events[1].Name)
assert.Equal(t, []stackitem.Item{}, s.events[1].Item.Value())
} }

View file

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/compiler"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -74,7 +75,7 @@ func vmAndCompileInterop(t *testing.T, src string) (*vm.VM, *storagePlugin) {
type storagePlugin struct { type storagePlugin struct {
mem map[string][]byte mem map[string][]byte
interops map[uint32]vm.InteropFunc interops map[uint32]vm.InteropFunc
events []stackitem.Item events []state.NotificationEvent
} }
func newStoragePlugin() *storagePlugin { func newStoragePlugin() *storagePlugin {
@ -99,7 +100,12 @@ func (s *storagePlugin) getInterop(id uint32) *vm.InteropFuncPrice {
} }
func (s *storagePlugin) Notify(v *vm.VM) error { func (s *storagePlugin) Notify(v *vm.VM) error {
s.events = append(s.events, v.Estack().Pop().Item()) name := string(v.Estack().Pop().Bytes())
item := stackitem.NewArray(v.Estack().Pop().Array())
s.events = append(s.events, state.NotificationEvent{
Name: name,
Item: item,
})
return nil return nil
} }

View file

@ -661,16 +661,15 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
} }
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) { func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
arr, ok := note.Item.Value().([]stackitem.Item) if note.Name != "transfer" && note.Name != "Transfer" {
if !ok || len(arr) != 4 {
return return
} }
op, ok := arr[0].Value().([]byte) arr, ok := note.Item.Value().([]stackitem.Item)
if !ok || (string(op) != "transfer" && string(op) != "Transfer") { if !ok || len(arr) != 3 {
return return
} }
var from []byte var from []byte
fromValue := arr[1].Value() fromValue := arr[0].Value()
// we don't have `from` set when we are minting tokens // we don't have `from` set when we are minting tokens
if fromValue != nil { if fromValue != nil {
from, ok = fromValue.([]byte) from, ok = fromValue.([]byte)
@ -679,7 +678,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
} }
} }
var to []byte var to []byte
toValue := arr[2].Value() toValue := arr[1].Value()
// we don't have `to` set when we are burning tokens // we don't have `to` set when we are burning tokens
if toValue != nil { if toValue != nil {
to, ok = toValue.([]byte) to, ok = toValue.([]byte)
@ -687,9 +686,9 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
return return
} }
} }
amount, ok := arr[3].Value().(*big.Int) amount, ok := arr[2].Value().(*big.Int)
if !ok { if !ok {
bs, ok := arr[3].Value().([]byte) bs, ok := arr[2].Value().([]byte)
if !ok { if !ok {
return return
} }

View file

@ -42,6 +42,7 @@ func GetNotifications(ic *interop.Context, v *vm.VM) error {
for i := range notifications { for i := range notifications {
ev := stackitem.NewArray([]stackitem.Item{ ev := stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(notifications[i].ScriptHash.BytesBE()), stackitem.NewByteArray(notifications[i].ScriptHash.BytesBE()),
stackitem.Make(notifications[i].Name),
notifications[i].Item, notifications[i].Item,
}) })
arr.Append(ev) arr.Append(ev)

View file

@ -27,6 +27,8 @@ const (
// MaxTraceableBlocks is the maximum number of blocks before current chain // MaxTraceableBlocks is the maximum number of blocks before current chain
// height we're able to give information about. // height we're able to give information about.
MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement MaxTraceableBlocks = transaction.MaxValidUntilBlockIncrement
// MaxEventNameLen is the maximum length of a name for event.
MaxEventNameLen = 32
) )
// StorageContext contains storing id and read/write flag, it's used as // StorageContext contains storing id and read/write flag, it's used as
@ -244,19 +246,26 @@ func runtimeGetTrigger(ic *interop.Context, v *vm.VM) error {
// runtimeNotify should pass stack item to the notify plugin to handle it, but // runtimeNotify should pass stack item to the notify plugin to handle it, but
// in neo-go the only meaningful thing to do here is to log. // in neo-go the only meaningful thing to do here is to log.
func runtimeNotify(ic *interop.Context, v *vm.VM) error { func runtimeNotify(ic *interop.Context, v *vm.VM) error {
// It can be just about anything. name := v.Estack().Pop().Bytes()
e := v.Estack().Pop() if len(name) > MaxEventNameLen {
item := e.Item() return fmt.Errorf("event name must be less than %d", MaxEventNameLen)
}
elem := v.Estack().Pop()
args := elem.Array()
// But it has to be serializable, otherwise we either have some broken // But it has to be serializable, otherwise we either have some broken
// (recursive) structure inside or an interop item that can't be used // (recursive) structure inside or an interop item that can't be used
// outside of the interop subsystem anyway. I'd probably fail transactions // outside of the interop subsystem anyway. I'd probably fail transactions
// that emit such broken notifications, but that might break compatibility // that emit such broken notifications, but that might break compatibility
// with testnet/mainnet, so we're replacing these with error messages. // with testnet/mainnet, so we're replacing these with error messages.
_, err := stackitem.SerializeItem(item) _, err := stackitem.SerializeItem(elem.Item())
if err != nil { if err != nil {
item = stackitem.NewByteArray([]byte(fmt.Sprintf("bad notification: %v", err))) args = []stackitem.Item{stackitem.NewByteArray([]byte(fmt.Sprintf("bad notification: %v", err)))}
}
ne := state.NotificationEvent{
ScriptHash: v.GetCurrentScriptHash(),
Name: string(name),
Item: stackitem.NewArray(args),
} }
ne := state.NotificationEvent{ScriptHash: v.GetCurrentScriptHash(), Item: item}
ic.Notifications = append(ic.Notifications, ne) ic.Notifications = append(ic.Notifications, ne)
return nil return nil
} }

View file

@ -247,9 +247,9 @@ func TestRuntimeGetNotifications(t *testing.T) {
defer chain.Close() defer chain.Close()
ic.Notifications = []state.NotificationEvent{ ic.Notifications = []state.NotificationEvent{
{ScriptHash: util.Uint160{1}, Item: stackitem.NewByteArray([]byte{11})}, {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})},
{ScriptHash: util.Uint160{2}, Item: stackitem.NewByteArray([]byte{22})}, {ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
{ScriptHash: util.Uint160{1}, Item: stackitem.NewByteArray([]byte{33})}, {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
} }
t.Run("NoFilter", func(t *testing.T) { t.Run("NoFilter", func(t *testing.T) {
@ -261,7 +261,10 @@ func TestRuntimeGetNotifications(t *testing.T) {
for i := range arr { for i := range arr {
elem := arr[i].Value().([]stackitem.Item) elem := arr[i].Value().([]stackitem.Item)
require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value()) require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value())
require.Equal(t, ic.Notifications[i].Item, elem[1]) name, err := elem[1].TryBytes()
require.NoError(t, err)
require.Equal(t, ic.Notifications[i].Name, string(name))
require.Equal(t, ic.Notifications[i].Item, elem[2])
} }
}) })
@ -274,7 +277,10 @@ func TestRuntimeGetNotifications(t *testing.T) {
require.Equal(t, 1, len(arr)) require.Equal(t, 1, len(arr))
elem := arr[0].Value().([]stackitem.Item) elem := arr[0].Value().([]stackitem.Item)
require.Equal(t, h, elem[0].Value()) require.Equal(t, h, elem[0].Value())
require.Equal(t, ic.Notifications[1].Item, elem[1]) name, err := elem[1].TryBytes()
require.NoError(t, err)
require.Equal(t, ic.Notifications[1].Name, string(name))
require.Equal(t, ic.Notifications[1].Item, elem[2])
}) })
} }

View file

@ -138,8 +138,8 @@ func addrToStackItem(u *util.Uint160) stackitem.Item {
func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) { func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
ne := state.NotificationEvent{ ne := state.NotificationEvent{
ScriptHash: c.Hash, ScriptHash: c.Hash,
Name: "Transfer",
Item: stackitem.NewArray([]stackitem.Item{ Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("Transfer")),
addrToStackItem(from), addrToStackItem(from),
addrToStackItem(to), addrToStackItem(to),
stackitem.NewBigInteger(amount), stackitem.NewBigInteger(amount),

View file

@ -1,6 +1,8 @@
package state package state
import ( import (
"errors"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
@ -12,7 +14,8 @@ import (
// notification and that item itself. // notification and that item itself.
type NotificationEvent struct { type NotificationEvent struct {
ScriptHash util.Uint160 ScriptHash util.Uint160
Item stackitem.Item Name string
Item *stackitem.Array
} }
// AppExecResult represent the result of the script execution, gathering together // AppExecResult represent the result of the script execution, gathering together
@ -29,13 +32,24 @@ type AppExecResult struct {
// EncodeBinary implements the Serializable interface. // EncodeBinary implements the Serializable interface.
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) { func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
ne.ScriptHash.EncodeBinary(w) ne.ScriptHash.EncodeBinary(w)
w.WriteString(ne.Name)
stackitem.EncodeBinaryStackItem(ne.Item, w) stackitem.EncodeBinaryStackItem(ne.Item, w)
} }
// DecodeBinary implements the Serializable interface. // DecodeBinary implements the Serializable interface.
func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
ne.ScriptHash.DecodeBinary(r) ne.ScriptHash.DecodeBinary(r)
ne.Item = stackitem.DecodeBinaryStackItem(r) ne.Name = r.ReadString()
item := stackitem.DecodeBinaryStackItem(r)
if r.Err != nil {
return
}
arr, ok := item.Value().([]stackitem.Item)
if !ok {
r.Err = errors.New("Array or Struct expected")
return
}
ne.Item = stackitem.NewArray(arr)
} }
// EncodeBinary implements the Serializable interface. // EncodeBinary implements the Serializable interface.

View file

@ -12,7 +12,8 @@ import (
func TestEncodeDecodeNotificationEvent(t *testing.T) { func TestEncodeDecodeNotificationEvent(t *testing.T) {
event := &NotificationEvent{ event := &NotificationEvent{
ScriptHash: random.Uint160(), ScriptHash: random.Uint160(),
Item: stackitem.NewBool(true), Name: "Event",
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
} }
testserdes.EncodeDecodeBinary(t, event, new(NotificationEvent)) testserdes.EncodeDecodeBinary(t, event, new(NotificationEvent))

View file

@ -17,12 +17,12 @@ func CheckWitness(hashOrKey []byte) bool {
func Log(message string) {} func Log(message string) {}
// Notify sends a notification (collecting all arguments in an array) to the // Notify sends a notification (collecting all arguments in an array) to the
// executing environment. Unlike Log it can accept any data and resulting // executing environment. Unlike Log it can accept any data along with the event name
// notification is saved in application log. It's intended to be used as a // and resulting notification is saved in application log. It's intended to be used as a
// part of contract's API to external systems, these events can be monitored // part of contract's API to external systems, these events can be monitored
// from outside and act upon accordingly. This function uses // from outside and act upon accordingly. This function uses
// `System.Runtime.Notify` syscall. // `System.Runtime.Notify` syscall.
func Notify(arg ...interface{}) {} func Notify(name string, arg ...interface{}) {}
// GetTime returns the timestamp of the most recent block. Note that when running // GetTime returns the timestamp of the most recent block. Note that when running
// script in test mode this would be the last accepted (persisted) block in the // script in test mode this would be the last accepted (persisted) block in the

View file

@ -28,7 +28,11 @@ type NotificationEvent struct {
// result.NotificationEvent. // result.NotificationEvent.
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent { func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
seen := make(map[stackitem.Item]bool) seen := make(map[stackitem.Item]bool)
item := smartcontract.ParameterFromStackItem(event.Item, seen) args := stackitem.NewArray([]stackitem.Item{
stackitem.Make(event.Name),
event.Item,
})
item := smartcontract.ParameterFromStackItem(args, seen)
return NotificationEvent{ return NotificationEvent{
Contract: event.ScriptHash, Contract: event.ScriptHash,
Item: item, Item: item,

View file

@ -51,8 +51,8 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "10e262ef80c76bdecca287a2c047841fc02c3129" const testContractHash = "402da558b87b5e54b59dc242c788bb4dd4cd906c"
const deploymentTxHash = "49f555734b90eb7d4f87041b5146a9b6bc7cf70060bb665212773719091b3a81" const deploymentTxHash = "2afd69cc80ebe900a060450e8628b57063f3ec93ca5fc7f94582be4a4f3a041f"
var rpcTestCases = map[string][]rpcTestCase{ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {

Binary file not shown.

Binary file not shown.

View file

@ -82,8 +82,9 @@ func runtimeLog(vm *VM) error {
// runtimeNotify handles the syscall "System.Runtime.Notify" for printing and logging stuff. // runtimeNotify handles the syscall "System.Runtime.Notify" for printing and logging stuff.
func runtimeNotify(vm *VM) error { func runtimeNotify(vm *VM) error {
name := vm.Estack().Pop().Bytes()
item := vm.Estack().Pop() item := vm.Estack().Pop()
fmt.Printf("NEO-GO-VM (notify) > %s\n", item.Value()) fmt.Printf("NEO-GO-VM (notify) > [%s] %s\n", string(name), item.Value())
return nil return nil
} }