Merge pull request #499 from nspcc-dev/fix-notifications-and-getreferences-bugs
Fix notifications and getreferences bugs
This commit is contained in:
commit
2679d3fa35
9 changed files with 163 additions and 24 deletions
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -468,15 +469,39 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
||||||
|
|
||||||
case *transaction.InvocationTX:
|
case *transaction.InvocationTX:
|
||||||
systemInterop := newInteropContext(0x10, bc, tmpStore, block, tx)
|
systemInterop := newInteropContext(0x10, bc, tmpStore, block, tx)
|
||||||
vm := bc.spawnVMWithInterops(systemInterop)
|
v := bc.spawnVMWithInterops(systemInterop)
|
||||||
vm.SetCheckedHash(tx.VerificationHash().Bytes())
|
v.SetCheckedHash(tx.VerificationHash().Bytes())
|
||||||
vm.LoadScript(t.Script)
|
v.LoadScript(t.Script)
|
||||||
err := vm.Run()
|
err := v.Run()
|
||||||
if !vm.HasFailed() {
|
if !v.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")
|
||||||
}
|
}
|
||||||
|
for _, note := range systemInterop.notifications {
|
||||||
|
arr, ok := note.Item.Value().([]vm.StackItem)
|
||||||
|
if !ok || len(arr) != 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
op, ok := arr[0].Value().([]byte)
|
||||||
|
if !ok || string(op) != "transfer" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
from, ok := arr[1].Value().([]byte)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
to, ok := arr[2].Value().([]byte)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
amount, ok := arr[3].Value().(*big.Int)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: #498
|
||||||
|
_, _, _, _ = op, from, to, amount
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"tx": tx.Hash().ReverseString(),
|
"tx": tx.Hash().ReverseString(),
|
||||||
|
@ -484,6 +509,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: v.State(),
|
||||||
|
GasConsumed: util.Fixed8(0),
|
||||||
|
Stack: v.Stack("estack"),
|
||||||
|
Events: systemInterop.notifications,
|
||||||
|
}
|
||||||
|
err = putAppExecResultIntoStore(tmpStore, aer)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to store notifications")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,8 +144,8 @@ func (ic *interopContext) txGetReferences(v *vm.VM) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
stackrefs := make([]vm.StackItem, 0, len(refs))
|
stackrefs := make([]vm.StackItem, 0, len(refs))
|
||||||
for k, v := range refs {
|
for _, k := range tx.Inputs {
|
||||||
tio := txInOut{k, *v}
|
tio := txInOut{*k, *refs[*k]}
|
||||||
stackrefs = append(stackrefs, vm.NewInteropItem(tio))
|
stackrefs = append(stackrefs, vm.NewInteropItem(tio))
|
||||||
}
|
}
|
||||||
v.Estack().PushVal(stackrefs)
|
v.Estack().PushVal(stackrefs)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,7 +391,7 @@ func (ic *interopContext) checkStorageContext(stc *StorageContext) error {
|
||||||
return errors.New("no contract found")
|
return errors.New("no contract found")
|
||||||
}
|
}
|
||||||
if !contract.HasStorage() {
|
if !contract.HasStorage() {
|
||||||
return errors.New("contract can't have storage")
|
return fmt.Errorf("contract %s can't use storage", stc.ScriptHash)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,13 @@ type interopContext struct {
|
||||||
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.
|
||||||
|
|
80
pkg/core/notification_event.go
Normal file
80
pkg/core/notification_event.go
Normal 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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -197,7 +198,7 @@ func (i *ByteArrayItem) Value() interface{} {
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
func (i *ByteArrayItem) MarshalJSON() ([]byte, error) {
|
func (i *ByteArrayItem) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(string(i.value))
|
return json.Marshal(hex.EncodeToString(i.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ByteArrayItem) String() string {
|
func (i *ByteArrayItem) String() string {
|
||||||
|
|
Loading…
Reference in a new issue