core, rpc: allow to store several AppExecResult for a single hash
It is required for we have several executions per block.
This commit is contained in:
parent
e700fb2c96
commit
7ca93e76ac
18 changed files with 384 additions and 178 deletions
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network"
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/server"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -197,9 +198,10 @@ func (e *executor) checkTxPersisted(t *testing.T, prefix ...string) (*transactio
|
||||||
require.NoError(t, err, "can't decode tx hash: %s", line)
|
require.NoError(t, err, "can't decode tx hash: %s", line)
|
||||||
|
|
||||||
tx, height := e.GetTransaction(t, h)
|
tx, height := e.GetTransaction(t, h)
|
||||||
aer, err := e.Chain.GetAppExecResult(tx.Hash())
|
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, vm.HaltState, aer[0].VMState)
|
||||||
return tx, height
|
return tx, height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -349,7 +349,7 @@ func (bc *Blockchain) notificationDispatcher() {
|
||||||
// subscribers.
|
// subscribers.
|
||||||
if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 {
|
if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 {
|
||||||
aer := event.appExecResults[0]
|
aer := event.appExecResults[0]
|
||||||
if !aer.TxHash.Equals(event.block.Hash()) {
|
if !aer.Container.Equals(event.block.Hash()) {
|
||||||
panic("inconsistent application execution results")
|
panic("inconsistent application execution results")
|
||||||
}
|
}
|
||||||
for ch := range executionFeed {
|
for ch := range executionFeed {
|
||||||
|
@ -364,7 +364,7 @@ func (bc *Blockchain) notificationDispatcher() {
|
||||||
aerIdx := 1
|
aerIdx := 1
|
||||||
for _, tx := range event.block.Transactions {
|
for _, tx := range event.block.Transactions {
|
||||||
aer := event.appExecResults[aerIdx]
|
aer := event.appExecResults[aerIdx]
|
||||||
if !aer.TxHash.Equals(tx.Hash()) {
|
if !aer.Container.Equals(tx.Hash()) {
|
||||||
panic("inconsistent application execution results")
|
panic("inconsistent application execution results")
|
||||||
}
|
}
|
||||||
aerIdx++
|
aerIdx++
|
||||||
|
@ -384,7 +384,7 @@ func (bc *Blockchain) notificationDispatcher() {
|
||||||
}
|
}
|
||||||
|
|
||||||
aer = event.appExecResults[aerIdx]
|
aer = event.appExecResults[aerIdx]
|
||||||
if !aer.TxHash.Equals(event.block.Hash()) {
|
if !aer.Container.Equals(event.block.Hash()) {
|
||||||
panic("inconsistent application execution results")
|
panic("inconsistent application execution results")
|
||||||
}
|
}
|
||||||
for ch := range executionFeed {
|
for ch := range executionFeed {
|
||||||
|
@ -612,13 +612,15 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
faultException = err.Error()
|
faultException = err.Error()
|
||||||
}
|
}
|
||||||
aer := &state.AppExecResult{
|
aer := &state.AppExecResult{
|
||||||
TxHash: tx.Hash(),
|
Container: tx.Hash(),
|
||||||
|
Execution: state.Execution{
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
GasConsumed: v.GasConsumed(),
|
GasConsumed: v.GasConsumed(),
|
||||||
Stack: v.Estack().ToArray(),
|
Stack: v.Estack().ToArray(),
|
||||||
Events: systemInterop.Notifications,
|
Events: systemInterop.Notifications,
|
||||||
FaultException: faultException,
|
FaultException: faultException,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
err = cache.PutAppExecResult(aer, writeBuf)
|
err = cache.PutAppExecResult(aer, writeBuf)
|
||||||
|
@ -645,7 +647,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
return fmt.Errorf("postPersist failed: %w", err)
|
return fmt.Errorf("postPersist failed: %w", err)
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
err = cache.PutAppExecResult(aer, writeBuf)
|
err = cache.AppendAppExecResult(aer, writeBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to store postPersist exec result: %w", err)
|
return fmt.Errorf("failed to store postPersist exec result: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -723,12 +725,14 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.C
|
||||||
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
|
||||||
}
|
}
|
||||||
return &state.AppExecResult{
|
return &state.AppExecResult{
|
||||||
TxHash: block.Hash(), // application logs can be retrieved by block hash
|
Container: block.Hash(), // application logs can be retrieved by block hash
|
||||||
|
Execution: state.Execution{
|
||||||
Trigger: trig,
|
Trigger: trig,
|
||||||
VMState: v.State(),
|
VMState: v.State(),
|
||||||
GasConsumed: v.GasConsumed(),
|
GasConsumed: v.GasConsumed(),
|
||||||
Stack: v.Estack().ToArray(),
|
Stack: v.Estack().ToArray(),
|
||||||
Events: systemInterop.Notifications,
|
Events: systemInterop.Notifications,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,10 +950,10 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
|
||||||
return bc.dao.GetTransaction(hash)
|
return bc.dao.GetTransaction(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAppExecResult returns application execution result by the given
|
// GetAppExecResults returns application execution results with the specified trigger by the given
|
||||||
// tx hash or block hash.
|
// tx hash or block hash.
|
||||||
func (bc *Blockchain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
func (bc *Blockchain) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
||||||
return bc.dao.GetAppExecResult(hash)
|
return bc.dao.GetAppExecResults(hash, trig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStorageItem returns an item from storage.
|
// GetStorageItem returns an item from storage.
|
||||||
|
|
|
@ -253,9 +253,10 @@ func TestVerifyTx(t *testing.T) {
|
||||||
b := bc.newBlock(txMove)
|
b := bc.newBlock(txMove)
|
||||||
require.NoError(t, bc.AddBlock(b))
|
require.NoError(t, bc.AddBlock(b))
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResult(txMove.Hash())
|
aer, err := bc.GetAppExecResults(txMove.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, aer.VMState, vm.HaltState)
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, aer[0].VMState, vm.HaltState)
|
||||||
|
|
||||||
res, err := invokeNativePolicyMethod(bc, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE())
|
res, err := invokeNativePolicyMethod(bc, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -911,9 +912,9 @@ func TestSubscriptions(t *testing.T) {
|
||||||
assert.Empty(t, blockCh)
|
assert.Empty(t, blockCh)
|
||||||
|
|
||||||
aer := <-executionCh
|
aer := <-executionCh
|
||||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
assert.Equal(t, b.Hash(), aer.Container)
|
||||||
aer = <-executionCh
|
aer = <-executionCh
|
||||||
assert.Equal(t, b.Hash(), aer.TxHash)
|
assert.Equal(t, b.Hash(), aer.Container)
|
||||||
|
|
||||||
notif := <-notificationCh
|
notif := <-notificationCh
|
||||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||||
|
@ -963,7 +964,7 @@ func TestSubscriptions(t *testing.T) {
|
||||||
assert.Empty(t, blockCh)
|
assert.Empty(t, blockCh)
|
||||||
|
|
||||||
exec := <-executionCh
|
exec := <-executionCh
|
||||||
require.Equal(t, b.Hash(), exec.TxHash)
|
require.Equal(t, b.Hash(), exec.Container)
|
||||||
require.Equal(t, exec.VMState, vm.HaltState)
|
require.Equal(t, exec.VMState, vm.HaltState)
|
||||||
|
|
||||||
// 3 burn events for every tx and 1 mint for primary node
|
// 3 burn events for every tx and 1 mint for primary node
|
||||||
|
@ -978,7 +979,7 @@ func TestSubscriptions(t *testing.T) {
|
||||||
tx := <-txCh
|
tx := <-txCh
|
||||||
require.Equal(t, txExpected, tx)
|
require.Equal(t, txExpected, tx)
|
||||||
exec := <-executionCh
|
exec := <-executionCh
|
||||||
require.Equal(t, tx.Hash(), exec.TxHash)
|
require.Equal(t, tx.Hash(), exec.Container)
|
||||||
if exec.VMState == vm.HaltState {
|
if exec.VMState == vm.HaltState {
|
||||||
notif := <-notificationCh
|
notif := <-notificationCh
|
||||||
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
|
require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash)
|
||||||
|
@ -992,7 +993,7 @@ func TestSubscriptions(t *testing.T) {
|
||||||
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash)
|
||||||
|
|
||||||
exec = <-executionCh
|
exec = <-executionCh
|
||||||
require.Equal(t, b.Hash(), exec.TxHash)
|
require.Equal(t, b.Hash(), exec.Container)
|
||||||
require.Equal(t, exec.VMState, vm.HaltState)
|
require.Equal(t, exec.VMState, vm.HaltState)
|
||||||
|
|
||||||
bc.UnsubscribeFromBlocks(blockCh)
|
bc.UnsubscribeFromBlocks(blockCh)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
"github.com/nspcc-dev/neo-go/pkg/crypto"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
@ -38,7 +39,7 @@ type Blockchainer interface {
|
||||||
CurrentBlockHash() util.Uint256
|
CurrentBlockHash() util.Uint256
|
||||||
HasBlock(util.Uint256) bool
|
HasBlock(util.Uint256) bool
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
GetAppExecResults(util.Uint256, trigger.Type) ([]state.AppExecResult, error)
|
||||||
GetNativeContractScriptHash(string) (util.Uint160, error)
|
GetNativeContractScriptHash(string) (util.Uint160, error)
|
||||||
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
GetNextBlockValidators() ([]*keys.PublicKey, error)
|
||||||
GetNEP5Balances(util.Uint160) *state.NEP5Balances
|
GetNEP5Balances(util.Uint160) *state.NEP5Balances
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
iocore "io"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
@ -14,6 +15,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,11 +30,12 @@ var (
|
||||||
|
|
||||||
// DAO is a data access object.
|
// DAO is a data access object.
|
||||||
type DAO interface {
|
type DAO interface {
|
||||||
|
AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error
|
||||||
AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error)
|
AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error)
|
||||||
DeleteContractState(hash util.Uint160) error
|
DeleteContractState(hash util.Uint160) error
|
||||||
DeleteStorageItem(id int32, key []byte) error
|
DeleteStorageItem(id int32, key []byte) error
|
||||||
GetAndDecode(entity io.Serializable, key []byte) error
|
GetAndDecode(entity io.Serializable, key []byte) error
|
||||||
GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error)
|
GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error)
|
||||||
GetBatch() *storage.MemBatch
|
GetBatch() *storage.MemBatch
|
||||||
GetBlock(hash util.Uint256) (*block.Block, error)
|
GetBlock(hash util.Uint256) (*block.Block, error)
|
||||||
GetContractState(hash util.Uint160) (*state.Contract, error)
|
GetContractState(hash util.Uint160) (*state.Contract, error)
|
||||||
|
@ -266,22 +269,59 @@ func (dao *Simple) AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.
|
||||||
|
|
||||||
// -- start notification event.
|
// -- start notification event.
|
||||||
|
|
||||||
// GetAppExecResult gets application execution result from the
|
// GetAppExecResults gets application execution results with the specified trigger from the
|
||||||
// given store.
|
// given store.
|
||||||
func (dao *Simple) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
func (dao *Simple) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
||||||
aer := &state.AppExecResult{}
|
|
||||||
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
|
key := storage.AppendPrefix(storage.STNotification, hash.BytesBE())
|
||||||
err := dao.GetAndDecode(aer, key)
|
aers, err := dao.Store.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return aer, nil
|
r := io.NewBinReaderFromBuf(aers)
|
||||||
|
result := make([]state.AppExecResult, 0, 2)
|
||||||
|
for {
|
||||||
|
aer := new(state.AppExecResult)
|
||||||
|
aer.DecodeBinary(r)
|
||||||
|
if r.Err != nil {
|
||||||
|
if r.Err == iocore.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, r.Err
|
||||||
|
}
|
||||||
|
if aer.Trigger&trig != 0 {
|
||||||
|
result = append(result, *aer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendAppExecResult appends given application execution result to the existing
|
||||||
|
// set of execution results for the corresponding hash. It can reuse given buffer
|
||||||
|
// for the purpose of value serialization.
|
||||||
|
func (dao *Simple) AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||||
|
key := storage.AppendPrefix(storage.STNotification, aer.Container.BytesBE())
|
||||||
|
aers, err := dao.Store.Get(key)
|
||||||
|
if err != nil && err != storage.ErrKeyNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(aers) == 0 {
|
||||||
|
return dao.PutAppExecResult(aer, buf)
|
||||||
|
}
|
||||||
|
if buf == nil {
|
||||||
|
buf = io.NewBufBinWriter()
|
||||||
|
}
|
||||||
|
aer.EncodeBinary(buf.BinWriter)
|
||||||
|
if buf.Err != nil {
|
||||||
|
return buf.Err
|
||||||
|
}
|
||||||
|
aers = append(aers, buf.Bytes()...)
|
||||||
|
return dao.Store.Put(key, aers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutAppExecResult puts given application execution result into the
|
// PutAppExecResult puts given application execution result into the
|
||||||
// given store. It can reuse given buffer for the purpose of value serialization.
|
// given store. It can reuse given buffer for the purpose of value serialization.
|
||||||
func (dao *Simple) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
func (dao *Simple) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error {
|
||||||
key := storage.AppendPrefix(storage.STNotification, aer.TxHash.BytesBE())
|
key := storage.AppendPrefix(storage.STNotification, aer.Container.BytesBE())
|
||||||
if buf == nil {
|
if buf == nil {
|
||||||
return dao.Put(aer, key)
|
return dao.Put(aer, key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -82,15 +83,18 @@ func TestPutGetAppExecResult(t *testing.T) {
|
||||||
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet)
|
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet)
|
||||||
hash := random.Uint256()
|
hash := random.Uint256()
|
||||||
appExecResult := &state.AppExecResult{
|
appExecResult := &state.AppExecResult{
|
||||||
TxHash: hash,
|
Container: hash,
|
||||||
|
Execution: state.Execution{
|
||||||
|
Trigger: trigger.Application,
|
||||||
Events: []state.NotificationEvent{},
|
Events: []state.NotificationEvent{},
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
err := dao.PutAppExecResult(appExecResult, nil)
|
err := dao.AppendAppExecResult(appExecResult, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
gotAppExecResult, err := dao.GetAppExecResult(hash)
|
gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, appExecResult, gotAppExecResult)
|
require.Equal(t, []state.AppExecResult{*appExecResult}, gotAppExecResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutGetStorageItem(t *testing.T) {
|
func TestPutGetStorageItem(t *testing.T) {
|
||||||
|
|
|
@ -191,15 +191,17 @@ func TestNativeContract_Invoke(t *testing.T) {
|
||||||
b := chain.newBlock(tx, tx2)
|
b := chain.newBlock(tx, tx2)
|
||||||
require.NoError(t, chain.AddBlock(b))
|
require.NoError(t, chain.AddBlock(b))
|
||||||
|
|
||||||
res, err := chain.GetAppExecResult(tx.Hash())
|
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, 1, len(res))
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||||
require.Equal(t, big.NewInt(42), res.Stack[0].Value())
|
require.Equal(t, 1, len(res[0].Stack))
|
||||||
|
require.Equal(t, big.NewInt(42), res[0].Stack[0].Value())
|
||||||
|
|
||||||
res, err = chain.GetAppExecResult(tx2.Hash())
|
res, err = chain.GetAppExecResults(tx2.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.FaultState, res.VMState)
|
require.Equal(t, 1, len(res))
|
||||||
|
require.Equal(t, vm.FaultState, res[0].VMState)
|
||||||
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
select {
|
select {
|
||||||
|
@ -277,12 +279,13 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
|
||||||
b := chain.newBlock(tx)
|
b := chain.newBlock(tx)
|
||||||
require.NoError(t, chain.AddBlock(b))
|
require.NoError(t, chain.AddBlock(b))
|
||||||
|
|
||||||
res, err := chain.GetAppExecResult(tx.Hash())
|
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(res))
|
||||||
// we expect it to be FeePerByte from Policy contract
|
// we expect it to be FeePerByte from Policy contract
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res[0].Stack))
|
||||||
require.Equal(t, big.NewInt(1000), res.Stack[0].Value())
|
require.Equal(t, big.NewInt(1000), res[0].Stack[0].Value())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("native Policy, setFeePerByte", func(t *testing.T) {
|
t.Run("native Policy, setFeePerByte", func(t *testing.T) {
|
||||||
|
@ -303,12 +306,13 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
|
||||||
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
res, err := chain.GetAppExecResult(tx.Hash())
|
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(res))
|
||||||
// we expect it to be `true` which means that native policy value was successfully updated
|
// we expect it to be `true` which means that native policy value was successfully updated
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, 1, len(res[0].Stack))
|
||||||
require.Equal(t, true, res.Stack[0].Value())
|
require.Equal(t, true, res[0].Stack[0].Value())
|
||||||
|
|
||||||
require.NoError(t, chain.persist())
|
require.NoError(t, chain.persist())
|
||||||
|
|
||||||
|
@ -350,11 +354,12 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
|
||||||
b := chain.newBlock(tx)
|
b := chain.newBlock(tx)
|
||||||
require.NoError(t, chain.AddBlock(b))
|
require.NoError(t, chain.AddBlock(b))
|
||||||
|
|
||||||
res, err := chain.GetAppExecResult(tx.Hash())
|
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, 1, len(res))
|
||||||
require.Equal(t, 1, len(res.Stack))
|
require.Equal(t, vm.HaltState, res[0].VMState)
|
||||||
require.Equal(t, int64(5), res.Stack[0].Value().(*big.Int).Int64())
|
require.Equal(t, 1, len(res[0].Stack))
|
||||||
|
require.Equal(t, int64(5), res[0].Stack[0].Value().(*big.Int).Int64())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,10 +379,11 @@ func TestAllContractsHaveName(t *testing.T) {
|
||||||
require.NoError(t, signTx(bc, tx))
|
require.NoError(t, signTx(bc, tx))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResult(tx.Hash())
|
aers, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, aer.Stack, 1)
|
require.Equal(t, 1, len(aers))
|
||||||
require.Equal(t, []byte(name), aer.Stack[0].Value())
|
require.Len(t, aers[0].Stack, 1)
|
||||||
|
require.Equal(t, []byte(name), aers[0].Stack[0].Value())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,12 +53,13 @@ func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r native.Role, nodes
|
||||||
})
|
})
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResult(tx.Hash())
|
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
if ok {
|
if ok {
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
require.Equal(t, vm.HaltState, aer[0].VMState)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, vm.FaultState, aer.VMState)
|
require.Equal(t, vm.FaultState, aer[0].VMState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,17 +80,18 @@ func (bc *Blockchain) getNodesByRole(t *testing.T, ok bool, r native.Role, index
|
||||||
require.NoError(t, signTx(bc, tx))
|
require.NoError(t, signTx(bc, tx))
|
||||||
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
require.NoError(t, bc.AddBlock(bc.newBlock(tx)))
|
||||||
|
|
||||||
aer, err := bc.GetAppExecResult(tx.Hash())
|
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
if ok {
|
if ok {
|
||||||
require.Equal(t, vm.HaltState, aer.VMState)
|
require.Equal(t, vm.HaltState, aer[0].VMState)
|
||||||
require.Equal(t, 1, len(aer.Stack))
|
require.Equal(t, 1, len(aer[0].Stack))
|
||||||
arrItem := aer.Stack[0]
|
arrItem := aer[0].Stack[0]
|
||||||
require.Equal(t, stackitem.ArrayT, arrItem.Type())
|
require.Equal(t, stackitem.ArrayT, arrItem.Type())
|
||||||
arr := arrItem.(*stackitem.Array)
|
arr := arrItem.(*stackitem.Array)
|
||||||
require.Equal(t, resLen, arr.Len())
|
require.Equal(t, resLen, arr.Len())
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, vm.FaultState, aer.VMState)
|
require.Equal(t, vm.FaultState, aer[0].VMState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,10 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTxHalt(t *testing.T, bc *Blockchain, h util.Uint256) {
|
func checkTxHalt(t *testing.T, bc *Blockchain, h util.Uint256) {
|
||||||
aer, err := bc.GetAppExecResult(h)
|
aer, err := bc.GetAppExecResults(h, trigger.Application)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vm.HaltState, aer.VMState, aer.FaultException)
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNEO_Vote(t *testing.T) {
|
func TestNEO_Vote(t *testing.T) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
"github.com/nspcc-dev/neo-go/pkg/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"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/emit"
|
||||||
|
@ -240,11 +241,11 @@ func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interfac
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := chain.GetAppExecResult(tx.Hash())
|
res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res, nil
|
return &res[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
|
func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) {
|
||||||
|
|
|
@ -23,13 +23,8 @@ type NotificationEvent struct {
|
||||||
// AppExecResult represent the result of the script execution, gathering together
|
// AppExecResult represent the result of the script execution, gathering together
|
||||||
// all resulting notifications, state, stack and other metadata.
|
// all resulting notifications, state, stack and other metadata.
|
||||||
type AppExecResult struct {
|
type AppExecResult struct {
|
||||||
TxHash util.Uint256
|
Container util.Uint256
|
||||||
Trigger trigger.Type
|
Execution
|
||||||
VMState vm.State
|
|
||||||
GasConsumed int64
|
|
||||||
Stack []stackitem.Item
|
|
||||||
Events []NotificationEvent
|
|
||||||
FaultException string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
|
@ -57,7 +52,7 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
|
||||||
|
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteBytes(aer.TxHash[:])
|
w.WriteBytes(aer.Container[:])
|
||||||
w.WriteB(byte(aer.Trigger))
|
w.WriteB(byte(aer.Trigger))
|
||||||
w.WriteB(byte(aer.VMState))
|
w.WriteB(byte(aer.VMState))
|
||||||
w.WriteU64LE(uint64(aer.GasConsumed))
|
w.WriteU64LE(uint64(aer.GasConsumed))
|
||||||
|
@ -68,7 +63,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
|
|
||||||
// DecodeBinary implements the Serializable interface.
|
// DecodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
||||||
r.ReadBytes(aer.TxHash[:])
|
r.ReadBytes(aer.Container[:])
|
||||||
aer.Trigger = trigger.Type(r.ReadB())
|
aer.Trigger = trigger.Type(r.ReadB())
|
||||||
aer.VMState = vm.State(r.ReadB())
|
aer.VMState = vm.State(r.ReadB())
|
||||||
aer.GasConsumed = int64(r.ReadU64LE())
|
aer.GasConsumed = int64(r.ReadU64LE())
|
||||||
|
@ -127,6 +122,62 @@ func (ne *NotificationEvent) UnmarshalJSON(data []byte) error {
|
||||||
// appExecResultAux is an auxiliary struct for JSON marshalling
|
// appExecResultAux is an auxiliary struct for JSON marshalling
|
||||||
type appExecResultAux struct {
|
type appExecResultAux struct {
|
||||||
TxHash *util.Uint256 `json:"txid"`
|
TxHash *util.Uint256 `json:"txid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements implements json.Marshaler interface.
|
||||||
|
func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
|
||||||
|
// do not marshal block hash
|
||||||
|
var hash *util.Uint256
|
||||||
|
if aer.Trigger == trigger.Application {
|
||||||
|
hash = &aer.Container
|
||||||
|
}
|
||||||
|
h, err := json.Marshal(&appExecResultAux{
|
||||||
|
TxHash: hash,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal hash: %w", err)
|
||||||
|
}
|
||||||
|
exec, err := json.Marshal(aer.Execution)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal execution: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h[len(h)-1] != '}' || exec[0] != '{' {
|
||||||
|
return nil, errors.New("can't merge internal jsons")
|
||||||
|
}
|
||||||
|
h[len(h)-1] = ','
|
||||||
|
h = append(h, exec[1:]...)
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
||||||
|
func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
|
||||||
|
aux := new(appExecResultAux)
|
||||||
|
if err := json.Unmarshal(data, aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &aer.Execution); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.TxHash != nil {
|
||||||
|
aer.Container = *aux.TxHash
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution represents the result of a single script execution, gathering together
|
||||||
|
// all resulting notifications, state, stack and other metadata.
|
||||||
|
type Execution struct {
|
||||||
|
Trigger trigger.Type
|
||||||
|
VMState vm.State
|
||||||
|
GasConsumed int64
|
||||||
|
Stack []stackitem.Item
|
||||||
|
Events []NotificationEvent
|
||||||
|
FaultException string
|
||||||
|
}
|
||||||
|
|
||||||
|
// executionAux represents an auxiliary struct for Execution JSON marshalling.
|
||||||
|
type executionAux struct {
|
||||||
Trigger string `json:"trigger"`
|
Trigger string `json:"trigger"`
|
||||||
VMState string `json:"vmstate"`
|
VMState string `json:"vmstate"`
|
||||||
GasConsumed int64 `json:"gasconsumed,string"`
|
GasConsumed int64 `json:"gasconsumed,string"`
|
||||||
|
@ -136,45 +187,38 @@ type appExecResultAux struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements implements json.Marshaler interface.
|
// MarshalJSON implements implements json.Marshaler interface.
|
||||||
func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
|
func (e Execution) MarshalJSON() ([]byte, error) {
|
||||||
var st json.RawMessage
|
var st json.RawMessage
|
||||||
arr := make([]json.RawMessage, len(aer.Stack))
|
arr := make([]json.RawMessage, len(e.Stack))
|
||||||
for i := range arr {
|
for i := range arr {
|
||||||
data, err := stackitem.ToJSONWithTypes(aer.Stack[i])
|
data, err := stackitem.ToJSONWithTypes(e.Stack[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
st = []byte(`"error: recursive reference"`)
|
st = []byte(`"error: recursive reference"`)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
arr[i] = data
|
arr[i] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if st == nil {
|
if st == nil {
|
||||||
st, err = json.Marshal(arr)
|
st, err = json.Marshal(arr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// do not marshal block hash
|
|
||||||
var hash *util.Uint256
|
|
||||||
if aer.Trigger == trigger.Application {
|
|
||||||
hash = &aer.TxHash
|
|
||||||
}
|
}
|
||||||
return json.Marshal(&appExecResultAux{
|
return json.Marshal(&executionAux{
|
||||||
TxHash: hash,
|
Trigger: e.Trigger.String(),
|
||||||
Trigger: aer.Trigger.String(),
|
VMState: e.VMState.String(),
|
||||||
VMState: aer.VMState.String(),
|
GasConsumed: e.GasConsumed,
|
||||||
GasConsumed: aer.GasConsumed,
|
|
||||||
Stack: st,
|
Stack: st,
|
||||||
Events: aer.Events,
|
Events: e.Events,
|
||||||
FaultException: aer.FaultException,
|
FaultException: e.FaultException,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
||||||
func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
|
func (e *Execution) UnmarshalJSON(data []byte) error {
|
||||||
aux := new(appExecResultAux)
|
aux := new(executionAux)
|
||||||
if err := json.Unmarshal(data, aux); err != nil {
|
if err := json.Unmarshal(data, aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -188,26 +232,21 @@ func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
aer.Stack = st
|
e.Stack = st
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger, err := trigger.FromString(aux.Trigger)
|
trigger, err := trigger.FromString(aux.Trigger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
aer.Trigger = trigger
|
e.Trigger = trigger
|
||||||
if aux.TxHash != nil {
|
|
||||||
aer.TxHash = *aux.TxHash
|
|
||||||
}
|
|
||||||
state, err := vm.StateFromString(aux.VMState)
|
state, err := vm.StateFromString(aux.VMState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
aer.VMState = state
|
e.VMState = state
|
||||||
aer.Events = aux.Events
|
e.Events = aux.Events
|
||||||
aer.GasConsumed = aux.GasConsumed
|
e.GasConsumed = aux.GasConsumed
|
||||||
aer.FaultException = aux.FaultException
|
e.FaultException = aux.FaultException
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,25 +26,29 @@ func TestEncodeDecodeNotificationEvent(t *testing.T) {
|
||||||
func TestEncodeDecodeAppExecResult(t *testing.T) {
|
func TestEncodeDecodeAppExecResult(t *testing.T) {
|
||||||
t.Run("halt", func(t *testing.T) {
|
t.Run("halt", func(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
appExecResult := &AppExecResult{
|
||||||
TxHash: random.Uint256(),
|
Container: random.Uint256(),
|
||||||
|
Execution: Execution{
|
||||||
Trigger: 1,
|
Trigger: 1,
|
||||||
VMState: vm.HaltState,
|
VMState: vm.HaltState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
||||||
})
|
})
|
||||||
t.Run("fault", func(t *testing.T) {
|
t.Run("fault", func(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
appExecResult := &AppExecResult{
|
||||||
TxHash: random.Uint256(),
|
Container: random.Uint256(),
|
||||||
|
Execution: Execution{
|
||||||
Trigger: 1,
|
Trigger: 1,
|
||||||
VMState: vm.FaultState,
|
VMState: vm.FaultState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
FaultException: "unhandled error",
|
FaultException: "unhandled error",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
||||||
|
@ -91,36 +95,42 @@ func TestMarshalUnmarshalJSONNotificationEvent(t *testing.T) {
|
||||||
func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
|
func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
|
||||||
t.Run("positive, transaction", func(t *testing.T) {
|
t.Run("positive, transaction", func(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
appExecResult := &AppExecResult{
|
||||||
TxHash: random.Uint256(),
|
Container: random.Uint256(),
|
||||||
|
Execution: Execution{
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: vm.HaltState,
|
VMState: vm.HaltState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
|
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("positive, fault state", func(t *testing.T) {
|
t.Run("positive, fault state", func(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
appExecResult := &AppExecResult{
|
||||||
TxHash: random.Uint256(),
|
Container: random.Uint256(),
|
||||||
|
Execution: Execution{
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: vm.FaultState,
|
VMState: vm.FaultState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
FaultException: "unhandled exception",
|
FaultException: "unhandled exception",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
|
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
|
||||||
})
|
})
|
||||||
t.Run("positive, block", func(t *testing.T) {
|
t.Run("positive, block", func(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
appExecResult := &AppExecResult{
|
||||||
TxHash: random.Uint256(),
|
Container: random.Uint256(),
|
||||||
|
Execution: Execution{
|
||||||
Trigger: trigger.OnPersist,
|
Trigger: trigger.OnPersist,
|
||||||
VMState: vm.HaltState,
|
VMState: vm.HaltState,
|
||||||
GasConsumed: 10,
|
GasConsumed: 10,
|
||||||
Stack: []stackitem.Item{},
|
Stack: []stackitem.Item{},
|
||||||
Events: []NotificationEvent{},
|
Events: []NotificationEvent{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
data, err := json.Marshal(appExecResult)
|
data, err := json.Marshal(appExecResult)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -128,12 +138,14 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
|
||||||
require.NoError(t, json.Unmarshal(data, actual))
|
require.NoError(t, json.Unmarshal(data, actual))
|
||||||
expected := &AppExecResult{
|
expected := &AppExecResult{
|
||||||
// we have no way to restore block hash as it was not marshalled
|
// we have no way to restore block hash as it was not marshalled
|
||||||
TxHash: util.Uint256{},
|
Container: util.Uint256{},
|
||||||
|
Execution: Execution{
|
||||||
Trigger: appExecResult.Trigger,
|
Trigger: appExecResult.Trigger,
|
||||||
VMState: appExecResult.VMState,
|
VMState: appExecResult.VMState,
|
||||||
GasConsumed: appExecResult.GasConsumed,
|
GasConsumed: appExecResult.GasConsumed,
|
||||||
Stack: appExecResult.Stack,
|
Stack: appExecResult.Stack,
|
||||||
Events: appExecResult.Events,
|
Events: appExecResult.Events,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
require.Equal(t, expected, actual)
|
require.Equal(t, expected, actual)
|
||||||
})
|
})
|
||||||
|
@ -144,8 +156,10 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
|
||||||
i[0] = recursive
|
i[0] = recursive
|
||||||
errorCases := []*AppExecResult{
|
errorCases := []*AppExecResult{
|
||||||
{
|
{
|
||||||
|
Execution: Execution{
|
||||||
Stack: i,
|
Stack: i,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, errCase := range errorCases {
|
for _, errCase := range errorCases {
|
||||||
_, err := json.Marshal(errCase)
|
_, err := json.Marshal(errCase)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
@ -74,7 +75,7 @@ func (chain *testChain) Close() {
|
||||||
func (chain testChain) HeaderHeight() uint32 {
|
func (chain testChain) HeaderHeight() uint32 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
func (chain testChain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
func (chain testChain) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
|
|
|
@ -25,10 +25,10 @@ import (
|
||||||
var errNetworkNotInitialized = errors.New("RPC client network is not initialized")
|
var errNetworkNotInitialized = errors.New("RPC client network is not initialized")
|
||||||
|
|
||||||
// GetApplicationLog returns the contract log based on the specified txid.
|
// GetApplicationLog returns the contract log based on the specified txid.
|
||||||
func (c *Client) GetApplicationLog(hash util.Uint256) (*state.AppExecResult, error) {
|
func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) {
|
||||||
var (
|
var (
|
||||||
params = request.NewRawParams(hash.StringLE())
|
params = request.NewRawParams(hash.StringLE())
|
||||||
resp = new(state.AppExecResult)
|
resp = new(result.ApplicationLog)
|
||||||
)
|
)
|
||||||
if err := c.performRequest("getapplicationlog", params, resp); err != nil {
|
if err := c.performRequest("getapplicationlog", params, resp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -110,19 +110,23 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
invoke: func(c *Client) (interface{}, error) {
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
return c.GetApplicationLog(util.Uint256{})
|
return c.GetApplicationLog(util.Uint256{})
|
||||||
},
|
},
|
||||||
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}}`,
|
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","executions":[{"trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}]}}`,
|
||||||
result: func(c *Client) interface{} {
|
result: func(c *Client) interface{} {
|
||||||
txHash, err := util.Uint256DecodeStringLE("17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521")
|
txHash, err := util.Uint256DecodeStringLE("17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return &state.AppExecResult{
|
return &result.ApplicationLog{
|
||||||
TxHash: txHash,
|
Container: txHash,
|
||||||
|
Executions: []state.Execution{
|
||||||
|
{
|
||||||
Trigger: trigger.Application,
|
Trigger: trigger.Application,
|
||||||
VMState: vm.HaltState,
|
VMState: vm.HaltState,
|
||||||
GasConsumed: 1,
|
GasConsumed: 1,
|
||||||
Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))},
|
Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))},
|
||||||
Events: []state.NotificationEvent{},
|
Events: []state.NotificationEvent{},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
79
pkg/rpc/response/result/application_log.go
Normal file
79
pkg/rpc/response/result/application_log.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplicationLog represent the results of the script executions for block or transaction.
|
||||||
|
type ApplicationLog struct {
|
||||||
|
Container util.Uint256
|
||||||
|
Executions []state.Execution
|
||||||
|
}
|
||||||
|
|
||||||
|
// applicationLogAux is an auxiliary struct for ApplicationLog JSON marshalling.
|
||||||
|
type applicationLogAux struct {
|
||||||
|
TxHash *util.Uint256 `json:"txid,omitempty"`
|
||||||
|
BlockHash *util.Uint256 `json:"blockhash,omitempty"`
|
||||||
|
Executions []json.RawMessage `json:"executions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements implements json.Marshaler interface.
|
||||||
|
func (l ApplicationLog) MarshalJSON() ([]byte, error) {
|
||||||
|
result := &applicationLogAux{
|
||||||
|
Executions: make([]json.RawMessage, len(l.Executions)),
|
||||||
|
}
|
||||||
|
if l.Executions[0].Trigger == trigger.Application {
|
||||||
|
result.TxHash = &l.Container
|
||||||
|
} else {
|
||||||
|
result.BlockHash = &l.Container
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for i := range result.Executions {
|
||||||
|
result.Executions[i], err = json.Marshal(l.Executions[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal execution #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.Marshal(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements implements json.Unmarshaler interface.
|
||||||
|
func (l *ApplicationLog) UnmarshalJSON(data []byte) error {
|
||||||
|
aux := new(applicationLogAux)
|
||||||
|
if err := json.Unmarshal(data, aux); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if aux.TxHash != nil {
|
||||||
|
l.Container = *aux.TxHash
|
||||||
|
} else if aux.BlockHash != nil {
|
||||||
|
l.Container = *aux.BlockHash
|
||||||
|
} else {
|
||||||
|
return errors.New("no block or transaction hash")
|
||||||
|
}
|
||||||
|
l.Executions = make([]state.Execution, len(aux.Executions))
|
||||||
|
for i := range l.Executions {
|
||||||
|
err := json.Unmarshal(aux.Executions[i], &l.Executions[i])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal execution #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplicationLog creates ApplicationLog from a set of several application execution results.
|
||||||
|
func NewApplicationLog(hash util.Uint256, aers []state.AppExecResult) ApplicationLog {
|
||||||
|
result := ApplicationLog{
|
||||||
|
Container: hash,
|
||||||
|
}
|
||||||
|
for _, aer := range aers {
|
||||||
|
result.Executions = append(result.Executions, aer.Execution)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -526,12 +527,11 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
|
||||||
return nil, response.ErrInvalidParams
|
return nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
appExecResult, err := s.chain.GetAppExecResult(hash)
|
appExecResults, err := s.chain.GetAppExecResults(hash, trigger.All)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewRPCError("Unknown transaction or block", "", err)
|
return nil, response.NewRPCError("Unknown transaction or block", "", err)
|
||||||
}
|
}
|
||||||
|
return result.NewApplicationLog(hash, appExecResults), nil
|
||||||
return appExecResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Error) {
|
func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Error) {
|
||||||
|
@ -852,11 +852,14 @@ func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, *resp
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewInvalidParamsError(err.Error(), err)
|
return nil, response.NewInvalidParamsError(err.Error(), err)
|
||||||
}
|
}
|
||||||
st, err := s.chain.GetAppExecResult(txHash)
|
aers, err := s.chain.GetAppExecResults(txHash, trigger.Application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.NewRPCError("Unknown transaction", err.Error(), err)
|
return nil, response.NewRPCError("Unknown transaction", err.Error(), err)
|
||||||
}
|
}
|
||||||
results = result.NewTransactionOutputRaw(tx, header, st, s.chain)
|
if len(aers) == 0 {
|
||||||
|
return nil, response.NewRPCError("Unknown transaction", "", nil)
|
||||||
|
}
|
||||||
|
results = result.NewTransactionOutputRaw(tx, header, &aers[0], s.chain)
|
||||||
} else {
|
} else {
|
||||||
results = tx.Bytes()
|
results = tx.Bytes()
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,15 +68,16 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
params: `["` + deploymentTxHash + `"]`,
|
params: `["` + deploymentTxHash + `"]`,
|
||||||
result: func(e *executor) interface{} { return &state.AppExecResult{} },
|
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||||
check: func(t *testing.T, e *executor, acc interface{}) {
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
res, ok := acc.(*state.AppExecResult)
|
res, ok := acc.(*result.ApplicationLog)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
expectedTxHash, err := util.Uint256DecodeStringLE(deploymentTxHash)
|
expectedTxHash, err := util.Uint256DecodeStringLE(deploymentTxHash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedTxHash, res.TxHash)
|
assert.Equal(t, 1, len(res.Executions))
|
||||||
assert.Equal(t, trigger.Application, res.Trigger)
|
assert.Equal(t, expectedTxHash, res.Container)
|
||||||
assert.Equal(t, vm.HaltState, res.VMState)
|
assert.Equal(t, trigger.Application, res.Executions[0].Trigger)
|
||||||
|
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -890,10 +891,13 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) []
|
||||||
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["%s"]}`
|
rpc := `{"jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["%s"]}`
|
||||||
body := doRPCCall(fmt.Sprintf(rpc, e.chain.GetHeaderHash(1).StringLE()), httpSrv.URL, t)
|
body := doRPCCall(fmt.Sprintf(rpc, e.chain.GetHeaderHash(1).StringLE()), httpSrv.URL, t)
|
||||||
data := checkErrGetResult(t, body, false)
|
data := checkErrGetResult(t, body, false)
|
||||||
var res state.AppExecResult
|
var res result.ApplicationLog
|
||||||
require.NoError(t, json.Unmarshal(data, &res))
|
require.NoError(t, json.Unmarshal(data, &res))
|
||||||
require.Equal(t, trigger.PostPersist, res.Trigger)
|
require.Equal(t, 2, len(res.Executions))
|
||||||
require.Equal(t, vm.HaltState, res.VMState)
|
require.Equal(t, trigger.OnPersist, res.Executions[0].Trigger)
|
||||||
|
require.Equal(t, vm.HaltState, res.Executions[0].VMState)
|
||||||
|
require.Equal(t, trigger.PostPersist, res.Executions[1].Trigger)
|
||||||
|
require.Equal(t, vm.HaltState, res.Executions[1].VMState)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("submit", func(t *testing.T) {
|
t.Run("submit", func(t *testing.T) {
|
||||||
|
|
Loading…
Reference in a new issue