forked from TrueCloudLab/neoneo-go
Merge pull request #1462 from nspcc-dev/rpc/exceptions
rpc, core: add FaultException to AppExecResult and Invoke* results
This commit is contained in:
commit
8c2fd91c5c
9 changed files with 190 additions and 75 deletions
|
@ -584,6 +584,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
v.GasLimit = tx.SystemFee
|
v.GasLimit = tx.SystemFee
|
||||||
|
|
||||||
err := v.Run()
|
err := v.Run()
|
||||||
|
var faultException string
|
||||||
if !v.HasFailed() {
|
if !v.HasFailed() {
|
||||||
_, err := systemInterop.DAO.Persist()
|
_, err := systemInterop.DAO.Persist()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -597,14 +598,16 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
|
||||||
zap.String("tx", tx.Hash().StringLE()),
|
zap.String("tx", tx.Hash().StringLE()),
|
||||||
zap.Uint32("block", block.Index),
|
zap.Uint32("block", block.Index),
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
|
faultException = err.Error()
|
||||||
}
|
}
|
||||||
aer := &state.AppExecResult{
|
aer := &state.AppExecResult{
|
||||||
TxHash: tx.Hash(),
|
TxHash: tx.Hash(),
|
||||||
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,
|
||||||
}
|
}
|
||||||
appExecResults = append(appExecResults, aer)
|
appExecResults = append(appExecResults, aer)
|
||||||
err = cache.PutAppExecResult(aer, writeBuf)
|
err = cache.PutAppExecResult(aer, writeBuf)
|
||||||
|
|
|
@ -23,12 +23,13 @@ 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
|
TxHash util.Uint256
|
||||||
Trigger trigger.Type
|
Trigger trigger.Type
|
||||||
VMState vm.State
|
VMState vm.State
|
||||||
GasConsumed int64
|
GasConsumed int64
|
||||||
Stack []stackitem.Item
|
Stack []stackitem.Item
|
||||||
Events []NotificationEvent
|
Events []NotificationEvent
|
||||||
|
FaultException string
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
|
@ -62,6 +63,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteU64LE(uint64(aer.GasConsumed))
|
w.WriteU64LE(uint64(aer.GasConsumed))
|
||||||
stackitem.EncodeBinaryStackItem(stackitem.NewArray(aer.Stack), w)
|
stackitem.EncodeBinaryStackItem(stackitem.NewArray(aer.Stack), w)
|
||||||
w.WriteArray(aer.Events)
|
w.WriteArray(aer.Events)
|
||||||
|
w.WriteVarBytes([]byte(aer.FaultException))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeBinary implements the Serializable interface.
|
// DecodeBinary implements the Serializable interface.
|
||||||
|
@ -80,6 +82,7 @@ func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
||||||
aer.Stack = arr
|
aer.Stack = arr
|
||||||
}
|
}
|
||||||
r.ReadArray(&aer.Events)
|
r.ReadArray(&aer.Events)
|
||||||
|
aer.FaultException = r.ReadString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling.
|
// notificationEventAux is an auxiliary struct for NotificationEvent JSON marshalling.
|
||||||
|
@ -123,12 +126,13 @@ func (ne *NotificationEvent) UnmarshalJSON(data []byte) error {
|
||||||
|
|
||||||
// appExecResultAux is an auxiliary struct for JSON marshalling
|
// appExecResultAux is an auxiliary struct for JSON marshalling
|
||||||
type appExecResultAux struct {
|
type appExecResultAux struct {
|
||||||
TxHash *util.Uint256 `json:"txid"`
|
TxHash *util.Uint256 `json:"txid"`
|
||||||
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"`
|
||||||
Stack json.RawMessage `json:"stack"`
|
Stack json.RawMessage `json:"stack"`
|
||||||
Events []NotificationEvent `json:"notifications"`
|
Events []NotificationEvent `json:"notifications"`
|
||||||
|
FaultException string `json:"exception,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements implements json.Marshaler interface.
|
// MarshalJSON implements implements json.Marshaler interface.
|
||||||
|
@ -158,12 +162,13 @@ func (aer *AppExecResult) MarshalJSON() ([]byte, error) {
|
||||||
hash = &aer.TxHash
|
hash = &aer.TxHash
|
||||||
}
|
}
|
||||||
return json.Marshal(&appExecResultAux{
|
return json.Marshal(&appExecResultAux{
|
||||||
TxHash: hash,
|
TxHash: hash,
|
||||||
Trigger: aer.Trigger.String(),
|
Trigger: aer.Trigger.String(),
|
||||||
VMState: aer.VMState.String(),
|
VMState: aer.VMState.String(),
|
||||||
GasConsumed: aer.GasConsumed,
|
GasConsumed: aer.GasConsumed,
|
||||||
Stack: st,
|
Stack: st,
|
||||||
Events: aer.Events,
|
Events: aer.Events,
|
||||||
|
FaultException: aer.FaultException,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,6 +207,7 @@ func (aer *AppExecResult) UnmarshalJSON(data []byte) error {
|
||||||
aer.VMState = state
|
aer.VMState = state
|
||||||
aer.Events = aux.Events
|
aer.Events = aux.Events
|
||||||
aer.GasConsumed = aux.GasConsumed
|
aer.GasConsumed = aux.GasConsumed
|
||||||
|
aer.FaultException = aux.FaultException
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,31 @@ func TestEncodeDecodeNotificationEvent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEncodeDecodeAppExecResult(t *testing.T) {
|
func TestEncodeDecodeAppExecResult(t *testing.T) {
|
||||||
appExecResult := &AppExecResult{
|
t.Run("halt", func(t *testing.T) {
|
||||||
TxHash: random.Uint256(),
|
appExecResult := &AppExecResult{
|
||||||
Trigger: 1,
|
TxHash: random.Uint256(),
|
||||||
VMState: vm.HaltState,
|
Trigger: 1,
|
||||||
GasConsumed: 10,
|
VMState: vm.HaltState,
|
||||||
Stack: []stackitem.Item{},
|
GasConsumed: 10,
|
||||||
Events: []NotificationEvent{},
|
Stack: []stackitem.Item{},
|
||||||
}
|
Events: []NotificationEvent{},
|
||||||
|
}
|
||||||
|
|
||||||
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
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",
|
||||||
|
}
|
||||||
|
|
||||||
|
testserdes.EncodeDecodeBinary(t, appExecResult, new(AppExecResult))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarshalUnmarshalJSONNotificationEvent(t *testing.T) {
|
func TestMarshalUnmarshalJSONNotificationEvent(t *testing.T) {
|
||||||
|
@ -86,6 +101,18 @@ func TestMarshalUnmarshalJSONAppExecResult(t *testing.T) {
|
||||||
testserdes.MarshalUnmarshalJSON(t, appExecResult, new(AppExecResult))
|
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",
|
||||||
|
}
|
||||||
|
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(),
|
TxHash: random.Uint256(),
|
||||||
|
|
|
@ -7,6 +7,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/encoding/address"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"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/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
@ -34,8 +35,10 @@ func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
|
||||||
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return 0, errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get NEP5 decimals: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
return topIntFromStack(result.Stack)
|
||||||
|
@ -46,8 +49,10 @@ func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) {
|
||||||
result, err := c.InvokeFunction(tokenHash, "name", []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(tokenHash, "name", []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return "", errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get NEP5 name: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topStringFromStack(result.Stack)
|
return topStringFromStack(result.Stack)
|
||||||
|
@ -58,8 +63,10 @@ func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) {
|
||||||
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return "", errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get NEP5 symbol: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topStringFromStack(result.Stack)
|
return topStringFromStack(result.Stack)
|
||||||
|
@ -70,8 +77,10 @@ func (c *Client) NEP5TotalSupply(tokenHash util.Uint160) (int64, error) {
|
||||||
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return 0, errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get NEP5 total supply: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
return topIntFromStack(result.Stack)
|
||||||
|
@ -85,8 +94,10 @@ func (c *Client) NEP5BalanceOf(tokenHash, acc util.Uint160) (int64, error) {
|
||||||
}}, nil)
|
}}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return 0, errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get NEP5 balance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
return topIntFromStack(result.Stack)
|
||||||
|
@ -156,7 +167,7 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee,
|
||||||
return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
|
return nil, fmt.Errorf("can't add system fee to transaction: %w", err)
|
||||||
}
|
}
|
||||||
if result.State != "HALT" {
|
if result.State != "HALT" {
|
||||||
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s", result.State)
|
return nil, fmt.Errorf("can't add system fee to transaction: bad vm state: %s due to an error: %s", result.State, result.FaultException)
|
||||||
}
|
}
|
||||||
sysFee = result.GasConsumed
|
sysFee = result.GasConsumed
|
||||||
}
|
}
|
||||||
|
@ -225,3 +236,14 @@ func topStringFromStack(st []stackitem.Item) (string, error) {
|
||||||
}
|
}
|
||||||
return string(bs), nil
|
return string(bs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getInvocationError returns an error in case of bad VM state or empty stack.
|
||||||
|
func getInvocationError(result *result.Invoke) error {
|
||||||
|
if result.State != "HALT" {
|
||||||
|
return fmt.Errorf("invocation failed: %s", result.FaultException)
|
||||||
|
}
|
||||||
|
if len(result.Stack) == 0 {
|
||||||
|
return errors.New("result stack is empty")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
"github.com/nspcc-dev/neo-go/pkg/core/native"
|
||||||
|
@ -33,8 +32,10 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) {
|
||||||
result, err := c.InvokeFunction(PolicyContractHash, operation, []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(PolicyContractHash, operation, []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
}
|
||||||
return 0, errors.New("invalid VM state")
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to invoke %s Policy method: %w", operation, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return topIntFromStack(result.Stack)
|
return topIntFromStack(result.Stack)
|
||||||
|
@ -45,10 +46,11 @@ func (c *Client) GetBlockedAccounts() (native.BlockedAccounts, error) {
|
||||||
result, err := c.InvokeFunction(PolicyContractHash, "getBlockedAccounts", []smartcontract.Parameter{}, nil)
|
result, err := c.InvokeFunction(PolicyContractHash, "getBlockedAccounts", []smartcontract.Parameter{}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if result.State != "HALT" || len(result.Stack) == 0 {
|
|
||||||
return nil, errors.New("invalid VM state")
|
|
||||||
}
|
}
|
||||||
|
err = getInvocationError(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get blocked accounts: %w", err)
|
||||||
|
}
|
||||||
return topBlockedAccountsFromStack(result.Stack)
|
return topBlockedAccountsFromStack(result.Stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -541,12 +541,17 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
|
||||||
for i, cosigner := range tx.Signers {
|
for i, cosigner := range tx.Signers {
|
||||||
if accs[i].Contract.Deployed {
|
if accs[i].Contract.Deployed {
|
||||||
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
|
res, err := c.InvokeFunction(cosigner.Account, manifest.MethodVerify, []smartcontract.Parameter{}, tx.Signers)
|
||||||
if err == nil && res.State == "HALT" && len(res.Stack) == 1 {
|
if err != nil {
|
||||||
r, err := topIntFromStack(res.Stack)
|
return fmt.Errorf("failed to invoke verify: %w", err)
|
||||||
if err != nil || r == 0 {
|
}
|
||||||
return core.ErrVerificationFailed
|
if res.State != "HALT" {
|
||||||
}
|
return fmt.Errorf("invalid VM state %s due to an error: %s", res.State, res.FaultException)
|
||||||
} else {
|
}
|
||||||
|
if l := len(res.Stack); l != 1 {
|
||||||
|
return fmt.Errorf("result stack length should be equal to 1, got %d", l)
|
||||||
|
}
|
||||||
|
r, err := topIntFromStack(res.Stack)
|
||||||
|
if err != nil || r == 0 {
|
||||||
return core.ErrVerificationFailed
|
return core.ErrVerificationFailed
|
||||||
}
|
}
|
||||||
tx.NetworkFee += res.GasConsumed
|
tx.NetworkFee += res.GasConsumed
|
||||||
|
|
|
@ -677,6 +677,41 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "positive, FAULT state",
|
||||||
|
invoke: func(c *Client) (interface{}, error) {
|
||||||
|
hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
contr, err := util.Uint160DecodeStringLE("af7c7328eee5a275a3bcaee2bf0cf662b5e739be")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c.InvokeFunction(contr, "balanceOf", []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.Hash160Type,
|
||||||
|
Value: hash,
|
||||||
|
},
|
||||||
|
}, []transaction.Signer{{
|
||||||
|
Account: util.Uint160{1, 2, 3},
|
||||||
|
}})
|
||||||
|
},
|
||||||
|
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"script":"1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf","state":"FAULT","gasconsumed":"31100000","stack":[{"type":"ByteString","value":"JivsCEQy"}],"tx":"d101361426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf000000000000000000000000","exception":"gas limit exceeded"}}`,
|
||||||
|
result: func(c *Client) interface{} {
|
||||||
|
bytes, err := hex.DecodeString("262bec084432")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &result.Invoke{
|
||||||
|
State: "FAULT",
|
||||||
|
GasConsumed: 31100000,
|
||||||
|
Script: "1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89151c10962616c616e63654f6667be39e7b562f60cbfe2aebca375a2e5ee28737caf",
|
||||||
|
Stack: []stackitem.Item{stackitem.NewByteArray(bytes)},
|
||||||
|
FaultException: "gas limit exceeded",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"invokescript": {
|
"invokescript": {
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,17 +9,19 @@ import (
|
||||||
// Invoke represents code invocation result and is used by several RPC calls
|
// Invoke represents code invocation result and is used by several RPC calls
|
||||||
// that invoke functions, scripts and generic bytecode.
|
// that invoke functions, scripts and generic bytecode.
|
||||||
type Invoke struct {
|
type Invoke struct {
|
||||||
State string `json:"state"`
|
State string
|
||||||
GasConsumed int64 `json:"gasconsumed,string"`
|
GasConsumed int64
|
||||||
Script string `json:"script"`
|
Script string
|
||||||
Stack []stackitem.Item `json:"stack"`
|
Stack []stackitem.Item
|
||||||
|
FaultException string
|
||||||
}
|
}
|
||||||
|
|
||||||
type invokeAux struct {
|
type invokeAux struct {
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
GasConsumed int64 `json:"gasconsumed,string"`
|
GasConsumed int64 `json:"gasconsumed,string"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Stack json.RawMessage `json:"stack"`
|
Stack json.RawMessage `json:"stack"`
|
||||||
|
FaultException string `json:"exception,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements json.Marshaler.
|
// MarshalJSON implements json.Marshaler.
|
||||||
|
@ -43,10 +45,11 @@ func (r Invoke) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json.Marshal(&invokeAux{
|
return json.Marshal(&invokeAux{
|
||||||
GasConsumed: r.GasConsumed,
|
GasConsumed: r.GasConsumed,
|
||||||
Script: r.Script,
|
Script: r.Script,
|
||||||
State: r.State,
|
State: r.State,
|
||||||
Stack: st,
|
Stack: st,
|
||||||
|
FaultException: r.FaultException,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,5 +75,6 @@ func (r *Invoke) UnmarshalJSON(data []byte) error {
|
||||||
r.GasConsumed = aux.GasConsumed
|
r.GasConsumed = aux.GasConsumed
|
||||||
r.Script = aux.Script
|
r.Script = aux.Script
|
||||||
r.State = aux.State
|
r.State = aux.State
|
||||||
|
r.FaultException = aux.FaultException
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -712,8 +712,14 @@ func (s *Server) getDecimals(contractID int32, cache map[int32]decimals) (decima
|
||||||
return decimals{}, fmt.Errorf("can't create script: %w", err)
|
return decimals{}, fmt.Errorf("can't create script: %w", err)
|
||||||
}
|
}
|
||||||
res := s.runScriptInVM(script, nil)
|
res := s.runScriptInVM(script, nil)
|
||||||
if res == nil || res.State != "HALT" || len(res.Stack) == 0 {
|
if res == nil {
|
||||||
return decimals{}, errors.New("execution error : no result")
|
return decimals{}, fmt.Errorf("execution error: no result")
|
||||||
|
}
|
||||||
|
if res.State != "HALT" {
|
||||||
|
return decimals{}, fmt.Errorf("execution error: bad VM state %s due to an error %s", res.State, res.FaultException)
|
||||||
|
}
|
||||||
|
if len(res.Stack) == 0 {
|
||||||
|
return decimals{}, fmt.Errorf("execution error: empty stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
d := decimals{Hash: h}
|
d := decimals{Hash: h}
|
||||||
|
@ -997,12 +1003,17 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu
|
||||||
vm := s.chain.GetTestVM(tx)
|
vm := s.chain.GetTestVM(tx)
|
||||||
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
vm.GasLimit = int64(s.config.MaxGasInvoke)
|
||||||
vm.LoadScriptWithFlags(script, smartcontract.All)
|
vm.LoadScriptWithFlags(script, smartcontract.All)
|
||||||
_ = vm.Run()
|
err := vm.Run()
|
||||||
|
var faultException string
|
||||||
|
if err != nil {
|
||||||
|
faultException = err.Error()
|
||||||
|
}
|
||||||
result := &result.Invoke{
|
result := &result.Invoke{
|
||||||
State: vm.State().String(),
|
State: vm.State().String(),
|
||||||
GasConsumed: vm.GasConsumed(),
|
GasConsumed: vm.GasConsumed(),
|
||||||
Script: hex.EncodeToString(script),
|
Script: hex.EncodeToString(script),
|
||||||
Stack: vm.Estack().ToArray(),
|
Stack: vm.Estack().ToArray(),
|
||||||
|
FaultException: faultException,
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue