core, rpc: support [invokefunction, invokescript, invokecontractverify]historic
This commit is contained in:
parent
f8b5972f61
commit
63c26ca270
10 changed files with 793 additions and 67 deletions
22
docs/rpc.md
22
docs/rpc.md
|
@ -168,6 +168,28 @@ block. It can be removed in future versions, but at the moment you can use it
|
|||
to see how much GAS is burned with particular block (because system fees are
|
||||
burned).
|
||||
|
||||
#### `invokecontractverifyhistoric`, `invokefunctionhistoric` and `invokescripthistoric` calls
|
||||
|
||||
These methods provide the ability of *historical* calls and accept block hash or
|
||||
block index or stateroot hash as the first parameter and the list of parameters
|
||||
that is the same as of `invokecontractverify`, `invokefunction` and
|
||||
`invokescript` correspondingly. The historical call assumes that the contracts'
|
||||
storage state has all its values got from MPT with the specified stateroot and
|
||||
the transaction will be invoked using interop context with block of the specified
|
||||
height. This allows to perform test invocation using the specified past chain
|
||||
state. These methods may be useful for debugging purposes.
|
||||
|
||||
Behavior note: any historical RPC call need the historical chain state to be
|
||||
presented in the node storage, thus if the node keeps only latest MPT state
|
||||
the historical call can not be handled properly.The historical calls only
|
||||
guaranteed to correctly work on archival node that stores all MPT data. If a
|
||||
node keeps the number of latest states and has the GC on (this setting
|
||||
corresponds to the `RemoveUntraceableBlocks` set to `true`), then the behaviour
|
||||
of historical RPC call is undefined. GC can always kick some data out of the
|
||||
storage while the historical call is executing, thus keep in mind that the call
|
||||
can be processed with `RemoveUntraceableBlocks` only with limitations on
|
||||
available data.
|
||||
|
||||
#### `submitnotaryrequest` call
|
||||
|
||||
This method can be used on P2P Notary enabled networks to submit new notary
|
||||
|
|
|
@ -2148,6 +2148,35 @@ func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *
|
|||
return systemInterop
|
||||
}
|
||||
|
||||
// GetTestHistoricVM returns an interop context with VM set up for a test run.
|
||||
func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) {
|
||||
if bc.config.KeepOnlyLatestState {
|
||||
return nil, errors.New("only latest state is supported")
|
||||
}
|
||||
if b == nil {
|
||||
return nil, errors.New("block is mandatory to produce test historic VM")
|
||||
}
|
||||
var mode = mpt.ModeAll
|
||||
if bc.config.RemoveUntraceableBlocks {
|
||||
if b.Index < bc.BlockHeight()-bc.config.MaxTraceableBlocks {
|
||||
return nil, fmt.Errorf("state for height %d is outdated and removed from the storage", b.Index)
|
||||
}
|
||||
mode |= mpt.ModeGCFlag
|
||||
}
|
||||
sr, err := bc.stateRoot.GetStateRoot(b.Index)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve stateroot for height %d: %w", b.Index, err)
|
||||
}
|
||||
s := mpt.NewTrieStore(sr.Root, mode, storage.NewPrivateMemCachedStore(bc.dao.Store))
|
||||
dTrie := dao.NewSimple(s, bc.config.StateRootInHeader, bc.config.P2PSigExtensions)
|
||||
dTrie.Version = bc.dao.Version
|
||||
systemInterop := bc.newInteropContext(t, dTrie, b, tx)
|
||||
vm := systemInterop.SpawnVM()
|
||||
vm.SetPriceGetter(systemInterop.GetPrice)
|
||||
vm.LoadToken = contract.LoadToken(systemInterop)
|
||||
return systemInterop, nil
|
||||
}
|
||||
|
||||
// Various witness verification errors.
|
||||
var (
|
||||
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||
|
|
|
@ -60,6 +60,7 @@ type Blockchainer interface {
|
|||
GetStateModule() StateRoot
|
||||
GetStorageItem(id int32, key []byte) state.StorageItem
|
||||
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context
|
||||
GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error)
|
||||
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||
SetOracle(service services.Oracle)
|
||||
mempool.Feer // fee interface
|
||||
|
|
|
@ -15,4 +15,5 @@ type StateRoot interface {
|
|||
GetState(root util.Uint256, key []byte) ([]byte, error)
|
||||
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
||||
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
||||
GetLatestStateHeight(root util.Uint256) (uint32, error)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package stateroot
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -101,6 +102,34 @@ func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) {
|
|||
return s.getStateRoot(makeStateRootKey(height))
|
||||
}
|
||||
|
||||
// GetLatestStateHeight returns the latest blockchain height by the given stateroot.
|
||||
func (s *Module) GetLatestStateHeight(root util.Uint256) (uint32, error) {
|
||||
rootBytes := root.BytesBE()
|
||||
rootStartOffset := 1 + 4 // stateroot version (1 byte) + stateroot index (4 bytes)
|
||||
rootEndOffset := rootStartOffset + util.Uint256Size
|
||||
var (
|
||||
h uint32
|
||||
found bool
|
||||
rootKey = makeStateRootKey(s.localHeight.Load())
|
||||
)
|
||||
s.Store.Seek(storage.SeekRange{
|
||||
Prefix: []byte{rootKey[0]}, // DataMPTAux
|
||||
Start: rootKey[1:], // Start is a value that should be appended to the Prefix
|
||||
Backwards: true,
|
||||
}, func(k, v []byte) bool {
|
||||
if len(k) == 5 && bytes.Equal(v[rootStartOffset:rootEndOffset], rootBytes) {
|
||||
h = binary.BigEndian.Uint32(k[1:]) // cut prefix DataMPTAux
|
||||
found = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if found {
|
||||
return h, nil
|
||||
}
|
||||
return h, storage.ErrKeyNotFound
|
||||
}
|
||||
|
||||
// CurrentLocalStateRoot returns hash of the local state root.
|
||||
func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
||||
return s.currentLocal.Load().(util.Uint256)
|
||||
|
|
|
@ -296,3 +296,20 @@ func checkVoteBroadcasted(t *testing.T, bc *core.Blockchain, p *payload.Extensib
|
|||
require.True(t, len(pubs) > int(valIndex))
|
||||
require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r))
|
||||
}
|
||||
|
||||
func TestStateroot_GetLatestStateHeight(t *testing.T) {
|
||||
bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
||||
c.P2PSigExtensions = true
|
||||
})
|
||||
e := neotest.NewExecutor(t, bc, validators, committee)
|
||||
initBasicChain(t, e)
|
||||
|
||||
m := bc.GetStateModule()
|
||||
for i := uint32(0); i < bc.BlockHeight(); i++ {
|
||||
r, err := m.GetStateRoot(i)
|
||||
require.NoError(t, err)
|
||||
h, err := bc.GetStateModule().GetLatestStateHeight(r.Root)
|
||||
require.NoError(t, err, i)
|
||||
require.Equal(t, i, h)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -588,6 +588,33 @@ func (c *Client) InvokeScript(script []byte, signers []transaction.Signer) (*res
|
|||
return c.invokeSomething("invokescript", p, signers)
|
||||
}
|
||||
|
||||
// InvokeScriptAtHeight returns the result of the given script after running it
|
||||
// true the VM using the provided chain state retrieved from the specified chain
|
||||
// height.
|
||||
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(height, script)
|
||||
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeScriptAtBlock returns the result of the given script after running it
|
||||
// true the VM using the provided chain state retrieved from the specified block
|
||||
// hash.
|
||||
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(blockHash.StringLE(), script)
|
||||
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeScriptWithState returns the result of the given script after running it
|
||||
// true the VM using the provided chain state retrieved from the specified
|
||||
// stateroot hash.
|
||||
// NOTE: This is a test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(stateroot.StringLE(), script)
|
||||
return c.invokeSomething("invokescripthistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeFunction returns the results after calling the smart contract scripthash
|
||||
// with the given operation and parameters.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
|
@ -596,6 +623,33 @@ func (c *Client) InvokeFunction(contract util.Uint160, operation string, params
|
|||
return c.invokeSomething("invokefunction", p, signers)
|
||||
}
|
||||
|
||||
// InvokeFunctionAtHeight returns the results after calling the smart contract
|
||||
// with the given operation and parameters at the given blockchain state
|
||||
// specified by the blockchain height.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(height, contract.StringLE(), operation, params)
|
||||
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeFunctionAtBlock returns the results after calling the smart contract
|
||||
// with the given operation and parameters at given the blockchain state
|
||||
// specified by the block hash.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(blockHash.StringLE(), contract.StringLE(), operation, params)
|
||||
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeFunctionWithState returns the results after calling the smart contract
|
||||
// with the given operation and parameters at the given blockchain state defined
|
||||
// by the specified stateroot hash.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(stateroot.StringLE(), contract.StringLE(), operation, params)
|
||||
return c.invokeSomething("invokefunctionhistoric", p, signers)
|
||||
}
|
||||
|
||||
// InvokeContractVerify returns the results after calling `verify` method of the smart contract
|
||||
// with the given parameters under verification trigger type.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
|
@ -604,6 +658,33 @@ func (c *Client) InvokeContractVerify(contract util.Uint160, params []smartcontr
|
|||
return c.invokeSomething("invokecontractverify", p, signers, witnesses...)
|
||||
}
|
||||
|
||||
// InvokeContractVerifyAtHeight returns the results after calling `verify` method
|
||||
// of the smart contract with the given parameters under verification trigger type
|
||||
// at the blockchain state specified by the blockchain height.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(height, contract.StringLE(), params)
|
||||
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||
}
|
||||
|
||||
// InvokeContractVerifyAtBlock returns the results after calling `verify` method
|
||||
// of the smart contract with the given parameters under verification trigger type
|
||||
// at the blockchain state specified by the block hash.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(blockHash.StringLE(), contract.StringLE(), params)
|
||||
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||
}
|
||||
|
||||
// InvokeContractVerifyWithState returns the results after calling `verify` method
|
||||
// of the smart contract with the given parameters under verification trigger type
|
||||
// at the blockchain state specified by the stateroot hash.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
var p = request.NewRawParams(stateroot.StringLE(), contract.StringLE(), params)
|
||||
return c.invokeSomething("invokecontractverifyhistoric", p, signers, witnesses...)
|
||||
}
|
||||
|
||||
// invokeSomething is an inner wrapper for Invoke* functions.
|
||||
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
var resp = new(result.Invoke)
|
||||
|
|
|
@ -766,6 +766,56 @@ func TestInvokeVerify(t *testing.T) {
|
|||
require.True(t, res.Stack[0].Value().(bool))
|
||||
})
|
||||
|
||||
t.Run("positive, historic, by height, with signer", func(t *testing.T) {
|
||||
h := chain.BlockHeight() - 1
|
||||
res, err := c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.True(t, res.Stack[0].Value().(bool))
|
||||
})
|
||||
|
||||
t.Run("positive, historic, by block, with signer", func(t *testing.T) {
|
||||
res, err := c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(int(chain.BlockHeight())-1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.True(t, res.Stack[0].Value().(bool))
|
||||
})
|
||||
|
||||
t.Run("positive, historic, by stateroot, with signer", func(t *testing.T) {
|
||||
h := chain.BlockHeight() - 1
|
||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||
require.NoError(t, err)
|
||||
res, err := c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.True(t, res.Stack[0].Value().(bool))
|
||||
})
|
||||
|
||||
t.Run("bad, historic, by hash: contract not found", func(t *testing.T) {
|
||||
var h uint32 = 1
|
||||
_, err = c.InvokeContractVerifyAtHeight(h, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.Error(t, err)
|
||||
// TODO: check that error is `ErrUnknownVerificationContract`
|
||||
})
|
||||
|
||||
t.Run("bad, historic, by block: contract not found", func(t *testing.T) {
|
||||
_, err = c.InvokeContractVerifyAtBlock(chain.GetHeaderHash(1), contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.Error(t, err)
|
||||
// TODO: check that error is `ErrUnknownVerificationContract`
|
||||
})
|
||||
|
||||
t.Run("bad, historic, by stateroot: contract not found", func(t *testing.T) {
|
||||
var h uint32 = 1
|
||||
sr, err := chain.GetStateModule().GetStateRoot(h)
|
||||
require.NoError(t, err)
|
||||
_, err = c.InvokeContractVerifyWithState(sr.Root, contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}})
|
||||
require.Error(t, err)
|
||||
// TODO: check that error is `ErrUnknownVerificationContract`
|
||||
})
|
||||
|
||||
t.Run("positive, with signer and witness", func(t *testing.T) {
|
||||
res, err := c.InvokeContractVerify(contract, smartcontract.Params{}, []transaction.Signer{{Account: testchain.PrivateKeyByID(0).PublicKey().GetScriptHash()}}, transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.RET)}})
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/iterator"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||
|
@ -140,8 +141,11 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
|
|||
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
||||
"getversion": (*Server).getVersion,
|
||||
"invokefunction": (*Server).invokeFunction,
|
||||
"invokefunctionhistoric": (*Server).invokeFunctionHistoric,
|
||||
"invokescript": (*Server).invokescript,
|
||||
"invokescripthistoric": (*Server).invokescripthistoric,
|
||||
"invokecontractverify": (*Server).invokeContractVerify,
|
||||
"invokecontractverifyhistoric": (*Server).invokeContractVerifyHistoric,
|
||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||
"submitblock": (*Server).submitBlock,
|
||||
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
||||
|
@ -866,7 +870,7 @@ func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method stri
|
|||
}
|
||||
script := bw.Bytes()
|
||||
tx := &transaction.Transaction{Script: script}
|
||||
b, err := s.getFakeNextBlock()
|
||||
b, err := s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -1571,16 +1575,40 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
|
|||
|
||||
// invokeFunction implements the `invokeFunction` RPC call.
|
||||
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
||||
tx, verbose, respErr := s.getInvokeFunctionParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, nil, verbose)
|
||||
}
|
||||
|
||||
// invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call.
|
||||
func (s *Server) invokeFunctionHistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||
b, respErr := s.getHistoricParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
if len(reqParams) < 2 {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
tx, verbose, respErr := s.getInvokeFunctionParams(reqParams[1:])
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
|
||||
}
|
||||
|
||||
func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
|
||||
if len(reqParams) < 2 {
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||
if responseErr != nil {
|
||||
return nil, responseErr
|
||||
return nil, false, responseErr
|
||||
}
|
||||
method, err := reqParams[1].GetString()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
var params *request.Param
|
||||
if len(reqParams) > 2 {
|
||||
|
@ -1590,7 +1618,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
if len(reqParams) > 3 {
|
||||
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
}
|
||||
|
@ -1598,7 +1626,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
if len(reqParams) > 4 {
|
||||
verbose, err = reqParams[4].GetBoolean()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
}
|
||||
if len(tx.Signers) == 0 {
|
||||
|
@ -1606,28 +1634,48 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
|||
}
|
||||
script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't create invocation script", err)
|
||||
return nil, false, response.NewInternalServerError("can't create invocation script", err)
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
||||
return tx, verbose, nil
|
||||
}
|
||||
|
||||
// invokescript implements the `invokescript` RPC call.
|
||||
func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
|
||||
if len(reqParams) < 1 {
|
||||
tx, verbose, respErr := s.getInvokeScriptParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, nil, verbose)
|
||||
}
|
||||
|
||||
// invokescripthistoric implements the `invokescripthistoric` RPC call.
|
||||
func (s *Server) invokescripthistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||
b, respErr := s.getHistoricParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
if len(reqParams) < 2 {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
tx, verbose, respErr := s.getInvokeScriptParams(reqParams[1:])
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
|
||||
}
|
||||
|
||||
script, err := reqParams[0].GetBytesBase64()
|
||||
func (s *Server) getInvokeScriptParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
|
||||
script, err := reqParams.Value(0).GetBytesBase64()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
|
||||
tx := &transaction.Transaction{}
|
||||
if len(reqParams) > 1 {
|
||||
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
tx.Scripts = witnesses
|
||||
|
@ -1636,33 +1684,57 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
|||
if len(reqParams) > 2 {
|
||||
verbose, err = reqParams[2].GetBoolean()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return nil, false, response.ErrInvalidParams
|
||||
}
|
||||
}
|
||||
if len(tx.Signers) == 0 {
|
||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||
}
|
||||
tx.Script = script
|
||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
||||
return tx, verbose, nil
|
||||
}
|
||||
|
||||
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||
func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
|
||||
scriptHash, tx, invocationScript, respErr := s.getInvokeContractVerifyParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, nil, false)
|
||||
}
|
||||
|
||||
// invokeContractVerifyHistoric implements the `invokecontractverifyhistoric` RPC call.
|
||||
func (s *Server) invokeContractVerifyHistoric(reqParams request.Params) (interface{}, *response.Error) {
|
||||
b, respErr := s.getHistoricParams(reqParams)
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
if len(reqParams) < 2 {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
scriptHash, tx, invocationScript, respErr := s.getInvokeContractVerifyParams(reqParams[1:])
|
||||
if respErr != nil {
|
||||
return nil, respErr
|
||||
}
|
||||
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, b, false)
|
||||
}
|
||||
|
||||
func (s *Server) getInvokeContractVerifyParams(reqParams request.Params) (util.Uint160, *transaction.Transaction, []byte, *response.Error) {
|
||||
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||
if responseErr != nil {
|
||||
return nil, responseErr
|
||||
return util.Uint160{}, nil, nil, responseErr
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
if len(reqParams) > 1 {
|
||||
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
||||
if err != nil {
|
||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||
return util.Uint160{}, nil, nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||
}
|
||||
if len(args) > 0 {
|
||||
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
||||
if err != nil {
|
||||
return nil, response.NewRPCError("can't create witness invocation script", err.Error(), err)
|
||||
return util.Uint160{}, nil, nil, response.NewRPCError("can't create witness invocation script", err.Error(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1672,7 +1744,7 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
|||
if len(reqParams) > 2 {
|
||||
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||
if err != nil {
|
||||
return nil, response.ErrInvalidParams
|
||||
return util.Uint160{}, nil, nil, response.ErrInvalidParams
|
||||
}
|
||||
tx.Signers = signers
|
||||
tx.Scripts = witnesses
|
||||
|
@ -1680,16 +1752,51 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
|||
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
||||
tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}}
|
||||
}
|
||||
return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, false)
|
||||
return scriptHash, tx, invocationScript, nil
|
||||
}
|
||||
|
||||
func (s *Server) getFakeNextBlock() (*block.Block, error) {
|
||||
// getHistoricParams checks that historic calls are supported and returns fake block
|
||||
// with the specified index to perform the historic call. It also checks that
|
||||
// specified stateroot is stored at the specified height for further request
|
||||
// handling consistency.
|
||||
func (s *Server) getHistoricParams(reqParams request.Params) (*block.Block, *response.Error) {
|
||||
if s.chain.GetConfig().KeepOnlyLatestState {
|
||||
return nil, response.NewInvalidRequestError("only latest state is supported", errKeepOnlyLatestState)
|
||||
}
|
||||
if len(reqParams) < 1 {
|
||||
return nil, response.ErrInvalidParams
|
||||
}
|
||||
height, respErr := s.blockHeightFromParam(reqParams.Value(0))
|
||||
if respErr != nil {
|
||||
hash, err := reqParams.Value(0).GetUint256()
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError("invalid block hash or index or stateroot hash", err)
|
||||
}
|
||||
b, err := s.chain.GetBlock(hash)
|
||||
if err != nil {
|
||||
stateH, err := s.chain.GetStateModule().GetLatestStateHeight(hash)
|
||||
if err != nil {
|
||||
return nil, response.NewInvalidParamsError(fmt.Sprintf("unknown block or stateroot: %s", err), err)
|
||||
}
|
||||
height = int(stateH)
|
||||
} else {
|
||||
height = int(b.Index)
|
||||
}
|
||||
}
|
||||
b, err := s.getFakeNextBlock(uint32(height))
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError(fmt.Sprintf("can't create fake block for height %d: %s", height, err.Error()), err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (s *Server) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) {
|
||||
// When transferring funds, script execution does no auto GAS claim,
|
||||
// because it depends on persisting tx height.
|
||||
// This is why we provide block here.
|
||||
b := block.New(s.stateRootEnabled)
|
||||
b.Index = s.chain.BlockHeight() + 1
|
||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight())))
|
||||
b.Index = nextBlockHeight
|
||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(nextBlockHeight - 1)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1702,12 +1809,23 @@ func (s *Server) getFakeNextBlock() (*block.Block, error) {
|
|||
// witness invocation script in case of `verification` trigger (it pushes `verify`
|
||||
// arguments on stack before verification). In case of contract verification
|
||||
// contractScriptHash should be specified.
|
||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, verbose bool) (*result.Invoke, *response.Error) {
|
||||
b, err := s.getFakeNextBlock()
|
||||
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*result.Invoke, *response.Error) {
|
||||
var (
|
||||
err error
|
||||
ic *interop.Context
|
||||
)
|
||||
if b == nil {
|
||||
b, err = s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't create fake block", err)
|
||||
}
|
||||
ic := s.chain.GetTestVM(t, tx, b)
|
||||
ic = s.chain.GetTestVM(t, tx, b)
|
||||
} else {
|
||||
ic, err = s.chain.GetTestHistoricVM(t, tx, b)
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("failed to create historic VM", err)
|
||||
}
|
||||
}
|
||||
if verbose {
|
||||
ic.VM.EnableInvocationTree()
|
||||
}
|
||||
|
@ -1720,7 +1838,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
|
|||
ic.VM.GasLimit = gasPolicy
|
||||
}
|
||||
|
||||
err := s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
|
||||
err = s.chain.InitVerificationContext(ic, contractScriptHash, &transaction.Witness{InvocationScript: script, VerificationScript: []byte{}})
|
||||
if err != nil {
|
||||
return nil, response.NewInternalServerError("can't prepare verification VM", err)
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ const (
|
|||
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||
block20StateRootLE = "19ec3c3d01afe5274e8bb4a393c97da708c5608c5b0ad116c16108b6a04fb08e"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -999,6 +1000,134 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
fail: true,
|
||||
},
|
||||
},
|
||||
"invokefunctionhistoric": {
|
||||
{
|
||||
name: "positive, by index",
|
||||
params: `[20, "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.NotNil(t, res.Script)
|
||||
assert.NotEqual(t, "", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, by stateroot",
|
||||
params: `["` + block20StateRootLE + `", "50befd26fdf6e4d957c11e078b24ebce6291456f", "test", []]`,
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.NotNil(t, res.Script)
|
||||
assert.NotEqual(t, "", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, with notifications",
|
||||
params: `[20, "` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`,
|
||||
result: func(e *executor) interface{} {
|
||||
script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 32167260,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make(true)},
|
||||
Notifications: []state.NotificationEvent{{
|
||||
ScriptHash: nnsHash,
|
||||
Name: "Transfer",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.Make([]byte{0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x08, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}),
|
||||
stackitem.Make([]byte{0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b}),
|
||||
stackitem.Make(1),
|
||||
stackitem.Make("neo.com"),
|
||||
}),
|
||||
}},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, verbose",
|
||||
params: `[20, "` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`,
|
||||
result: func(e *executor) interface{} {
|
||||
script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52}
|
||||
stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib)
|
||||
cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib)
|
||||
return &result.Invoke{
|
||||
State: "HALT",
|
||||
GasConsumed: 15928320,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{stackitem.Make("1.2.3.4")},
|
||||
Notifications: []state.NotificationEvent{},
|
||||
Diagnostics: &result.InvokeDiag{
|
||||
Changes: []storage.Operation{},
|
||||
Invocations: []*vm.InvocationTree{{
|
||||
Current: hash.Hash160(script),
|
||||
Calls: []*vm.InvocationTree{
|
||||
{
|
||||
Current: nnsHash,
|
||||
Calls: []*vm.InvocationTree{
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: cryptoHash,
|
||||
},
|
||||
{
|
||||
Current: stdHash,
|
||||
},
|
||||
{
|
||||
Current: cryptoHash,
|
||||
},
|
||||
{
|
||||
Current: cryptoHash,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
params: `[]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "no args",
|
||||
params: `[20]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "not a string",
|
||||
params: `[20, 42, "test", []]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "not a scripthash",
|
||||
params: `[20,"qwerty", "test", []]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bad params",
|
||||
params: `[20,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": "qwerty"}]]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bad height",
|
||||
params: `[100500,"50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bad stateroot",
|
||||
params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","50befd26fdf6e4d957c11e078b24ebce6291456f", "test", [{"type": "Integer", "value": 1}]]`,
|
||||
fail: true,
|
||||
},
|
||||
},
|
||||
"invokescript": {
|
||||
{
|
||||
name: "positive",
|
||||
|
@ -1098,6 +1227,132 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
fail: true,
|
||||
},
|
||||
},
|
||||
"invokescripthistoric": {
|
||||
{
|
||||
name: "positive, by index",
|
||||
params: `[20,"UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.NotEqual(t, "", res.Script)
|
||||
assert.NotEqual(t, "", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, by stateroot",
|
||||
params: `["` + block20StateRootLE + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.NotEqual(t, "", res.Script)
|
||||
assert.NotEqual(t, "", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive,verbose",
|
||||
params: `[20, "UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY=",[],true]`,
|
||||
result: func(e *executor) interface{} {
|
||||
script := []byte{0x51, 0xc5, 0x6b, 0xd, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x68, 0xf, 0x4e, 0x65, 0x6f, 0x2e, 0x52, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x4c, 0x6f, 0x67, 0x61, 0x6c, 0x75, 0x66}
|
||||
return &result.Invoke{
|
||||
State: "FAULT",
|
||||
GasConsumed: 60,
|
||||
Script: script,
|
||||
Stack: []stackitem.Item{},
|
||||
FaultException: "at instruction 0 (ROT): too big index",
|
||||
Notifications: []state.NotificationEvent{},
|
||||
Diagnostics: &result.InvokeDiag{
|
||||
Changes: []storage.Operation{},
|
||||
Invocations: []*vm.InvocationTree{{
|
||||
Current: hash.Hash160(script),
|
||||
}},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, good witness",
|
||||
// script is base64-encoded `invokescript_contract.avm` representation, hashes are hex-encoded LE bytes of hashes used in the contract with `0x` prefix
|
||||
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c01","0x090c060e00010205040307030102000902030f0d"]]`, invokescriptContractAVM),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.Equal(t, big.NewInt(3), res.Stack[0].Value())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, bad witness of second hash",
|
||||
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c01"]]`, invokescriptContractAVM),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.Equal(t, big.NewInt(2), res.Stack[0].Value())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, no good hashes",
|
||||
params: fmt.Sprintf(`[20,"%s"]`, invokescriptContractAVM),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
require.Equal(t, 1, len(res.Stack))
|
||||
require.Equal(t, big.NewInt(1), res.Stack[0].Value())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, bad hashes witness",
|
||||
params: fmt.Sprintf(`[20,"%s",["0x0000000009070e030d0f0e020d0c06050e030c02"]]`, invokescriptContractAVM),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
assert.Equal(t, 1, len(res.Stack))
|
||||
assert.Equal(t, big.NewInt(1), res.Stack[0].Value())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
params: `[]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "no script",
|
||||
params: `[20]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "not a string",
|
||||
params: `[20,42]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bas string",
|
||||
params: `[20, "qwerty"]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bas height",
|
||||
params: `[100500,"qwerty"]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "bas stateroot",
|
||||
params: `["` + util.Uint256{1, 2, 3}.StringLE() + `","UcVrDUhlbGxvLCB3b3JsZCFoD05lby5SdW50aW1lLkxvZ2FsdWY="]`,
|
||||
fail: true,
|
||||
},
|
||||
},
|
||||
"invokecontractverify": {
|
||||
{
|
||||
name: "positive",
|
||||
|
@ -1203,6 +1458,129 @@ var rpcTestCases = map[string][]rpcTestCase{
|
|||
fail: true,
|
||||
},
|
||||
},
|
||||
"invokecontractverifyhistoric": {
|
||||
{
|
||||
name: "positive, by index",
|
||||
params: fmt.Sprintf(`[20,"%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address()))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, by stateroot",
|
||||
params: fmt.Sprintf(`["`+block20StateRootLE+`","%s", [], [{"account":"%s"}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, res.Script) // empty witness invocation script (pushes args of `verify` on stack, but this `verify` don't have args)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, true, res.Stack[0].Value().(bool), fmt.Sprintf("check address in verification_contract.go: expected %s", testchain.PrivateKeyByID(0).Address()))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, no signers",
|
||||
params: fmt.Sprintf(`[20,"%s", []]`, verifyContractHash),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, res.Script)
|
||||
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, no arguments",
|
||||
params: fmt.Sprintf(`[20,"%s"]`, verifyContractHash),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, res.Script)
|
||||
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, with signers and scripts",
|
||||
params: fmt.Sprintf(`[20,"%s", [], [{"account":"%s", "invocation":"MQo=", "verification": ""}]]`, verifyContractHash, testchain.PrivateKeyByID(0).PublicKey().GetScriptHash().StringLE()),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
assert.Nil(t, res.Script)
|
||||
assert.Equal(t, "HALT", res.State)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, with arguments, result=true",
|
||||
params: fmt.Sprintf(`[20,"%s", [{"type": "String", "value": "good_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": false}]]`, verifyWithArgsContractHash),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
expectedInvScript := io.NewBufBinWriter()
|
||||
emit.Int(expectedInvScript.BinWriter, 0)
|
||||
emit.Int(expectedInvScript.BinWriter, int64(4))
|
||||
emit.String(expectedInvScript.BinWriter, "good_string")
|
||||
require.NoError(t, expectedInvScript.Err)
|
||||
assert.Equal(t, expectedInvScript.Bytes(), res.Script) // witness invocation script (pushes args of `verify` on stack)
|
||||
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, true, res.Stack[0].Value().(bool))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive, with arguments, result=false",
|
||||
params: fmt.Sprintf(`[20, "%s", [{"type": "String", "value": "invalid_string"}, {"type": "Integer", "value": "4"}, {"type":"Boolean", "value": false}]]`, verifyWithArgsContractHash),
|
||||
result: func(e *executor) interface{} { return &result.Invoke{} },
|
||||
check: func(t *testing.T, e *executor, inv interface{}) {
|
||||
res, ok := inv.(*result.Invoke)
|
||||
require.True(t, ok)
|
||||
expectedInvScript := io.NewBufBinWriter()
|
||||
emit.Int(expectedInvScript.BinWriter, 0)
|
||||
emit.Int(expectedInvScript.BinWriter, int64(4))
|
||||
emit.String(expectedInvScript.BinWriter, "invalid_string")
|
||||
require.NoError(t, expectedInvScript.Err)
|
||||
assert.Equal(t, expectedInvScript.Bytes(), res.Script)
|
||||
assert.Equal(t, "HALT", res.State, res.FaultException)
|
||||
assert.NotEqual(t, 0, res.GasConsumed)
|
||||
assert.Equal(t, false, res.Stack[0].Value().(bool))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unknown contract",
|
||||
params: fmt.Sprintf(`[20, "%s", []]`, util.Uint160{}.String()),
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "no params",
|
||||
params: `[]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "no args",
|
||||
params: `[20]`,
|
||||
fail: true,
|
||||
},
|
||||
{
|
||||
name: "not a string",
|
||||
params: `[20,42, []]`,
|
||||
fail: true,
|
||||
},
|
||||
},
|
||||
"sendrawtransaction": {
|
||||
{
|
||||
name: "positive",
|
||||
|
|
Loading…
Reference in a new issue