Merge pull request #1531 from nspcc-dev/core/applicationlog_with_multiple_triggers

core, rpc: store multiple execution results for single hash
This commit is contained in:
Roman Khimov 2020-11-12 19:05:22 +03:00 committed by GitHub
commit 0f827ee6ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 441 additions and 199 deletions

View file

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

View file

@ -339,7 +339,7 @@ Example:
"method" : "transaction_executed", "method" : "transaction_executed",
"params" : [ "params" : [
{ {
"txid" : "0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7", "container" : "0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7",
"executions" : [ "executions" : [
{ {
"trigger" : "Application", "trigger" : "Application",

View file

@ -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.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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())
@ -126,7 +121,56 @@ 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"` Container util.Uint256 `json:"container"`
}
// MarshalJSON implements implements json.Marshaler interface.
func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
h, err := json.Marshal(&appExecResultAux{
Container: aer.Container,
})
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
}
aer.Container = aux.Container
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 +180,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 +225,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
} }

View file

@ -7,7 +7,6 @@ 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/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"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/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -26,25 +25,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,51 +94,44 @@ 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) testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
require.NoError(t, err)
actual := new(AppExecResult)
require.NoError(t, json.Unmarshal(data, actual))
expected := &AppExecResult{
// we have no way to restore block hash as it was not marshalled
TxHash: util.Uint256{},
Trigger: appExecResult.Trigger,
VMState: appExecResult.VMState,
GasConsumed: appExecResult.GasConsumed,
Stack: appExecResult.Stack,
Events: appExecResult.Events,
}
require.Equal(t, expected, actual)
}) })
t.Run("MarshalJSON recursive reference", func(t *testing.T) { t.Run("MarshalJSON recursive reference", func(t *testing.T) {
@ -144,8 +140,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)
@ -155,7 +153,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
t.Run("UnmarshalJSON error", func(t *testing.T) { t.Run("UnmarshalJSON error", func(t *testing.T) {
nilStackCases := []string{ nilStackCases := []string{
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"WrongType","value":"1"}],"notifications":[]}`, `{"container":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"WrongType","value":"1"}],"notifications":[]}`,
} }
for _, str := range nilStackCases { for _, str := range nilStackCases {
actual := new(AppExecResult) actual := new(AppExecResult)
@ -165,9 +163,9 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
} }
errorCases := []string{ errorCases := []string{
`{"txid":"0xBadHash","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`, `{"container":"0xBadHash","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"BadState","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`, `{"container":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"BadState","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
`{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"BadTrigger","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`, `{"container":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"BadTrigger","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`,
} }
for _, str := range errorCases { for _, str := range errorCases {
actual := new(AppExecResult) actual := new(AppExecResult)

View file

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

View file

@ -18,6 +18,7 @@ import (
"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/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"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/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
@ -25,11 +26,14 @@ 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, trig *trigger.Type) (*result.ApplicationLog, error) {
var ( var (
params = request.NewRawParams(hash.StringLE()) params = request.NewRawParams(hash.StringLE())
resp = new(state.AppExecResult) resp = new(result.ApplicationLog)
) )
if trig != nil {
params.Values = append(params.Values, trig.String())
}
if err := c.performRequest("getapplicationlog", params, resp); err != nil { if err := c.performRequest("getapplicationlog", params, resp); err != nil {
return nil, err return nil, err
} }

View file

@ -108,21 +108,25 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{}) return c.GetApplicationLog(util.Uint256{}, nil)
}, },
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{},
},
},
} }
}, },
}, },
@ -969,7 +973,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "getapplicationlog_invalid_params_error", name: "getapplicationlog_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{}) return c.GetApplicationLog(util.Uint256{}, nil)
}, },
}, },
{ {
@ -1144,7 +1148,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "getapplicationlog_unmarshalling_error", name: "getapplicationlog_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetApplicationLog(util.Uint256{}) return c.GetApplicationLog(util.Uint256{}, nil)
}, },
}, },
{ {

View file

@ -119,9 +119,9 @@ func TestWSClientEvents(t *testing.T) {
var ok bool var ok bool
// Events from RPC server test chain. // Events from RPC server test chain.
var events = []string{ var events = []string{
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","trigger":"Application","vmstate":"HALT","gasconsumed":"22910000","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}}]}]}`, `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"container":"0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7","trigger":"Application","vmstate":"HALT","gasconsumed":"22910000","stack":[],"notifications":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}},{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}}]}`, `{"jsonrpc":"2.0","method":"notification_from_execution","params":[{"contract":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"dpFiJB7t+XwkgWUq3xug9b9XQxs="},{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"Integer","value":"1000"}]}]}}]}`,
`{"jsonrpc":"2.0","method":"transaction_executed","params":[{"txid":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","trigger":"Application","vmstate":"HALT","gasconsumed":"6042610","stack":[],"notifications":[{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}]}},{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}}]}]}`, `{"jsonrpc":"2.0","method":"transaction_executed","params":[{"container":"0xf97a72b7722c109f909a8bc16c22368c5023d85828b09b127b237aace33cf099","trigger":"Application","vmstate":"HALT","gasconsumed":"6042610","stack":[],"notifications":[{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"contract call","state":{"type":"Array","value":[{"type":"ByteString","value":"dHJhbnNmZXI="},{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}]}},{"contract":"0xe65ff7b3a02d207b584a5c27057d4e9862ef01da","eventname":"transfer","state":{"type":"Array","value":[{"type":"ByteString","value":"MW6FEDkBnTnfwsN9bD/uGf1YCYc="},{"type":"ByteString","value":"IHKCdK+vw29DoHHTKM+j5inZy7A="},{"type":"Integer","value":"123"}]}}]}]}`,
`{"jsonrpc":"2.0","method":"block_added","params":[{"size":1641,"nextblockhash":"0x003abea54aa3c5edba7e33fb7ca96452cb65ff8cd36ce1cdfd412a6c4d3ea38a","confirmations":6,"hash":"0xd9518e322440714b0564d6f84a9a39b527b5480e4e7f7932895777a4c8fa0a9e","version":0,"previousblockhash":"0xa496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0","merkleroot":"0x2b0f84636d814f3a952de145c8f4028f5664132f2719f5902e1884c9fba59806","time":1596101407001,"index":1,"nextconsensus":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","witnesses":[{"invocation":"DEANAGtuw7+VLVNvmpESGL4+xqlKgBSIWmMBEtABi86ixft2Q7AcaOC89M+yKVIuTel9doVJcCvfx93CcQ63DZqCDEBtwUEkjuzP9h8ZTL0GEKfGr01pazmh8s2TswJge5sAGryYE/+kjw5NCLFmowhPU73qUYQ9jq1zMNMXF+Deqxp/DEDkytkkwJec5n4x2+l5zsZHT6QTXJsByZOWXaGPVJKK8CeDccZba7Mf4MdSkWqSt61xUtlgM2Iqhe/Iuokf/ZEXDEAOH72S12CuAxVu0XNGyj3cgMtad+Bghxvr16T9+ELaWkpR4ko26FdStYC2XiCkzanXTtAD1Id5rREsxfFeKb83","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}],"consensusdata":{"primary":0,"nonce":"0000000000000457"},"tx":[{"hash":"0x32f9bd3a2707475407c41bf5daacf9560e25ed74f6d85b3afb2ef72edb2325ba","size":555,"version":0,"nonce":2,"sender":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","sysfee":"10000000","netfee":"4488350","validuntilblock":1200,"attributes":[],"signers":[{"account":"0x95307cb9cc8c4578cef9f6845895eb7aa8be125e","scopes":"CalledByEntry"}],"script":"Ahjd9QUMFKqKz4WdT+QCs05nPyFWgheWpIjrDBReEr6oeuuVWIT2+c54RYzMuXwwlRPADAh0cmFuc2ZlcgwUJQWey0h406h1+RxRzt7TMNRXX95BYn1bUjg=","witnesses":[{"invocation":"DEAIcSUsAtRql4t+IEeo+p4+YI7bA6PG+1xxUkPIb2vNlaMl4PumjQVFT+bg2ldxCYa6zccoc4n0Gfryi82EhGpGDECR4fQDr4njo94mF6/GA+OH0Y5k735yGMEZHs96586BRp6f0AQxfmIPvLcS4Yero9p0zgVl9BDg3TxU5piRylR5DEAcjOT7JjEwNRnKgDDkXfh63Yc3MorMbdb2asTiDu0aexy5M5XcikA1jypJT4wkhxjp0rrgFZRSzeYhwV0Klz+yDECIopKxLd4p+hLHxFq07WffXd++sN0WIRWzvMJncCrJqSP8zz65r8TGFFzvZMdGelWKO7KhBOhIK6wryuWNlaDI","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}]},{"hash":"0xd35d6386ec2f29b90839536f6af9466098d1665e951cdd0a20db6b4629b08369","size":559,"version":0,"nonce":3,"sender":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","sysfee":"10000000","netfee":"4492350","validuntilblock":1200,"attributes":[],"signers":[{"account":"0x95307cb9cc8c4578cef9f6845895eb7aa8be125e","scopes":"CalledByEntry"}],"script":"AwDodkgXAAAADBSqis+FnU/kArNOZz8hVoIXlqSI6wwUXhK+qHrrlViE9vnOeEWMzLl8MJUTwAwIdHJhbnNmZXIMFLyvQdaEx9StbuDZnalwe50fDI5mQWJ9W1I4","witnesses":[{"invocation":"DECKUPl9d502XPI564EC2BroqpN274uV3n1z6kCBCmbS715lzmPbh24LESMsAP2TFohhdhm16aDfNsPi5tkB/FE4DEDzJFts9VYc1lIivGAZZSxACzAV/96Kn2WAaS3bDIlAJHCShsfz+Rn3NuvMyutujYM4vyEipAX9gkjcvFWGKRObDECkI883onhG9aYTxwQWDxsmofuiooRJOic/cJ1H8nqUEvMqATYKgdHaBOJBVYsKq9M9oUv/fj6JFbMDrcasvpiaDECEqkq2b50aEc1NGM9DBAsYLEeZHrM1BwX3a2tBOeeD/KLtmTga1IZogsZgpis2BOToZO6LuN9FJYcn+/iGcC5u","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}]}]}]}`, `{"jsonrpc":"2.0","method":"block_added","params":[{"size":1641,"nextblockhash":"0x003abea54aa3c5edba7e33fb7ca96452cb65ff8cd36ce1cdfd412a6c4d3ea38a","confirmations":6,"hash":"0xd9518e322440714b0564d6f84a9a39b527b5480e4e7f7932895777a4c8fa0a9e","version":0,"previousblockhash":"0xa496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0","merkleroot":"0x2b0f84636d814f3a952de145c8f4028f5664132f2719f5902e1884c9fba59806","time":1596101407001,"index":1,"nextconsensus":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","witnesses":[{"invocation":"DEANAGtuw7+VLVNvmpESGL4+xqlKgBSIWmMBEtABi86ixft2Q7AcaOC89M+yKVIuTel9doVJcCvfx93CcQ63DZqCDEBtwUEkjuzP9h8ZTL0GEKfGr01pazmh8s2TswJge5sAGryYE/+kjw5NCLFmowhPU73qUYQ9jq1zMNMXF+Deqxp/DEDkytkkwJec5n4x2+l5zsZHT6QTXJsByZOWXaGPVJKK8CeDccZba7Mf4MdSkWqSt61xUtlgM2Iqhe/Iuokf/ZEXDEAOH72S12CuAxVu0XNGyj3cgMtad+Bghxvr16T9+ELaWkpR4ko26FdStYC2XiCkzanXTtAD1Id5rREsxfFeKb83","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}],"consensusdata":{"primary":0,"nonce":"0000000000000457"},"tx":[{"hash":"0x32f9bd3a2707475407c41bf5daacf9560e25ed74f6d85b3afb2ef72edb2325ba","size":555,"version":0,"nonce":2,"sender":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","sysfee":"10000000","netfee":"4488350","validuntilblock":1200,"attributes":[],"signers":[{"account":"0x95307cb9cc8c4578cef9f6845895eb7aa8be125e","scopes":"CalledByEntry"}],"script":"Ahjd9QUMFKqKz4WdT+QCs05nPyFWgheWpIjrDBReEr6oeuuVWIT2+c54RYzMuXwwlRPADAh0cmFuc2ZlcgwUJQWey0h406h1+RxRzt7TMNRXX95BYn1bUjg=","witnesses":[{"invocation":"DEAIcSUsAtRql4t+IEeo+p4+YI7bA6PG+1xxUkPIb2vNlaMl4PumjQVFT+bg2ldxCYa6zccoc4n0Gfryi82EhGpGDECR4fQDr4njo94mF6/GA+OH0Y5k735yGMEZHs96586BRp6f0AQxfmIPvLcS4Yero9p0zgVl9BDg3TxU5piRylR5DEAcjOT7JjEwNRnKgDDkXfh63Yc3MorMbdb2asTiDu0aexy5M5XcikA1jypJT4wkhxjp0rrgFZRSzeYhwV0Klz+yDECIopKxLd4p+hLHxFq07WffXd++sN0WIRWzvMJncCrJqSP8zz65r8TGFFzvZMdGelWKO7KhBOhIK6wryuWNlaDI","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}]},{"hash":"0xd35d6386ec2f29b90839536f6af9466098d1665e951cdd0a20db6b4629b08369","size":559,"version":0,"nonce":3,"sender":"NUVPACMnKFhpuHjsRjhUvXz1XhqfGZYVtY","sysfee":"10000000","netfee":"4492350","validuntilblock":1200,"attributes":[],"signers":[{"account":"0x95307cb9cc8c4578cef9f6845895eb7aa8be125e","scopes":"CalledByEntry"}],"script":"AwDodkgXAAAADBSqis+FnU/kArNOZz8hVoIXlqSI6wwUXhK+qHrrlViE9vnOeEWMzLl8MJUTwAwIdHJhbnNmZXIMFLyvQdaEx9StbuDZnalwe50fDI5mQWJ9W1I4","witnesses":[{"invocation":"DECKUPl9d502XPI564EC2BroqpN274uV3n1z6kCBCmbS715lzmPbh24LESMsAP2TFohhdhm16aDfNsPi5tkB/FE4DEDzJFts9VYc1lIivGAZZSxACzAV/96Kn2WAaS3bDIlAJHCShsfz+Rn3NuvMyutujYM4vyEipAX9gkjcvFWGKRObDECkI883onhG9aYTxwQWDxsmofuiooRJOic/cJ1H8nqUEvMqATYKgdHaBOJBVYsKq9M9oUv/fj6JFbMDrcasvpiaDECEqkq2b50aEc1NGM9DBAsYLEeZHrM1BwX3a2tBOeeD/KLtmTga1IZogsZgpis2BOToZO6LuN9FJYcn+/iGcC5u","verification":"EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFAtBE43vrw=="}]}]}]}`,
`{"jsonrpc":"2.0","method":"event_missed","params":[]}`, `{"jsonrpc":"2.0","method":"event_missed","params":[]}`,
} }

View file

@ -0,0 +1,84 @@
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
IsTransaction bool
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.IsTransaction {
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
// including only the results with the specified trigger.
func NewApplicationLog(hash util.Uint256, aers []state.AppExecResult, trig trigger.Type) ApplicationLog {
result := ApplicationLog{
Container: hash,
IsTransaction: aers[0].Trigger == trigger.Application,
}
for _, aer := range aers {
if aer.Trigger&trig != 0 {
result.Executions = append(result.Executions, aer.Execution)
}
}
return result
}

View file

@ -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,19 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} }
appExecResult, err := s.chain.GetAppExecResult(hash) trig := trigger.All
if len(reqParams) > 1 {
trig, err = trigger.FromString(reqParams.ValueWithType(1, request.StringT).String())
if err != nil {
return nil, response.ErrInvalidParams
}
}
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, trig), 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 +860,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()
} }

View file

@ -58,6 +58,7 @@ type rpcTestCase struct {
const testContractHash = "b0fda4dd46b8e5d207e86e774a4a133c6db69ee7" const testContractHash = "b0fda4dd46b8e5d207e86e774a4a133c6db69ee7"
const deploymentTxHash = "59f7b22b90e26f883a56b916c1580e3ee4f13caded686353cd77577e6194c173" const deploymentTxHash = "59f7b22b90e26f883a56b916c1580e3ee4f13caded686353cd77577e6194c173"
const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0"
const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7" const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7"
const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740"
@ -68,15 +69,53 @@ 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)
},
},
{
name: "positive, genesis block",
params: `["` + genesisBlockHash + `"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
assert.Equal(t, genesisBlockHash, res.Container.StringLE())
assert.Equal(t, 1, len(res.Executions))
assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
},
},
{
name: "positive, genesis block, postPersist",
params: `["` + genesisBlockHash + `", "PostPersist"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
assert.Equal(t, genesisBlockHash, res.Container.StringLE())
assert.Equal(t, 1, len(res.Executions))
assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block
assert.Equal(t, vm.HaltState, res.Executions[0].VMState)
},
},
{
name: "positive, genesis block, onPersist",
params: `["` + genesisBlockHash + `", "OnPersist"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
assert.Equal(t, genesisBlockHash, res.Container.StringLE())
assert.Equal(t, 0, len(res.Executions)) // no onPersist for genesis block
}, },
}, },
{ {
@ -890,10 +929,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) {

View file

@ -1,6 +1,9 @@
package trigger package trigger
import "fmt" import (
"fmt"
"strings"
)
//go:generate stringer -type=Type -output=trigger_type_string.go //go:generate stringer -type=Type -output=trigger_type_string.go
@ -40,8 +43,9 @@ const (
// FromString converts string to trigger Type // FromString converts string to trigger Type
func FromString(str string) (Type, error) { func FromString(str string) (Type, error) {
triggers := []Type{OnPersist, PostPersist, Verification, Application, All} triggers := []Type{OnPersist, PostPersist, Verification, Application, All}
str = strings.ToLower(str)
for _, t := range triggers { for _, t := range triggers {
if t.String() == str { if strings.ToLower(t.String()) == str {
return t, nil return t, nil
} }
} }