rpc: add FaultException to result.Invoke

Close #1440
This commit is contained in:
Anna Shaleva 2020-10-05 16:33:20 +03:00
parent d27775acc5
commit 9a493dd2a0
6 changed files with 121 additions and 42 deletions

View file

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

View file

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

View file

@ -540,12 +540,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

View file

@ -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": {
{ {

View file

@ -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 State string
GasConsumed int64 GasConsumed int64
Script string Script string
Stack []stackitem.Item 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
} }

View file

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