From 5ce269c035b74b93aafdc9fcaf8b2d96e8334ad0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Wed, 13 Nov 2019 16:55:20 +0300 Subject: [PATCH] core: change notify implementation to save notifications Fixes #453 and makes it possible to refer to these notifications later. --- pkg/core/blockchain.go | 14 +++++- pkg/core/interop_system.go | 6 ++- pkg/core/interops.go | 14 +++--- pkg/core/notification_event.go | 80 ++++++++++++++++++++++++++++++++++ pkg/core/storage/store.go | 1 + pkg/vm/serialization.go | 23 +++++++--- pkg/vm/stack.go | 5 +++ 7 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 pkg/core/notification_event.go diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 77ae2e8fa..87c6af601 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -473,7 +473,7 @@ func (bc *Blockchain) storeBlock(block *Block) error { vm.LoadScript(t.Script) err := vm.Run() if !vm.HasFailed() { - _, err := systemInterop.mem.Persist() + _, err = systemInterop.mem.Persist() if err != nil { return errors.Wrap(err, "failed to persist invocation results") } @@ -484,6 +484,18 @@ func (bc *Blockchain) storeBlock(block *Block) error { "err": err, }).Warn("contract invocation failed") } + aer := &AppExecResult{ + TxHash: tx.Hash(), + Trigger: 0x10, + VMState: vm.State(), + GasConsumed: util.Fixed8(0), + Stack: vm.Stack("estack"), + Events: systemInterop.notifications, + } + err = putAppExecResultIntoStore(tmpStore, aer) + if err != nil { + return errors.Wrap(err, "failed to store notifications") + } } } diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index fbbf768a2..51aa4ed77 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -343,8 +343,10 @@ func (ic *interopContext) runtimeCheckWitness(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 (ic *interopContext) runtimeNotify(v *vm.VM) error { - msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes()) - log.Infof("script %s notifies: %s", getContextScriptHash(v, 0), msg) + // It can be just about anything. + e := v.Estack().Pop() + ne := NotificationEvent{getContextScriptHash(v, 0), e.Item()} + ic.notifications = append(ic.notifications, ne) return nil } diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 1df944eda..a8e1dfa7d 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -14,16 +14,18 @@ import ( ) type interopContext struct { - bc Blockchainer - trigger byte - block *Block - tx *transaction.Transaction - mem *storage.MemCachedStore + bc Blockchainer + trigger byte + block *Block + tx *transaction.Transaction + mem *storage.MemCachedStore + notifications []NotificationEvent } func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext { mem := storage.NewMemCachedStore(s) - return &interopContext{bc, trigger, block, tx, mem} + nes := make([]NotificationEvent, 0) + return &interopContext{bc, trigger, block, tx, mem, nes} } // All lists are sorted, keep 'em this way, please. diff --git a/pkg/core/notification_event.go b/pkg/core/notification_event.go new file mode 100644 index 000000000..5b15e39cd --- /dev/null +++ b/pkg/core/notification_event.go @@ -0,0 +1,80 @@ +package core + +import ( + "github.com/CityOfZion/neo-go/pkg/core/storage" + "github.com/CityOfZion/neo-go/pkg/io" + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/CityOfZion/neo-go/pkg/vm" + "github.com/pkg/errors" +) + +// NotificationEvent is a tuple of scripthash that emitted the StackItem as a +// notification and that item itself. +type NotificationEvent struct { + ScriptHash util.Uint160 + Item vm.StackItem +} + +// AppExecResult represent the result of the script execution, gathering together +// all resulting notifications, state, stack and other metadata. +type AppExecResult struct { + TxHash util.Uint256 + Trigger byte + VMState string + GasConsumed util.Fixed8 + Stack string // JSON + Events []NotificationEvent +} + +// putAppExecResultIntoStore puts given application execution result into the +// given store. +func putAppExecResultIntoStore(s storage.Store, aer *AppExecResult) error { + buf := io.NewBufBinWriter() + aer.EncodeBinary(buf.BinWriter) + if buf.Err != nil { + return buf.Err + } + key := storage.AppendPrefix(storage.STNotification, aer.TxHash.Bytes()) + return s.Put(key, buf.Bytes()) +} + +// getAppExecResultFromStore gets application execution result from the +// given store. +func getAppExecResultFromStore(s storage.Store, hash util.Uint256) (*AppExecResult, error) { + aer := &AppExecResult{} + key := storage.AppendPrefix(storage.STNotification, hash.Bytes()) + if b, err := s.Get(key); err == nil { + r := io.NewBinReaderFromBuf(b) + aer.DecodeBinary(r) + if r.Err != nil { + return nil, errors.Wrap(r.Err, "decoding failure:") + } + } else { + return nil, err + } + return aer, nil +} + +// EncodeBinary implements the Serializable interface. +func (ne *NotificationEvent) EncodeBinary(w *io.BinWriter) { + w.WriteLE(ne.ScriptHash) + vm.EncodeBinaryStackItem(ne.Item, w) +} + +// DecodeBinary implements the Serializable interface. +func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { + r.ReadLE(&ne.ScriptHash) + ne.Item = vm.DecodeBinaryStackItem(r) +} + +// EncodeBinary implements the Serializable interface. +func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { + w.WriteLE(aer.TxHash) + w.WriteArray(aer.Events) +} + +// DecodeBinary implements the Serializable interface. +func (aer *AppExecResult) DecodeBinary(r *io.BinReader) { + r.ReadLE(&aer.TxHash) + r.ReadArray(&aer.Events) +} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index b494d1c8d..5ec0f9d67 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -14,6 +14,7 @@ const ( STSpentCoin KeyPrefix = 0x45 STValidator KeyPrefix = 0x48 STAsset KeyPrefix = 0x4c + STNotification KeyPrefix = 0x4d STContract KeyPrefix = 0x50 STStorage KeyPrefix = 0x70 IXHeaderHashList KeyPrefix = 0x80 diff --git a/pkg/vm/serialization.go b/pkg/vm/serialization.go index 6d731b1c6..81562df0d 100644 --- a/pkg/vm/serialization.go +++ b/pkg/vm/serialization.go @@ -21,13 +21,20 @@ const ( func serializeItem(item StackItem) ([]byte, error) { w := io.NewBufBinWriter() - serializeItemTo(item, w.BinWriter, make(map[StackItem]bool)) + EncodeBinaryStackItem(item, w.BinWriter) if w.Err != nil { return nil, w.Err } return w.Bytes(), nil } +// EncodeBinaryStackItem encodes given StackItem into the given BinWriter. It's +// similar to io.Serializable's EncodeBinary, but works with StackItem +// interface. +func EncodeBinaryStackItem(item StackItem, w *io.BinWriter) { + serializeItemTo(item, w, make(map[StackItem]bool)) +} + func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { if seen[item] { w.Err = errors.New("recursive structures are not supported") @@ -75,14 +82,18 @@ func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) { func deserializeItem(data []byte) (StackItem, error) { r := io.NewBinReaderFromBuf(data) - item := deserializeItemFrom(r) + item := DecodeBinaryStackItem(r) if r.Err != nil { return nil, r.Err } return item, nil } -func deserializeItemFrom(r *io.BinReader) StackItem { +// DecodeBinaryStackItem decodes previously serialized StackItem from the given +// reader. It's similar to the io.Serializable's DecodeBinary(), but implemented +// as a function because StackItem itself is an interface. Caveat: always check +// reader's error value before using the returned StackItem. +func DecodeBinaryStackItem(r *io.BinReader) StackItem { var t byte r.ReadLE(&t) if r.Err != nil { @@ -107,7 +118,7 @@ func deserializeItemFrom(r *io.BinReader) StackItem { size := int(r.ReadVarUint()) arr := make([]StackItem, size) for i := 0; i < size; i++ { - arr[i] = deserializeItemFrom(r) + arr[i] = DecodeBinaryStackItem(r) } if stackItemType(t) == arrayT { @@ -118,8 +129,8 @@ func deserializeItemFrom(r *io.BinReader) StackItem { size := int(r.ReadVarUint()) m := NewMapItem() for i := 0; i < size; i++ { - value := deserializeItemFrom(r) - key := deserializeItemFrom(r) + value := DecodeBinaryStackItem(r) + key := DecodeBinaryStackItem(r) if r.Err != nil { break } diff --git a/pkg/vm/stack.go b/pkg/vm/stack.go index 183f6eac6..23a4b2247 100644 --- a/pkg/vm/stack.go +++ b/pkg/vm/stack.go @@ -59,6 +59,11 @@ func (e *Element) Prev() *Element { return nil } +// Item returns StackItem contained in the element. +func (e *Element) Item() StackItem { + return e.value +} + // Value returns value of the StackItem contained in the element. func (e *Element) Value() interface{} { return e.value.Value()