core: change notify implementation to save notifications

Fixes #453 and makes it possible to refer to these notifications later.
This commit is contained in:
Roman Khimov 2019-11-13 16:55:20 +03:00
parent 9134cc1c37
commit 5ce269c035
7 changed files with 128 additions and 15 deletions

View file

@ -473,7 +473,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
vm.LoadScript(t.Script) vm.LoadScript(t.Script)
err := vm.Run() err := vm.Run()
if !vm.HasFailed() { if !vm.HasFailed() {
_, err := systemInterop.mem.Persist() _, err = systemInterop.mem.Persist()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to persist invocation results") return errors.Wrap(err, "failed to persist invocation results")
} }
@ -484,6 +484,18 @@ func (bc *Blockchain) storeBlock(block *Block) error {
"err": err, "err": err,
}).Warn("contract invocation failed") }).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")
}
} }
} }

View file

@ -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 // 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 (ic *interopContext) runtimeNotify(v *vm.VM) error { func (ic *interopContext) runtimeNotify(v *vm.VM) error {
msg := fmt.Sprintf("%q", v.Estack().Pop().Bytes()) // It can be just about anything.
log.Infof("script %s notifies: %s", getContextScriptHash(v, 0), msg) e := v.Estack().Pop()
ne := NotificationEvent{getContextScriptHash(v, 0), e.Item()}
ic.notifications = append(ic.notifications, ne)
return nil return nil
} }

View file

@ -14,16 +14,18 @@ import (
) )
type interopContext struct { type interopContext struct {
bc Blockchainer bc Blockchainer
trigger byte trigger byte
block *Block block *Block
tx *transaction.Transaction tx *transaction.Transaction
mem *storage.MemCachedStore mem *storage.MemCachedStore
notifications []NotificationEvent
} }
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext { func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *Block, tx *transaction.Transaction) *interopContext {
mem := storage.NewMemCachedStore(s) 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. // All lists are sorted, keep 'em this way, please.

View file

@ -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)
}

View file

@ -14,6 +14,7 @@ const (
STSpentCoin KeyPrefix = 0x45 STSpentCoin KeyPrefix = 0x45
STValidator KeyPrefix = 0x48 STValidator KeyPrefix = 0x48
STAsset KeyPrefix = 0x4c STAsset KeyPrefix = 0x4c
STNotification KeyPrefix = 0x4d
STContract KeyPrefix = 0x50 STContract KeyPrefix = 0x50
STStorage KeyPrefix = 0x70 STStorage KeyPrefix = 0x70
IXHeaderHashList KeyPrefix = 0x80 IXHeaderHashList KeyPrefix = 0x80

View file

@ -21,13 +21,20 @@ const (
func serializeItem(item StackItem) ([]byte, error) { func serializeItem(item StackItem) ([]byte, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
serializeItemTo(item, w.BinWriter, make(map[StackItem]bool)) EncodeBinaryStackItem(item, w.BinWriter)
if w.Err != nil { if w.Err != nil {
return nil, w.Err return nil, w.Err
} }
return w.Bytes(), nil 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) { func serializeItemTo(item StackItem, w *io.BinWriter, seen map[StackItem]bool) {
if seen[item] { if seen[item] {
w.Err = errors.New("recursive structures are not supported") 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) { func deserializeItem(data []byte) (StackItem, error) {
r := io.NewBinReaderFromBuf(data) r := io.NewBinReaderFromBuf(data)
item := deserializeItemFrom(r) item := DecodeBinaryStackItem(r)
if r.Err != nil { if r.Err != nil {
return nil, r.Err return nil, r.Err
} }
return item, nil 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 var t byte
r.ReadLE(&t) r.ReadLE(&t)
if r.Err != nil { if r.Err != nil {
@ -107,7 +118,7 @@ func deserializeItemFrom(r *io.BinReader) StackItem {
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
arr := make([]StackItem, size) arr := make([]StackItem, size)
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
arr[i] = deserializeItemFrom(r) arr[i] = DecodeBinaryStackItem(r)
} }
if stackItemType(t) == arrayT { if stackItemType(t) == arrayT {
@ -118,8 +129,8 @@ func deserializeItemFrom(r *io.BinReader) StackItem {
size := int(r.ReadVarUint()) size := int(r.ReadVarUint())
m := NewMapItem() m := NewMapItem()
for i := 0; i < size; i++ { for i := 0; i < size; i++ {
value := deserializeItemFrom(r) value := DecodeBinaryStackItem(r)
key := deserializeItemFrom(r) key := DecodeBinaryStackItem(r)
if r.Err != nil { if r.Err != nil {
break break
} }

View file

@ -59,6 +59,11 @@ func (e *Element) Prev() *Element {
return nil 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. // Value returns value of the StackItem contained in the element.
func (e *Element) Value() interface{} { func (e *Element) Value() interface{} {
return e.value.Value() return e.value.Value()