diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 9d8c930a8..811f036ae 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -343,7 +343,17 @@ func (ic *interopContext) runtimeCheckWitness(v *vm.VM) error { func (ic *interopContext) runtimeNotify(v *vm.VM) error { // It can be just about anything. e := v.Estack().Pop() - ne := state.NotificationEvent{ScriptHash: getContextScriptHash(v, 0), Item: e.Item()} + item := e.Item() + // 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 := vm.SerializeItem(item) + if err != nil { + item = vm.NewByteArrayItem([]byte(fmt.Sprintf("bad notification: %v", err))) + } + ne := state.NotificationEvent{ScriptHash: getContextScriptHash(v, 0), Item: item} ic.notifications = append(ic.notifications, ne) return nil } diff --git a/pkg/vm/interop.go b/pkg/vm/interop.go index 6247d5a70..b6f6b8221 100644 --- a/pkg/vm/interop.go +++ b/pkg/vm/interop.go @@ -93,7 +93,7 @@ func runtimeNotify(vm *VM) error { // RuntimeSerialize handles syscalls System.Runtime.Serialize and Neo.Runtime.Serialize. func RuntimeSerialize(vm *VM) error { item := vm.Estack().Pop() - data, err := serializeItem(item.value) + data, err := SerializeItem(item.value) if err != nil { return err } else if len(data) > MaxItemSize { @@ -109,7 +109,7 @@ func RuntimeSerialize(vm *VM) error { func RuntimeDeserialize(vm *VM) error { data := vm.Estack().Pop().Bytes() - item, err := deserializeItem(data) + item, err := DeserializeItem(data) if err != nil { return err } diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go index 245102f20..81c2fc6b1 100644 --- a/pkg/vm/serialization.go +++ b/pkg/vm/serialization.go @@ -17,7 +17,8 @@ const ( mapT stackItemType = 0x82 ) -func serializeItem(item StackItem) ([]byte, error) { +// SerializeItem encodes given StackItem into the byte slice. +func SerializeItem(item StackItem) ([]byte, error) { w := io.NewBufBinWriter() EncodeBinaryStackItem(item, w.BinWriter) if w.Err != nil { @@ -35,7 +36,7 @@ func EncodeBinaryStackItem(item StackItem, w *io.BinWriter) { func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { if seen[item] { - w.Err = errors.New("recursive structures are not supported") + w.Err = errors.New("recursive structures can't be serialized") return } @@ -50,7 +51,7 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { w.WriteBytes([]byte{byte(integerT)}) w.WriteVarBytes(IntToBytes(t.value)) case *InteropItem: - w.Err = errors.New("not supported") + w.Err = errors.New("interop item can't be serialized") case *ArrayItem, *StructItem: seen[item] = true @@ -78,7 +79,8 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { } } -func deserializeItem(data []byte) (StackItem, error) { +// DeserializeItem decodes StackItem from the given byte slice. +func DeserializeItem(data []byte) (StackItem, error) { r := io.NewBinReaderFromBuf(data) item := DecodeBinaryStackItem(r) if r.Err != nil { diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index cab01864a..5e236cf0a 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -653,7 +653,7 @@ func TestDeserializeUnknown(t *testing.T) { prog := append(getSyscallProg("Neo.Runtime.Deserialize"), byte(opcode.RET)) vm := load(prog) - data, err := serializeItem(NewBigIntegerItem(123)) + data, err := SerializeItem(NewBigIntegerItem(123)) require.NoError(t, err) data[0] = 0xFF