forked from TrueCloudLab/neoneo-go
Merge pull request #1127 from nspcc-dev/fix/notify
Provide event name in `runtime.Notify`
This commit is contained in:
commit
d6342ab68c
17 changed files with 84 additions and 43 deletions
|
@ -7,16 +7,16 @@ import (
|
|||
// Main is that famous Main() function, you know.
|
||||
func Main() bool {
|
||||
tx := runtime.GetScriptContainer()
|
||||
runtime.Notify(tx.Hash)
|
||||
runtime.Notify("Tx", tx.Hash)
|
||||
|
||||
callingScriptHash := runtime.GetCallingScriptHash()
|
||||
runtime.Notify(callingScriptHash)
|
||||
runtime.Notify("Calling", callingScriptHash)
|
||||
|
||||
execScriptHash := runtime.GetExecutingScriptHash()
|
||||
runtime.Notify(execScriptHash)
|
||||
runtime.Notify("Executing", execScriptHash)
|
||||
|
||||
entryScriptHash := runtime.GetEntryScriptHash()
|
||||
runtime.Notify(entryScriptHash)
|
||||
runtime.Notify("Entry", entryScriptHash)
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -54,6 +54,6 @@ func Log(args []interface{}) bool {
|
|||
|
||||
// Notify notifies about given message
|
||||
func Notify(args []interface{}) bool {
|
||||
runtime.Notify(args[0])
|
||||
runtime.Notify("Event", args[0])
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -30,8 +30,7 @@ func TestNotify(t *testing.T) {
|
|||
src := `package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
func Main(arg int) {
|
||||
runtime.Notify(arg, "sum", arg+1)
|
||||
runtime.Notify()
|
||||
runtime.Notify("Event1", arg, "sum", arg+1)
|
||||
runtime.Notify("single")
|
||||
}`
|
||||
|
||||
|
@ -39,10 +38,11 @@ func TestNotify(t *testing.T) {
|
|||
v.Estack().PushVal(11)
|
||||
|
||||
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))}
|
||||
assert.Equal(t, exp0, s.events[0].Value())
|
||||
assert.Equal(t, []stackitem.Item{}, s.events[1].Value())
|
||||
assert.Equal(t, []stackitem.Item{stackitem.NewByteArray([]byte("single"))}, s.events[2].Value())
|
||||
assert.Equal(t, "Event1", s.events[0].Name)
|
||||
assert.Equal(t, exp0, s.events[0].Item.Value())
|
||||
assert.Equal(t, "single", s.events[1].Name)
|
||||
assert.Equal(t, []stackitem.Item{}, s.events[1].Item.Value())
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"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/emit"
|
||||
"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 {
|
||||
mem map[string][]byte
|
||||
interops map[uint32]vm.InteropFunc
|
||||
events []stackitem.Item
|
||||
events []state.NotificationEvent
|
||||
}
|
||||
|
||||
func newStoragePlugin() *storagePlugin {
|
||||
|
@ -99,7 +100,12 @@ func (s *storagePlugin) getInterop(id uint32) *vm.InteropFuncPrice {
|
|||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
arr, ok := note.Item.Value().([]stackitem.Item)
|
||||
if !ok || len(arr) != 4 {
|
||||
if note.Name != "transfer" && note.Name != "Transfer" {
|
||||
return
|
||||
}
|
||||
op, ok := arr[0].Value().([]byte)
|
||||
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
|
||||
arr, ok := note.Item.Value().([]stackitem.Item)
|
||||
if !ok || len(arr) != 3 {
|
||||
return
|
||||
}
|
||||
var from []byte
|
||||
fromValue := arr[1].Value()
|
||||
fromValue := arr[0].Value()
|
||||
// we don't have `from` set when we are minting tokens
|
||||
if fromValue != nil {
|
||||
from, ok = fromValue.([]byte)
|
||||
|
@ -679,7 +678,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
|
|||
}
|
||||
}
|
||||
var to []byte
|
||||
toValue := arr[2].Value()
|
||||
toValue := arr[1].Value()
|
||||
// we don't have `to` set when we are burning tokens
|
||||
if toValue != nil {
|
||||
to, ok = toValue.([]byte)
|
||||
|
@ -687,9 +686,9 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
|
|||
return
|
||||
}
|
||||
}
|
||||
amount, ok := arr[3].Value().(*big.Int)
|
||||
amount, ok := arr[2].Value().(*big.Int)
|
||||
if !ok {
|
||||
bs, ok := arr[3].Value().([]byte)
|
||||
bs, ok := arr[2].Value().([]byte)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ func GetNotifications(ic *interop.Context, v *vm.VM) error {
|
|||
for i := range notifications {
|
||||
ev := stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray(notifications[i].ScriptHash.BytesBE()),
|
||||
stackitem.Make(notifications[i].Name),
|
||||
notifications[i].Item,
|
||||
})
|
||||
arr.Append(ev)
|
||||
|
|
|
@ -27,6 +27,8 @@ const (
|
|||
// MaxTraceableBlocks is the maximum number of blocks before current chain
|
||||
// height we're able to give information about.
|
||||
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
|
||||
|
@ -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
|
||||
// in neo-go the only meaningful thing to do here is to log.
|
||||
func runtimeNotify(ic *interop.Context, v *vm.VM) error {
|
||||
// It can be just about anything.
|
||||
e := v.Estack().Pop()
|
||||
item := e.Item()
|
||||
name := v.Estack().Pop().Bytes()
|
||||
if len(name) > MaxEventNameLen {
|
||||
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
|
||||
// (recursive) structure inside or an interop item that can't be used
|
||||
// outside of the interop subsystem anyway. I'd probably fail transactions
|
||||
// that emit such broken notifications, but that might break compatibility
|
||||
// with testnet/mainnet, so we're replacing these with error messages.
|
||||
_, err := stackitem.SerializeItem(item)
|
||||
_, err := stackitem.SerializeItem(elem.Item())
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -247,9 +247,9 @@ func TestRuntimeGetNotifications(t *testing.T) {
|
|||
defer chain.Close()
|
||||
|
||||
ic.Notifications = []state.NotificationEvent{
|
||||
{ScriptHash: util.Uint160{1}, Item: stackitem.NewByteArray([]byte{11})},
|
||||
{ScriptHash: util.Uint160{2}, 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{11})})},
|
||||
{ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})},
|
||||
{ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})},
|
||||
}
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
|
@ -261,7 +261,10 @@ func TestRuntimeGetNotifications(t *testing.T) {
|
|||
for i := range arr {
|
||||
elem := arr[i].Value().([]stackitem.Item)
|
||||
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))
|
||||
elem := arr[0].Value().([]stackitem.Item)
|
||||
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])
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
ne := state.NotificationEvent{
|
||||
ScriptHash: c.Hash,
|
||||
Name: "Transfer",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewByteArray([]byte("Transfer")),
|
||||
addrToStackItem(from),
|
||||
addrToStackItem(to),
|
||||
stackitem.NewBigInteger(amount),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
|
@ -12,7 +14,8 @@ import (
|
|||
// notification and that item itself.
|
||||
type NotificationEvent struct {
|
||||
ScriptHash util.Uint160
|
||||
Item stackitem.Item
|
||||
Name string
|
||||
Item *stackitem.Array
|
||||
}
|
||||
|
||||
// AppExecResult represent the result of the script execution, gathering together
|
||||
|
@ -29,13 +32,24 @@ type AppExecResult struct {
|
|||
// EncodeBinary implements the Serializable interface.
|
||||
func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) {
|
||||
ne.ScriptHash.EncodeBinary(w)
|
||||
w.WriteString(ne.Name)
|
||||
stackitem.EncodeBinaryStackItem(ne.Item, w)
|
||||
}
|
||||
|
||||
// DecodeBinary implements the Serializable interface.
|
||||
func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
|
||||
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.
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
func TestEncodeDecodeNotificationEvent(t *testing.T) {
|
||||
event := &NotificationEvent{
|
||||
ScriptHash: random.Uint160(),
|
||||
Item: stackitem.NewBool(true),
|
||||
Name: "Event",
|
||||
Item: stackitem.NewArray([]stackitem.Item{stackitem.NewBool(true)}),
|
||||
}
|
||||
|
||||
testserdes.EncodeDecodeBinary(t, event, new(NotificationEvent))
|
||||
|
|
|
@ -17,12 +17,12 @@ func CheckWitness(hashOrKey []byte) bool {
|
|||
func Log(message string) {}
|
||||
|
||||
// Notify sends a notification (collecting all arguments in an array) to the
|
||||
// executing environment. Unlike Log it can accept any data and resulting
|
||||
// notification is saved in application log. It's intended to be used as a
|
||||
// executing environment. Unlike Log it can accept any data along with the event name
|
||||
// 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
|
||||
// from outside and act upon accordingly. This function uses
|
||||
// `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
|
||||
// script in test mode this would be the last accepted (persisted) block in the
|
||||
|
|
|
@ -28,7 +28,11 @@ type NotificationEvent struct {
|
|||
// result.NotificationEvent.
|
||||
func StateEventToResultNotification(event state.NotificationEvent) NotificationEvent {
|
||||
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{
|
||||
Contract: event.ScriptHash,
|
||||
Item: item,
|
||||
|
|
|
@ -51,8 +51,8 @@ type rpcTestCase struct {
|
|||
check func(t *testing.T, e *executor, result interface{})
|
||||
}
|
||||
|
||||
const testContractHash = "10e262ef80c76bdecca287a2c047841fc02c3129"
|
||||
const deploymentTxHash = "49f555734b90eb7d4f87041b5146a9b6bc7cf70060bb665212773719091b3a81"
|
||||
const testContractHash = "402da558b87b5e54b59dc242c788bb4dd4cd906c"
|
||||
const deploymentTxHash = "2afd69cc80ebe900a060450e8628b57063f3ec93ca5fc7f94582be4a4f3a041f"
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
|
|
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
BIN
pkg/rpc/server/testdata/test_contract.avm
vendored
Binary file not shown.
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -82,8 +82,9 @@ func runtimeLog(vm *VM) error {
|
|||
|
||||
// runtimeNotify handles the syscall "System.Runtime.Notify" for printing and logging stuff.
|
||||
func runtimeNotify(vm *VM) error {
|
||||
name := vm.Estack().Pop().Bytes()
|
||||
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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue