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
|
to see how much GAS is burned with particular block (because system fees are
|
||||||
burned).
|
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
|
#### `submitnotaryrequest` call
|
||||||
|
|
||||||
This method can be used on P2P Notary enabled networks to submit new notary
|
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
|
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.
|
// Various witness verification errors.
|
||||||
var (
|
var (
|
||||||
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||||
|
|
|
@ -60,6 +60,7 @@ type Blockchainer interface {
|
||||||
GetStateModule() StateRoot
|
GetStateModule() StateRoot
|
||||||
GetStorageItem(id int32, key []byte) state.StorageItem
|
GetStorageItem(id int32, key []byte) state.StorageItem
|
||||||
GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context
|
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)
|
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
|
||||||
SetOracle(service services.Oracle)
|
SetOracle(service services.Oracle)
|
||||||
mempool.Feer // fee interface
|
mempool.Feer // fee interface
|
||||||
|
|
|
@ -15,4 +15,5 @@ type StateRoot interface {
|
||||||
GetState(root util.Uint256, key []byte) ([]byte, error)
|
GetState(root util.Uint256, key []byte) ([]byte, error)
|
||||||
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
GetStateProof(root util.Uint256, key []byte) ([][]byte, error)
|
||||||
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
GetStateRoot(height uint32) (*state.MPTRoot, error)
|
||||||
|
GetLatestStateHeight(root util.Uint256) (uint32, error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package stateroot
|
package stateroot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -101,6 +102,34 @@ func (s *Module) GetStateRoot(height uint32) (*state.MPTRoot, error) {
|
||||||
return s.getStateRoot(makeStateRootKey(height))
|
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.
|
// CurrentLocalStateRoot returns hash of the local state root.
|
||||||
func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
func (s *Module) CurrentLocalStateRoot() util.Uint256 {
|
||||||
return s.currentLocal.Load().(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, len(pubs) > int(valIndex))
|
||||||
require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r))
|
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)
|
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
|
// InvokeFunction returns the results after calling the smart contract scripthash
|
||||||
// with the given operation and parameters.
|
// with the given operation and parameters.
|
||||||
// NOTE: this is test invoke and will not affect the blockchain.
|
// 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)
|
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
|
// InvokeContractVerify returns the results after calling `verify` method of the smart contract
|
||||||
// with the given parameters under verification trigger type.
|
// with the given parameters under verification trigger type.
|
||||||
// NOTE: this is test invoke and will not affect the blockchain.
|
// 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...)
|
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.
|
// 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) {
|
func (c *Client) invokeSomething(method string, p request.RawParams, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||||
var resp = new(result.Invoke)
|
var resp = new(result.Invoke)
|
||||||
|
|
|
@ -766,6 +766,56 @@ func TestInvokeVerify(t *testing.T) {
|
||||||
require.True(t, res.Stack[0].Value().(bool))
|
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) {
|
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)}})
|
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)
|
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/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
|
"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/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/interop/iterator"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
"github.com/nspcc-dev/neo-go/pkg/core/mempoolevent"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||||
|
@ -108,46 +109,49 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
|
||||||
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
"calculatenetworkfee": (*Server).calculateNetworkFee,
|
||||||
"findstates": (*Server).findStates,
|
"findstates": (*Server).findStates,
|
||||||
"getapplicationlog": (*Server).getApplicationLog,
|
"getapplicationlog": (*Server).getApplicationLog,
|
||||||
"getbestblockhash": (*Server).getBestBlockHash,
|
"getbestblockhash": (*Server).getBestBlockHash,
|
||||||
"getblock": (*Server).getBlock,
|
"getblock": (*Server).getBlock,
|
||||||
"getblockcount": (*Server).getBlockCount,
|
"getblockcount": (*Server).getBlockCount,
|
||||||
"getblockhash": (*Server).getBlockHash,
|
"getblockhash": (*Server).getBlockHash,
|
||||||
"getblockheader": (*Server).getBlockHeader,
|
"getblockheader": (*Server).getBlockHeader,
|
||||||
"getblockheadercount": (*Server).getBlockHeaderCount,
|
"getblockheadercount": (*Server).getBlockHeaderCount,
|
||||||
"getblocksysfee": (*Server).getBlockSysFee,
|
"getblocksysfee": (*Server).getBlockSysFee,
|
||||||
"getcommittee": (*Server).getCommittee,
|
"getcommittee": (*Server).getCommittee,
|
||||||
"getconnectioncount": (*Server).getConnectionCount,
|
"getconnectioncount": (*Server).getConnectionCount,
|
||||||
"getcontractstate": (*Server).getContractState,
|
"getcontractstate": (*Server).getContractState,
|
||||||
"getnativecontracts": (*Server).getNativeContracts,
|
"getnativecontracts": (*Server).getNativeContracts,
|
||||||
"getnep11balances": (*Server).getNEP11Balances,
|
"getnep11balances": (*Server).getNEP11Balances,
|
||||||
"getnep11properties": (*Server).getNEP11Properties,
|
"getnep11properties": (*Server).getNEP11Properties,
|
||||||
"getnep11transfers": (*Server).getNEP11Transfers,
|
"getnep11transfers": (*Server).getNEP11Transfers,
|
||||||
"getnep17balances": (*Server).getNEP17Balances,
|
"getnep17balances": (*Server).getNEP17Balances,
|
||||||
"getnep17transfers": (*Server).getNEP17Transfers,
|
"getnep17transfers": (*Server).getNEP17Transfers,
|
||||||
"getpeers": (*Server).getPeers,
|
"getpeers": (*Server).getPeers,
|
||||||
"getproof": (*Server).getProof,
|
"getproof": (*Server).getProof,
|
||||||
"getrawmempool": (*Server).getRawMempool,
|
"getrawmempool": (*Server).getRawMempool,
|
||||||
"getrawtransaction": (*Server).getrawtransaction,
|
"getrawtransaction": (*Server).getrawtransaction,
|
||||||
"getstate": (*Server).getState,
|
"getstate": (*Server).getState,
|
||||||
"getstateheight": (*Server).getStateHeight,
|
"getstateheight": (*Server).getStateHeight,
|
||||||
"getstateroot": (*Server).getStateRoot,
|
"getstateroot": (*Server).getStateRoot,
|
||||||
"getstorage": (*Server).getStorage,
|
"getstorage": (*Server).getStorage,
|
||||||
"gettransactionheight": (*Server).getTransactionHeight,
|
"gettransactionheight": (*Server).getTransactionHeight,
|
||||||
"getunclaimedgas": (*Server).getUnclaimedGas,
|
"getunclaimedgas": (*Server).getUnclaimedGas,
|
||||||
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
"getnextblockvalidators": (*Server).getNextBlockValidators,
|
||||||
"getversion": (*Server).getVersion,
|
"getversion": (*Server).getVersion,
|
||||||
"invokefunction": (*Server).invokeFunction,
|
"invokefunction": (*Server).invokeFunction,
|
||||||
"invokescript": (*Server).invokescript,
|
"invokefunctionhistoric": (*Server).invokeFunctionHistoric,
|
||||||
"invokecontractverify": (*Server).invokeContractVerify,
|
"invokescript": (*Server).invokescript,
|
||||||
"sendrawtransaction": (*Server).sendrawtransaction,
|
"invokescripthistoric": (*Server).invokescripthistoric,
|
||||||
"submitblock": (*Server).submitBlock,
|
"invokecontractverify": (*Server).invokeContractVerify,
|
||||||
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
"invokecontractverifyhistoric": (*Server).invokeContractVerifyHistoric,
|
||||||
"submitoracleresponse": (*Server).submitOracleResponse,
|
"sendrawtransaction": (*Server).sendrawtransaction,
|
||||||
"validateaddress": (*Server).validateAddress,
|
"submitblock": (*Server).submitBlock,
|
||||||
"verifyproof": (*Server).verifyProof,
|
"submitnotaryrequest": (*Server).submitNotaryRequest,
|
||||||
|
"submitoracleresponse": (*Server).submitOracleResponse,
|
||||||
|
"validateaddress": (*Server).validateAddress,
|
||||||
|
"verifyproof": (*Server).verifyProof,
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
|
var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
|
||||||
|
@ -866,7 +870,7 @@ func (s *Server) invokeReadOnly(bw *io.BufBinWriter, h util.Uint160, method stri
|
||||||
}
|
}
|
||||||
script := bw.Bytes()
|
script := bw.Bytes()
|
||||||
tx := &transaction.Transaction{Script: script}
|
tx := &transaction.Transaction{Script: script}
|
||||||
b, err := s.getFakeNextBlock()
|
b, err := s.getFakeNextBlock(s.chain.BlockHeight() + 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -1571,16 +1575,40 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
|
||||||
|
|
||||||
// invokeFunction implements the `invokeFunction` RPC call.
|
// invokeFunction implements the `invokeFunction` RPC call.
|
||||||
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
|
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 {
|
if len(reqParams) < 2 {
|
||||||
return nil, response.ErrInvalidParams
|
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))
|
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||||
if responseErr != nil {
|
if responseErr != nil {
|
||||||
return nil, responseErr
|
return nil, false, responseErr
|
||||||
}
|
}
|
||||||
method, err := reqParams[1].GetString()
|
method, err := reqParams[1].GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
var params *request.Param
|
var params *request.Param
|
||||||
if len(reqParams) > 2 {
|
if len(reqParams) > 2 {
|
||||||
|
@ -1590,7 +1618,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
if len(reqParams) > 3 {
|
if len(reqParams) > 3 {
|
||||||
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
signers, _, err := reqParams[3].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
}
|
}
|
||||||
|
@ -1598,7 +1626,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
|
||||||
if len(reqParams) > 4 {
|
if len(reqParams) > 4 {
|
||||||
verbose, err = reqParams[4].GetBoolean()
|
verbose, err = reqParams[4].GetBoolean()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tx.Signers) == 0 {
|
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)
|
script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
|
||||||
if err != nil {
|
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
|
tx.Script = script
|
||||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
return tx, verbose, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokescript implements the `invokescript` RPC call.
|
// invokescript implements the `invokescript` RPC call.
|
||||||
func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
|
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
|
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 {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := &transaction.Transaction{}
|
tx := &transaction.Transaction{}
|
||||||
if len(reqParams) > 1 {
|
if len(reqParams) > 1 {
|
||||||
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
signers, witnesses, err := reqParams[1].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
tx.Scripts = witnesses
|
tx.Scripts = witnesses
|
||||||
|
@ -1636,33 +1684,57 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
|
||||||
if len(reqParams) > 2 {
|
if len(reqParams) > 2 {
|
||||||
verbose, err = reqParams[2].GetBoolean()
|
verbose, err = reqParams[2].GetBoolean()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return nil, false, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tx.Signers) == 0 {
|
if len(tx.Signers) == 0 {
|
||||||
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
|
||||||
}
|
}
|
||||||
tx.Script = script
|
tx.Script = script
|
||||||
return s.runScriptInVM(trigger.Application, script, util.Uint160{}, tx, verbose)
|
return tx, verbose, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
// invokeContractVerify implements the `invokecontractverify` RPC call.
|
||||||
func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
|
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))
|
scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
|
||||||
if responseErr != nil {
|
if responseErr != nil {
|
||||||
return nil, responseErr
|
return util.Uint160{}, nil, nil, responseErr
|
||||||
}
|
}
|
||||||
|
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
if len(reqParams) > 1 {
|
if len(reqParams) > 1 {
|
||||||
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
args, err := reqParams[1].GetArray() // second `invokecontractverify` parameter is an array of arguments for `verify` method
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
return util.Uint160{}, nil, nil, response.WrapErrorWithData(response.ErrInvalidParams, err)
|
||||||
}
|
}
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
err := request.ExpandArrayIntoScript(bw.BinWriter, args)
|
||||||
if err != nil {
|
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 {
|
if len(reqParams) > 2 {
|
||||||
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
signers, witnesses, err := reqParams[2].GetSignersWithWitnesses()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, response.ErrInvalidParams
|
return util.Uint160{}, nil, nil, response.ErrInvalidParams
|
||||||
}
|
}
|
||||||
tx.Signers = signers
|
tx.Signers = signers
|
||||||
tx.Scripts = witnesses
|
tx.Scripts = witnesses
|
||||||
|
@ -1680,16 +1752,51 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
|
||||||
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
tx.Signers = []transaction.Signer{{Account: scriptHash}}
|
||||||
tx.Scripts = []transaction.Witness{{InvocationScript: invocationScript, VerificationScript: []byte{}}}
|
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,
|
// When transferring funds, script execution does no auto GAS claim,
|
||||||
// because it depends on persisting tx height.
|
// because it depends on persisting tx height.
|
||||||
// This is why we provide block here.
|
// This is why we provide block here.
|
||||||
b := block.New(s.stateRootEnabled)
|
b := block.New(s.stateRootEnabled)
|
||||||
b.Index = s.chain.BlockHeight() + 1
|
b.Index = nextBlockHeight
|
||||||
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight())))
|
hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(nextBlockHeight - 1)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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`
|
// witness invocation script in case of `verification` trigger (it pushes `verify`
|
||||||
// arguments on stack before verification). In case of contract verification
|
// arguments on stack before verification). In case of contract verification
|
||||||
// contractScriptHash should be specified.
|
// 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) {
|
func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*result.Invoke, *response.Error) {
|
||||||
b, err := s.getFakeNextBlock()
|
var (
|
||||||
if err != nil {
|
err error
|
||||||
return nil, response.NewInternalServerError("can't create fake block", err)
|
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)
|
||||||
|
} else {
|
||||||
|
ic, err = s.chain.GetTestHistoricVM(t, tx, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewInternalServerError("failed to create historic VM", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ic := s.chain.GetTestVM(t, tx, b)
|
|
||||||
if verbose {
|
if verbose {
|
||||||
ic.VM.EnableInvocationTree()
|
ic.VM.EnableInvocationTree()
|
||||||
}
|
}
|
||||||
|
@ -1720,7 +1838,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
|
||||||
ic.VM.GasLimit = gasPolicy
|
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 {
|
if err != nil {
|
||||||
return nil, response.NewInternalServerError("can't prepare verification VM", err)
|
return nil, response.NewInternalServerError("can't prepare verification VM", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,7 @@ const (
|
||||||
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
|
||||||
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
|
||||||
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
|
||||||
|
block20StateRootLE = "19ec3c3d01afe5274e8bb4a393c97da708c5608c5b0ad116c16108b6a04fb08e"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -999,6 +1000,134 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
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": {
|
"invokescript": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -1098,6 +1227,132 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
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": {
|
"invokecontractverify": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
@ -1203,6 +1458,129 @@ var rpcTestCases = map[string][]rpcTestCase{
|
||||||
fail: true,
|
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": {
|
"sendrawtransaction": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
Loading…
Reference in a new issue