rpcclient: add invoker package and structure
This commit is contained in:
parent
55164132df
commit
fee7e2f223
2 changed files with 249 additions and 0 deletions
141
pkg/rpcclient/invoker/invoker.go
Normal file
141
pkg/rpcclient/invoker/invoker.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package invoker
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// RPCInvoke is a set of RPC methods needed to execute things at the current
|
||||
// blockchain height.
|
||||
type RPCInvoke interface {
|
||||
InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// RPCInvokeHistoric is a set of RPC methods needed to execute things at some
|
||||
// fixed point in blockchain's life.
|
||||
type RPCInvokeHistoric interface {
|
||||
InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error)
|
||||
InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||
InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// Invoker allows to test-execute things using RPC client. Its API simplifies
|
||||
// reusing the same signers list for a series of invocations and at the
|
||||
// same time uses regular Go types for call parameters. It doesn't do anything with
|
||||
// the result of invocation, that's left for upper (contract) layer to deal with.
|
||||
// Invoker does not produce any transactions and does not change the state of the
|
||||
// chain.
|
||||
type Invoker struct {
|
||||
client RPCInvoke
|
||||
signers []transaction.Signer
|
||||
}
|
||||
|
||||
type historicConverter struct {
|
||||
client RPCInvokeHistoric
|
||||
block *util.Uint256
|
||||
height *uint32
|
||||
root *util.Uint256
|
||||
}
|
||||
|
||||
// New creates an Invoker to test-execute things at the current blockchain height.
|
||||
func New(client RPCInvoke, signers []transaction.Signer) *Invoker {
|
||||
return &Invoker{client, signers}
|
||||
}
|
||||
|
||||
// NewHistoricAtBlock creates an Invoker to test-execute things at some given block.
|
||||
func NewHistoricAtBlock(block util.Uint256, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||
return New(&historicConverter{
|
||||
client: client,
|
||||
block: &block,
|
||||
}, signers)
|
||||
}
|
||||
|
||||
// NewHistoricAtHeight creates an Invoker to test-execute things at some given height.
|
||||
func NewHistoricAtHeight(height uint32, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||
return New(&historicConverter{
|
||||
client: client,
|
||||
height: &height,
|
||||
}, signers)
|
||||
}
|
||||
|
||||
// NewHistoricWithState creates an Invoker to test-execute things with some given state.
|
||||
func NewHistoricWithState(root util.Uint256, client RPCInvokeHistoric, signers []transaction.Signer) *Invoker {
|
||||
return New(&historicConverter{
|
||||
client: client,
|
||||
root: &root,
|
||||
}, signers)
|
||||
}
|
||||
|
||||
func (h *historicConverter) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
if h.block != nil {
|
||||
return h.client.InvokeScriptAtBlock(*h.block, script, signers)
|
||||
}
|
||||
if h.height != nil {
|
||||
return h.client.InvokeScriptAtHeight(*h.height, script, signers)
|
||||
}
|
||||
if h.root != nil {
|
||||
return h.client.InvokeScriptWithState(*h.root, script, signers)
|
||||
}
|
||||
panic("uninitialized historicConverter")
|
||||
}
|
||||
|
||||
func (h *historicConverter) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
if h.block != nil {
|
||||
return h.client.InvokeFunctionAtBlock(*h.block, contract, operation, params, signers)
|
||||
}
|
||||
if h.height != nil {
|
||||
return h.client.InvokeFunctionAtHeight(*h.height, contract, operation, params, signers)
|
||||
}
|
||||
if h.root != nil {
|
||||
return h.client.InvokeFunctionWithState(*h.root, contract, operation, params, signers)
|
||||
}
|
||||
panic("uninitialized historicConverter")
|
||||
}
|
||||
|
||||
func (h *historicConverter) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
if h.block != nil {
|
||||
return h.client.InvokeContractVerifyAtBlock(*h.block, contract, params, signers, witnesses...)
|
||||
}
|
||||
if h.height != nil {
|
||||
return h.client.InvokeContractVerifyAtHeight(*h.height, contract, params, signers, witnesses...)
|
||||
}
|
||||
if h.root != nil {
|
||||
return h.client.InvokeContractVerifyWithState(*h.root, contract, params, signers, witnesses...)
|
||||
}
|
||||
panic("uninitialized historicConverter")
|
||||
}
|
||||
|
||||
// Call invokes a method of the contract with the given parameters (and
|
||||
// Invoker-specific list of signers) and returns the result as is.
|
||||
func (v *Invoker) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
ps, err := smartcontract.NewParametersFromValues(params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.client.InvokeFunction(contract, operation, ps, v.signers)
|
||||
}
|
||||
|
||||
// Verify invokes contract's verify method in the verification context with
|
||||
// Invoker-specific signers and given witnesses and parameters.
|
||||
func (v *Invoker) Verify(contract util.Uint160, witnesses []transaction.Witness, params ...interface{}) (*result.Invoke, error) {
|
||||
ps, err := smartcontract.NewParametersFromValues(params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.client.InvokeContractVerify(contract, ps, v.signers, witnesses...)
|
||||
}
|
||||
|
||||
// Run executes given bytecode with Invoker-specific list of signers.
|
||||
func (v *Invoker) Run(script []byte) (*result.Invoke, error) {
|
||||
return v.client.InvokeScript(script, v.signers)
|
||||
}
|
108
pkg/rpcclient/invoker/invoker_test.go
Normal file
108
pkg/rpcclient/invoker/invoker_test.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package invoker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type rpcInv struct {
|
||||
resInv *result.Invoke
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *rpcInv) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeContractVerifyAtBlock(blockHash util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeContractVerifyAtHeight(height uint32, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeContractVerifyWithState(stateroot util.Uint256, contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeFunctionAtBlock(blockHash util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeFunctionAtHeight(height uint32, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeFunctionWithState(stateroot util.Uint256, contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeScriptAtBlock(blockHash util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeScriptAtHeight(height uint32, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
func (r *rpcInv) InvokeScriptWithState(stateroot util.Uint256, script []byte, signers []transaction.Signer) (*result.Invoke, error) {
|
||||
return r.resInv, r.err
|
||||
}
|
||||
|
||||
func TestInvoker(t *testing.T) {
|
||||
resExp := &result.Invoke{State: "HALT"}
|
||||
ri := &rpcInv{resExp, nil}
|
||||
|
||||
testInv := func(t *testing.T, inv *Invoker) {
|
||||
res, err := inv.Call(util.Uint160{}, "method")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
res, err = inv.Verify(util.Uint160{}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
res, err = inv.Run([]byte{1})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
res, err = inv.Call(util.Uint160{}, "method")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
res, err = inv.Verify(util.Uint160{}, nil, "param")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
res, err = inv.Call(util.Uint160{}, "method", 42)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, resExp, res)
|
||||
|
||||
_, err = inv.Verify(util.Uint160{}, nil, make(map[int]int))
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = inv.Call(util.Uint160{}, "method", make(map[int]int))
|
||||
require.Error(t, err)
|
||||
}
|
||||
t.Run("standard", func(t *testing.T) {
|
||||
testInv(t, New(ri, nil))
|
||||
})
|
||||
t.Run("historic, block", func(t *testing.T) {
|
||||
testInv(t, NewHistoricAtBlock(util.Uint256{}, ri, nil))
|
||||
})
|
||||
t.Run("historic, height", func(t *testing.T) {
|
||||
testInv(t, NewHistoricAtHeight(100500, ri, nil))
|
||||
})
|
||||
t.Run("historic, state", func(t *testing.T) {
|
||||
testInv(t, NewHistoricWithState(util.Uint256{}, ri, nil))
|
||||
})
|
||||
t.Run("broken historic", func(t *testing.T) {
|
||||
inv := New(&historicConverter{client: ri}, nil) // It's not possible to do this from outside.
|
||||
require.Panics(t, func() { _, _ = inv.Call(util.Uint160{}, "method") })
|
||||
require.Panics(t, func() { _, _ = inv.Verify(util.Uint160{}, nil, "param") })
|
||||
require.Panics(t, func() { _, _ = inv.Run([]byte{1}) })
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue