From 7ca93e76ac821bd818b6f0cdb15204a8a15de9b4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 11 Nov 2020 18:43:28 +0300 Subject: [PATCH 1/4] core, rpc: allow to store several AppExecResult for a single hash It is required for we have several executions per block. --- cli/executor_test.go | 6 +- pkg/core/blockchain.go | 44 ++++---- pkg/core/blockchain_test.go | 15 +-- pkg/core/blockchainer/blockchainer.go | 3 +- pkg/core/dao/dao.go | 54 ++++++++-- pkg/core/dao/dao_test.go | 16 +-- pkg/core/native_contract_test.go | 48 +++++---- pkg/core/native_designate_test.go | 18 ++-- pkg/core/native_neo_test.go | 5 +- pkg/core/native_policy_test.go | 5 +- pkg/core/state/notification_event.go | 117 ++++++++++++++------- pkg/core/state/notification_event_test.go | 92 +++++++++------- pkg/network/helper_test.go | 3 +- pkg/rpc/client/rpc.go | 4 +- pkg/rpc/client/rpc_test.go | 20 ++-- pkg/rpc/response/result/application_log.go | 79 ++++++++++++++ pkg/rpc/server/server.go | 13 ++- pkg/rpc/server/server_test.go | 20 ++-- 18 files changed, 384 insertions(+), 178 deletions(-) create mode 100644 pkg/rpc/response/result/application_log.go diff --git a/cli/executor_test.go b/cli/executor_test.go index 53c403b36..a8e489552 100644 --- a/cli/executor_test.go +++ b/cli/executor_test.go @@ -19,6 +19,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/network" "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/vm" "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) 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.Equal(t, vm.HaltState, aer.VMState) + require.Equal(t, 1, len(aer)) + require.Equal(t, vm.HaltState, aer[0].VMState) return tx, height } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index f575ed082..82f1ed459 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -349,7 +349,7 @@ func (bc *Blockchain) notificationDispatcher() { // subscribers. if len(txFeed) != 0 || len(notificationFeed) != 0 || len(executionFeed) != 0 { aer := event.appExecResults[0] - if !aer.TxHash.Equals(event.block.Hash()) { + if !aer.Container.Equals(event.block.Hash()) { panic("inconsistent application execution results") } for ch := range executionFeed { @@ -364,7 +364,7 @@ func (bc *Blockchain) notificationDispatcher() { aerIdx := 1 for _, tx := range event.block.Transactions { aer := event.appExecResults[aerIdx] - if !aer.TxHash.Equals(tx.Hash()) { + if !aer.Container.Equals(tx.Hash()) { panic("inconsistent application execution results") } aerIdx++ @@ -384,7 +384,7 @@ func (bc *Blockchain) notificationDispatcher() { } aer = event.appExecResults[aerIdx] - if !aer.TxHash.Equals(event.block.Hash()) { + if !aer.Container.Equals(event.block.Hash()) { panic("inconsistent application execution results") } for ch := range executionFeed { @@ -612,13 +612,15 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error faultException = err.Error() } aer := &state.AppExecResult{ - TxHash: tx.Hash(), - Trigger: trigger.Application, - VMState: v.State(), - GasConsumed: v.GasConsumed(), - Stack: v.Estack().ToArray(), - Events: systemInterop.Notifications, - FaultException: faultException, + Container: tx.Hash(), + Execution: state.Execution{ + Trigger: trigger.Application, + VMState: v.State(), + GasConsumed: v.GasConsumed(), + Stack: v.Estack().ToArray(), + Events: systemInterop.Notifications, + FaultException: faultException, + }, } appExecResults = append(appExecResults, aer) 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) } appExecResults = append(appExecResults, aer) - err = cache.PutAppExecResult(aer, writeBuf) + err = cache.AppendAppExecResult(aer, writeBuf) if err != nil { 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()) } return &state.AppExecResult{ - TxHash: block.Hash(), // application logs can be retrieved by block hash - Trigger: trig, - VMState: v.State(), - GasConsumed: v.GasConsumed(), - Stack: v.Estack().ToArray(), - Events: systemInterop.Notifications, + Container: block.Hash(), // application logs can be retrieved by block hash + Execution: state.Execution{ + Trigger: trig, + VMState: v.State(), + GasConsumed: v.GasConsumed(), + Stack: v.Estack().ToArray(), + Events: systemInterop.Notifications, + }, }, nil } @@ -946,10 +950,10 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio 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. -func (bc *Blockchain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) { - return bc.dao.GetAppExecResult(hash) +func (bc *Blockchain) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) { + return bc.dao.GetAppExecResults(hash, trig) } // GetStorageItem returns an item from storage. diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d43b35fae..93d18c442 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -253,9 +253,10 @@ func TestVerifyTx(t *testing.T) { b := bc.newBlock(txMove) 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.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()) require.NoError(t, err) @@ -911,9 +912,9 @@ func TestSubscriptions(t *testing.T) { assert.Empty(t, blockCh) aer := <-executionCh - assert.Equal(t, b.Hash(), aer.TxHash) + assert.Equal(t, b.Hash(), aer.Container) aer = <-executionCh - assert.Equal(t, b.Hash(), aer.TxHash) + assert.Equal(t, b.Hash(), aer.Container) notif := <-notificationCh require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) @@ -963,7 +964,7 @@ func TestSubscriptions(t *testing.T) { assert.Empty(t, blockCh) exec := <-executionCh - require.Equal(t, b.Hash(), exec.TxHash) + require.Equal(t, b.Hash(), exec.Container) require.Equal(t, exec.VMState, vm.HaltState) // 3 burn events for every tx and 1 mint for primary node @@ -978,7 +979,7 @@ func TestSubscriptions(t *testing.T) { tx := <-txCh require.Equal(t, txExpected, tx) exec := <-executionCh - require.Equal(t, tx.Hash(), exec.TxHash) + require.Equal(t, tx.Hash(), exec.Container) if exec.VMState == vm.HaltState { notif := <-notificationCh 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) exec = <-executionCh - require.Equal(t, b.Hash(), exec.TxHash) + require.Equal(t, b.Hash(), exec.Container) require.Equal(t, exec.VMState, vm.HaltState) bc.UnsubscribeFromBlocks(blockCh) diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index 8158798d8..1a1aa94b7 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -10,6 +10,7 @@ import ( "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/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/vm" ) @@ -38,7 +39,7 @@ type Blockchainer interface { CurrentBlockHash() util.Uint256 HasBlock(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) GetNextBlockValidators() ([]*keys.PublicKey, error) GetNEP5Balances(util.Uint160) *state.NEP5Balances diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index f383edf8d..a42ae99a9 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "errors" "fmt" + iocore "io" "sort" "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/transaction" "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" ) @@ -28,11 +30,12 @@ var ( // DAO is a data access object. type DAO interface { + AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error AppendNEP5Transfer(acc util.Uint160, index uint32, tr *state.NEP5Transfer) (bool, error) DeleteContractState(hash util.Uint160) error DeleteStorageItem(id int32, 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 GetBlock(hash util.Uint256) (*block.Block, 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. -// GetAppExecResult gets application execution result from the +// GetAppExecResults gets application execution results with the specified trigger from the // given store. -func (dao *Simple) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) { - aer := &state.AppExecResult{} +func (dao *Simple) GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) { key := storage.AppendPrefix(storage.STNotification, hash.BytesBE()) - err := dao.GetAndDecode(aer, key) + aers, err := dao.Store.Get(key) if err != nil { 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 // given store. It can reuse given buffer for the purpose of value serialization. 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 { return dao.Put(aer, key) } diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index 4550da3c6..bf6ba00b1 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -11,6 +11,7 @@ import ( "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/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/stackitem" "github.com/stretchr/testify/require" @@ -82,15 +83,18 @@ func TestPutGetAppExecResult(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet) hash := random.Uint256() appExecResult := &state.AppExecResult{ - TxHash: hash, - Events: []state.NotificationEvent{}, - Stack: []stackitem.Item{}, + Container: hash, + Execution: state.Execution{ + Trigger: trigger.Application, + Events: []state.NotificationEvent{}, + Stack: []stackitem.Item{}, + }, } - err := dao.PutAppExecResult(appExecResult, nil) + err := dao.AppendAppExecResult(appExecResult, nil) require.NoError(t, err) - gotAppExecResult, err := dao.GetAppExecResult(hash) + gotAppExecResult, err := dao.GetAppExecResults(hash, trigger.All) require.NoError(t, err) - require.Equal(t, appExecResult, gotAppExecResult) + require.Equal(t, []state.AppExecResult{*appExecResult}, gotAppExecResult) } func TestPutGetStorageItem(t *testing.T) { diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 2858bff41..6884d0e85 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -191,15 +191,17 @@ func TestNativeContract_Invoke(t *testing.T) { b := chain.newBlock(tx, tx2) 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.Equal(t, vm.HaltState, res.VMState) - require.Equal(t, 1, len(res.Stack)) - require.Equal(t, big.NewInt(42), res.Stack[0].Value()) + require.Equal(t, 1, len(res)) + require.Equal(t, vm.HaltState, res[0].VMState) + 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.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()) select { @@ -277,12 +279,13 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { b := chain.newBlock(tx) 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.Equal(t, 1, len(res)) // we expect it to be FeePerByte from Policy contract - require.Equal(t, vm.HaltState, res.VMState) - require.Equal(t, 1, len(res.Stack)) - require.Equal(t, big.NewInt(1000), res.Stack[0].Value()) + require.Equal(t, vm.HaltState, res[0].VMState) + require.Equal(t, 1, len(res[0].Stack)) + require.Equal(t, big.NewInt(1000), res[0].Stack[0].Value()) }) t.Run("native Policy, setFeePerByte", func(t *testing.T) { @@ -303,12 +306,13 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { require.NoError(t, chain.persist()) - res, err := chain.GetAppExecResult(tx.Hash()) + res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) 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 - require.Equal(t, vm.HaltState, res.VMState) - require.Equal(t, 1, len(res.Stack)) - require.Equal(t, true, res.Stack[0].Value()) + require.Equal(t, vm.HaltState, res[0].VMState) + require.Equal(t, 1, len(res[0].Stack)) + require.Equal(t, true, res[0].Stack[0].Value()) require.NoError(t, chain.persist()) @@ -350,11 +354,12 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { b := chain.newBlock(tx) 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.Equal(t, vm.HaltState, res.VMState) - require.Equal(t, 1, len(res.Stack)) - require.Equal(t, int64(5), res.Stack[0].Value().(*big.Int).Int64()) + require.Equal(t, 1, len(res)) + require.Equal(t, vm.HaltState, res[0].VMState) + 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, bc.AddBlock(bc.newBlock(tx))) - aer, err := bc.GetAppExecResult(tx.Hash()) + aers, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) - require.Len(t, aer.Stack, 1) - require.Equal(t, []byte(name), aer.Stack[0].Value()) + require.Equal(t, 1, len(aers)) + require.Len(t, aers[0].Stack, 1) + require.Equal(t, []byte(name), aers[0].Stack[0].Value()) }) } } diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index 8e7e827ed..f12fff91a 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -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))) - aer, err := bc.GetAppExecResult(tx.Hash()) + aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) + require.Equal(t, 1, len(aer)) if ok { - require.Equal(t, vm.HaltState, aer.VMState) + require.Equal(t, vm.HaltState, aer[0].VMState) } 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, bc.AddBlock(bc.newBlock(tx))) - aer, err := bc.GetAppExecResult(tx.Hash()) + aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) require.NoError(t, err) + require.Equal(t, 1, len(aer)) if ok { - require.Equal(t, vm.HaltState, aer.VMState) - require.Equal(t, 1, len(aer.Stack)) - arrItem := aer.Stack[0] + require.Equal(t, vm.HaltState, aer[0].VMState) + require.Equal(t, 1, len(aer[0].Stack)) + arrItem := aer[0].Stack[0] require.Equal(t, stackitem.ArrayT, arrItem.Type()) arr := arrItem.(*stackitem.Array) require.Equal(t, resLen, arr.Len()) } else { - require.Equal(t, vm.FaultState, aer.VMState) + require.Equal(t, vm.FaultState, aer[0].VMState) } } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 9601262f6..4ae1d4b5e 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -28,9 +28,10 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) { } 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.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) { diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 4312efdb4..8a5a617fa 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -12,6 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/io" "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/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -240,11 +241,11 @@ func invokeNativePolicyMethod(chain *Blockchain, method string, args ...interfac return nil, err } - res, err := chain.GetAppExecResult(tx.Hash()) + res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) if err != nil { return nil, err } - return res, nil + return &res[0], nil } func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) { diff --git a/pkg/core/state/notification_event.go b/pkg/core/state/notification_event.go index 4ecaf45e4..f0f81e7b7 100644 --- a/pkg/core/state/notification_event.go +++ b/pkg/core/state/notification_event.go @@ -23,13 +23,8 @@ type NotificationEvent struct { // AppExecResult represent the result of the script execution, gathering together // all resulting notifications, state, stack and other metadata. type AppExecResult struct { - TxHash util.Uint256 - Trigger trigger.Type - VMState vm.State - GasConsumed int64 - Stack []stackitem.Item - Events []NotificationEvent - FaultException string + Container util.Uint256 + Execution } // EncodeBinary implements the Serializable interface. @@ -57,7 +52,7 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) { // EncodeBinary implements the Serializable interface. func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { - w.WriteBytes(aer.TxHash[:]) + w.WriteBytes(aer.Container[:]) w.WriteB(byte(aer.Trigger)) w.WriteB(byte(aer.VMState)) w.WriteU64LE(uint64(aer.GasConsumed)) @@ -68,7 +63,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) { // DecodeBinary implements the Serializable interface. func (aer *AppExecResult) DecodeBinary(r *io.BinReader) { - r.ReadBytes(aer.TxHash[:]) + r.ReadBytes(aer.Container[:]) aer.Trigger = trigger.Type(r.ReadB()) aer.VMState = vm.State(r.ReadB()) aer.GasConsumed = int64(r.ReadU64LE()) @@ -126,7 +121,63 @@ func (ne *NotificationEvent) UnmarshalJSON(data []byte) error { // appExecResultAux is an auxiliary struct for JSON marshalling 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"` VMState string `json:"vmstate"` GasConsumed int64 `json:"gasconsumed,string"` @@ -136,45 +187,38 @@ type appExecResultAux struct { } // MarshalJSON implements implements json.Marshaler interface. -func (aer *AppExecResult) MarshalJSON() ([]byte, error) { +func (e Execution) MarshalJSON() ([]byte, error) { var st json.RawMessage - arr := make([]json.RawMessage, len(aer.Stack)) + arr := make([]json.RawMessage, len(e.Stack)) for i := range arr { - data, err := stackitem.ToJSONWithTypes(aer.Stack[i]) + data, err := stackitem.ToJSONWithTypes(e.Stack[i]) if err != nil { st = []byte(`"error: recursive reference"`) break } arr[i] = data } - var err error if st == nil { st, err = json.Marshal(arr) if err != nil { return nil, err } - } - // do not marshal block hash - var hash *util.Uint256 - if aer.Trigger == trigger.Application { - hash = &aer.TxHash } - return json.Marshal(&appExecResultAux{ - TxHash: hash, - Trigger: aer.Trigger.String(), - VMState: aer.VMState.String(), - GasConsumed: aer.GasConsumed, + return json.Marshal(&executionAux{ + Trigger: e.Trigger.String(), + VMState: e.VMState.String(), + GasConsumed: e.GasConsumed, Stack: st, - Events: aer.Events, - FaultException: aer.FaultException, + Events: e.Events, + FaultException: e.FaultException, }) } // UnmarshalJSON implements implements json.Unmarshaler interface. -func (aer *AppExecResult) UnmarshalJSON(data []byte) error { - aux := new(appExecResultAux) +func (e *Execution) UnmarshalJSON(data []byte) error { + aux := new(executionAux) if err := json.Unmarshal(data, aux); err != nil { return err } @@ -188,26 +232,21 @@ func (aer *AppExecResult) UnmarshalJSON(data []byte) error { } } if err == nil { - aer.Stack = st + e.Stack = st } } - trigger, err := trigger.FromString(aux.Trigger) if err != nil { return err } - aer.Trigger = trigger - if aux.TxHash != nil { - aer.TxHash = *aux.TxHash - } + e.Trigger = trigger state, err := vm.StateFromString(aux.VMState) if err != nil { return err } - aer.VMState = state - aer.Events = aux.Events - aer.GasConsumed = aux.GasConsumed - aer.FaultException = aux.FaultException - + e.VMState = state + e.Events = aux.Events + e.GasConsumed = aux.GasConsumed + e.FaultException = aux.FaultException return nil } diff --git a/pkg/core/state/notification_event_test.go b/pkg/core/state/notification_event_test.go index 7b51a9359..ee97d9645 100644 --- a/pkg/core/state/notification_event_test.go +++ b/pkg/core/state/notification_event_test.go @@ -26,25 +26,29 @@ func TestEncodeDecodeNotificationEvent(t *testing.T) { func TestEncodeDecodeAppExecResult(t *testing.T) { t.Run("halt", func(t *testing.T) { appExecResult := &AppExecResult{ - TxHash: random.Uint256(), - Trigger: 1, - VMState: vm.HaltState, - GasConsumed: 10, - Stack: []stackitem.Item{}, - Events: []NotificationEvent{}, + Container: random.Uint256(), + Execution: Execution{ + Trigger: 1, + VMState: vm.HaltState, + GasConsumed: 10, + Stack: []stackitem.Item{}, + Events: []NotificationEvent{}, + }, } testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult)) }) t.Run("fault", func(t *testing.T) { appExecResult := &AppExecResult{ - TxHash: random.Uint256(), - Trigger: 1, - VMState: vm.FaultState, - GasConsumed: 10, - Stack: []stackitem.Item{}, - Events: []NotificationEvent{}, - FaultException: "unhandled error", + Container: random.Uint256(), + Execution: Execution{ + Trigger: 1, + VMState: vm.FaultState, + GasConsumed: 10, + Stack: []stackitem.Item{}, + Events: []NotificationEvent{}, + FaultException: "unhandled error", + }, } testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult)) @@ -91,36 +95,42 @@ func TestMarshalUnmarshalJSONNotificationEvent(t *testing.T) { func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { t.Run("positive, transaction", func(t *testing.T) { appExecResult := &AppExecResult{ - TxHash: random.Uint256(), - Trigger: trigger.Application, - VMState: vm.HaltState, - GasConsumed: 10, - Stack: []stackitem.Item{}, - Events: []NotificationEvent{}, + Container: random.Uint256(), + Execution: Execution{ + Trigger: trigger.Application, + VMState: vm.HaltState, + GasConsumed: 10, + Stack: []stackitem.Item{}, + Events: []NotificationEvent{}, + }, } testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult)) }) t.Run("positive, fault state", func(t *testing.T) { appExecResult := &AppExecResult{ - TxHash: random.Uint256(), - Trigger: trigger.Application, - VMState: vm.FaultState, - GasConsumed: 10, - Stack: []stackitem.Item{}, - Events: []NotificationEvent{}, - FaultException: "unhandled exception", + Container: random.Uint256(), + Execution: Execution{ + Trigger: trigger.Application, + VMState: vm.FaultState, + GasConsumed: 10, + Stack: []stackitem.Item{}, + Events: []NotificationEvent{}, + FaultException: "unhandled exception", + }, } testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult)) }) t.Run("positive, block", func(t *testing.T) { appExecResult := &AppExecResult{ - TxHash: random.Uint256(), - Trigger: trigger.OnPersist, - VMState: vm.HaltState, - GasConsumed: 10, - Stack: []stackitem.Item{}, - Events: []NotificationEvent{}, + Container: random.Uint256(), + Execution: Execution{ + Trigger: trigger.OnPersist, + VMState: vm.HaltState, + GasConsumed: 10, + Stack: []stackitem.Item{}, + Events: []NotificationEvent{}, + }, } data, err := json.Marshal(appExecResult) require.NoError(t, err) @@ -128,12 +138,14 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { 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, + Container: util.Uint256{}, + Execution: Execution{ + Trigger: appExecResult.Trigger, + VMState: appExecResult.VMState, + GasConsumed: appExecResult.GasConsumed, + Stack: appExecResult.Stack, + Events: appExecResult.Events, + }, } require.Equal(t, expected, actual) }) @@ -144,7 +156,9 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { i[0] = recursive errorCases := []*AppExecResult{ { - Stack: i, + Execution: Execution{ + Stack: i, + }, }, } for _, errCase := range errorCases { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index c114d5f74..135935da4 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -18,6 +18,7 @@ import ( "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/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/vm" "go.uber.org/zap/zaptest" @@ -74,7 +75,7 @@ func (chain *testChain) Close() { func (chain testChain) HeaderHeight() uint32 { 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") } func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) { diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index c51746da8..a91e21d08 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -25,10 +25,10 @@ import ( var errNetworkNotInitialized = errors.New("RPC client network is not initialized") // 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 ( params = request.NewRawParams(hash.StringLE()) - resp = new(state.AppExecResult) + resp = new(result.ApplicationLog) ) if err := c.performRequest("getapplicationlog", params, resp); err != nil { return nil, err diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 61823bfa6..5de689e28 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -110,19 +110,23 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ invoke: func(c *Client) (interface{}, error) { 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{} { txHash, err := util.Uint256DecodeStringLE("17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521") if err != nil { panic(err) } - return &state.AppExecResult{ - TxHash: txHash, - Trigger: trigger.Application, - VMState: vm.HaltState, - GasConsumed: 1, - Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))}, - Events: []state.NotificationEvent{}, + return &result.ApplicationLog{ + Container: txHash, + Executions: []state.Execution{ + { + Trigger: trigger.Application, + VMState: vm.HaltState, + GasConsumed: 1, + Stack: []stackitem.Item{stackitem.NewBigInteger(big.NewInt(1))}, + Events: []state.NotificationEvent{}, + }, + }, } }, }, diff --git a/pkg/rpc/response/result/application_log.go b/pkg/rpc/response/result/application_log.go new file mode 100644 index 000000000..b318dd7aa --- /dev/null +++ b/pkg/rpc/response/result/application_log.go @@ -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 +} diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 65f2f7b60..59bd5b16d 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -30,6 +30,7 @@ import ( "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "go.uber.org/zap" ) @@ -526,12 +527,11 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp return nil, response.ErrInvalidParams } - appExecResult, err := s.chain.GetAppExecResult(hash) + appExecResults, err := s.chain.GetAppExecResults(hash, trigger.All) if err != nil { return nil, response.NewRPCError("Unknown transaction or block", "", err) } - - return appExecResult, nil + return result.NewApplicationLog(hash, appExecResults), nil } 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 { return nil, response.NewInvalidParamsError(err.Error(), err) } - st, err := s.chain.GetAppExecResult(txHash) + aers, err := s.chain.GetAppExecResults(txHash, trigger.Application) if err != nil { 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 { results = tx.Bytes() } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 2e4375c5a..35882c4bd 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -68,15 +68,16 @@ var rpcTestCases = map[string][]rpcTestCase{ { name: "positive", 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{}) { - res, ok := acc.(*state.AppExecResult) + res, ok := acc.(*result.ApplicationLog) require.True(t, ok) expectedTxHash, err := util.Uint256DecodeStringLE(deploymentTxHash) require.NoError(t, err) - assert.Equal(t, expectedTxHash, res.TxHash) - assert.Equal(t, trigger.Application, res.Trigger) - assert.Equal(t, vm.HaltState, res.VMState) + assert.Equal(t, 1, len(res.Executions)) + assert.Equal(t, expectedTxHash, res.Container) + 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"]}` body := doRPCCall(fmt.Sprintf(rpc, e.chain.GetHeaderHash(1).StringLE()), httpSrv.URL, t) data := checkErrGetResult(t, body, false) - var res state.AppExecResult + var res result.ApplicationLog require.NoError(t, json.Unmarshal(data, &res)) - require.Equal(t, trigger.PostPersist, res.Trigger) - require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 2, len(res.Executions)) + 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) { From 0b15ca8bd0658d99f77e7d33c9f85cd0fc706488 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 10 Nov 2020 14:30:00 +0300 Subject: [PATCH 2/4] rpc: add trigger parameter to `getapplicationlog` --- pkg/rpc/client/rpc.go | 6 +++- pkg/rpc/client/rpc_test.go | 6 ++-- pkg/rpc/response/result/application_log.go | 19 +++++++---- pkg/rpc/server/server.go | 10 +++++- pkg/rpc/server/server_test.go | 38 ++++++++++++++++++++++ 5 files changed, 67 insertions(+), 12 deletions(-) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index a91e21d08..54f483495 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -18,6 +18,7 @@ import ( "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/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/wallet" ) @@ -25,11 +26,14 @@ import ( var errNetworkNotInitialized = errors.New("RPC client network is not initialized") // GetApplicationLog returns the contract log based on the specified txid. -func (c *Client) GetApplicationLog(hash util.Uint256) (*result.ApplicationLog, error) { +func (c *Client) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) { var ( params = request.NewRawParams(hash.StringLE()) resp = new(result.ApplicationLog) ) + if trig != nil { + params.Values = append(params.Values, trig.String()) + } if err := c.performRequest("getapplicationlog", params, resp); err != nil { return nil, err } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 5de689e28..54b31f050 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -108,7 +108,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ { name: "positive", 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","executions":[{"trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}]}}`, result: func(c *Client) interface{} { @@ -973,7 +973,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getapplicationlog_invalid_params_error", invoke: func(c *Client) (interface{}, error) { - return c.GetApplicationLog(util.Uint256{}) + return c.GetApplicationLog(util.Uint256{}, nil) }, }, { @@ -1148,7 +1148,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{ { name: "getapplicationlog_unmarshalling_error", invoke: func(c *Client) (interface{}, error) { - return c.GetApplicationLog(util.Uint256{}) + return c.GetApplicationLog(util.Uint256{}, nil) }, }, { diff --git a/pkg/rpc/response/result/application_log.go b/pkg/rpc/response/result/application_log.go index b318dd7aa..98920e720 100644 --- a/pkg/rpc/response/result/application_log.go +++ b/pkg/rpc/response/result/application_log.go @@ -12,8 +12,9 @@ import ( // ApplicationLog represent the results of the script executions for block or transaction. type ApplicationLog struct { - Container util.Uint256 - Executions []state.Execution + Container util.Uint256 + IsTransaction bool + Executions []state.Execution } // applicationLogAux is an auxiliary struct for ApplicationLog JSON marshalling. @@ -28,7 +29,7 @@ func (l ApplicationLog) MarshalJSON() ([]byte, error) { result := &applicationLogAux{ Executions: make([]json.RawMessage, len(l.Executions)), } - if l.Executions[0].Trigger == trigger.Application { + if l.IsTransaction { result.TxHash = &l.Container } else { result.BlockHash = &l.Container @@ -67,13 +68,17 @@ func (l *ApplicationLog) UnmarshalJSON(data []byte) error { return nil } -// NewApplicationLog creates ApplicationLog from a set of several application execution results. -func NewApplicationLog(hash util.Uint256, aers []state.AppExecResult) ApplicationLog { +// 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, + Container: hash, + IsTransaction: aers[0].Trigger == trigger.Application, } for _, aer := range aers { - result.Executions = append(result.Executions, aer.Execution) + if aer.Trigger&trig != 0 { + result.Executions = append(result.Executions, aer.Execution) + } } return result } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 59bd5b16d..ce5937069 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -527,11 +527,19 @@ func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *resp return nil, response.ErrInvalidParams } + 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 { return nil, response.NewRPCError("Unknown transaction or block", "", err) } - return result.NewApplicationLog(hash, appExecResults), nil + return result.NewApplicationLog(hash, appExecResults, trig), nil } func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Error) { diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 35882c4bd..5694c9121 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -58,6 +58,7 @@ type rpcTestCase struct { const testContractHash = "b0fda4dd46b8e5d207e86e774a4a133c6db69ee7" const deploymentTxHash = "59f7b22b90e26f883a56b916c1580e3ee4f13caded686353cd77577e6194c173" +const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" @@ -80,6 +81,43 @@ var rpcTestCases = map[string][]rpcTestCase{ 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 + }, + }, { name: "no params", params: `[]`, From d6992cb5c44d86a7c7c974bfe3294d38d7f6b140 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 12 Nov 2020 13:06:11 +0300 Subject: [PATCH 3/4] core: marshal block hash for AppExecResult [NotificationsSubsystem] --- docs/notifications.md | 2 +- pkg/core/state/notification_event.go | 13 +++--------- pkg/core/state/notification_event_test.go | 26 +++++------------------ pkg/rpc/client/wsclient_test.go | 4 ++-- 4 files changed, 11 insertions(+), 34 deletions(-) diff --git a/docs/notifications.md b/docs/notifications.md index f7b77a794..46528f225 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -339,7 +339,7 @@ Example: "method" : "transaction_executed", "params" : [ { - "txid" : "0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7", + "container" : "0xe1cd5e57e721d2a2e05fb1f08721b12057b25ab1dd7fd0f33ee1639932fdfad7", "executions" : [ { "trigger" : "Application", diff --git a/pkg/core/state/notification_event.go b/pkg/core/state/notification_event.go index f0f81e7b7..72615eb41 100644 --- a/pkg/core/state/notification_event.go +++ b/pkg/core/state/notification_event.go @@ -121,18 +121,13 @@ func (ne *NotificationEvent) UnmarshalJSON(data []byte) error { // appExecResultAux is an auxiliary struct for JSON marshalling 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) { - // do not marshal block hash - var hash *util.Uint256 - if aer.Trigger == trigger.Application { - hash = &aer.Container - } h, err := json.Marshal(&appExecResultAux{ - TxHash: hash, + Container: aer.Container, }) if err != nil { return nil, fmt.Errorf("failed to marshal hash: %w", err) @@ -159,9 +154,7 @@ func (aer *AppExecResult) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &aer.Execution); err != nil { return err } - if aux.TxHash != nil { - aer.Container = *aux.TxHash - } + aer.Container = aux.Container return nil } diff --git a/pkg/core/state/notification_event_test.go b/pkg/core/state/notification_event_test.go index ee97d9645..525397af0 100644 --- a/pkg/core/state/notification_event_test.go +++ b/pkg/core/state/notification_event_test.go @@ -7,7 +7,6 @@ import ( "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/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/stackitem" "github.com/stretchr/testify/require" @@ -132,22 +131,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { Events: []NotificationEvent{}, }, } - data, err := json.Marshal(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 - Container: util.Uint256{}, - Execution: Execution{ - Trigger: appExecResult.Trigger, - VMState: appExecResult.VMState, - GasConsumed: appExecResult.GasConsumed, - Stack: appExecResult.Stack, - Events: appExecResult.Events, - }, - } - require.Equal(t, expected, actual) + testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult)) }) t.Run("MarshalJSON recursive reference", func(t *testing.T) { @@ -169,7 +153,7 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { t.Run("UnmarshalJSON error", func(t *testing.T) { 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 { actual := new(AppExecResult) @@ -179,9 +163,9 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) { } errorCases := []string{ - `{"txid":"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":[]}`, - `{"txid":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"BadTrigger","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`, + `{"container":"0xBadHash","trigger":"Application","vmstate":"HALT","gasconsumed":"1","stack":[{"type":"Integer","value":"1"}],"notifications":[]}`, + `{"container":"0x17145a039fca704fcdbeb46e6b210af98a1a9e5b9768e46ffc38f71c79ac2521","trigger":"Application","vmstate":"BadState","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 { actual := new(AppExecResult) diff --git a/pkg/rpc/client/wsclient_test.go b/pkg/rpc/client/wsclient_test.go index d9fdd30ee..c198842d0 100644 --- a/pkg/rpc/client/wsclient_test.go +++ b/pkg/rpc/client/wsclient_test.go @@ -119,9 +119,9 @@ func TestWSClientEvents(t *testing.T) { var ok bool // Events from RPC server test chain. 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":"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":"event_missed","params":[]}`, } From 251a660b85df16be00d9a60d956c438e5a3f2467 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 12 Nov 2020 17:49:28 +0300 Subject: [PATCH 4/4] smartcontract: ignore case in trigger.FromString --- pkg/smartcontract/trigger/trigger_type.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/smartcontract/trigger/trigger_type.go b/pkg/smartcontract/trigger/trigger_type.go index 0fe314da8..60f3c1b08 100644 --- a/pkg/smartcontract/trigger/trigger_type.go +++ b/pkg/smartcontract/trigger/trigger_type.go @@ -1,6 +1,9 @@ package trigger -import "fmt" +import ( + "fmt" + "strings" +) //go:generate stringer -type=Type -output=trigger_type_string.go @@ -40,8 +43,9 @@ const ( // FromString converts string to trigger Type func FromString(str string) (Type, error) { triggers := []Type{OnPersist, PostPersist, Verification, Application, All} + str = strings.ToLower(str) for _, t := range triggers { - if t.String() == str { + if strings.ToLower(t.String()) == str { return t, nil } }