neoneo-go/pkg/rpcclient/rpc.go
Roman Khimov 3c5a720e3a smartcontract: drop Params type and TryParse methods
They were first introduced in a058598ecc and
then carefully moved in 648e0bb242, but it looks
like they were never used by any external code. This code can be useful on the
server, but the server has its own params package to deal with
parameters. Clients usually create Parameters and then get results as
stackitem.Items, so they don't use this code either. So there is zero point in
keeping it.
2022-08-01 21:31:23 +03:00

1174 lines
43 KiB
Go

package rpcclient
import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/neorpc"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/network/payload"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
var errNetworkNotInitialized = errors.New("RPC client network is not initialized")
// CalculateNetworkFee calculates network fee for the transaction. The transaction may
// have empty witnesses for contract signers and may have only verification scripts
// filled for standard sig/multisig signers.
func (c *Client) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) {
var (
params = []interface{}{tx.Bytes()}
resp = new(result.NetworkFee)
)
if err := c.performRequest("calculatenetworkfee", params, resp); err != nil {
return 0, err
}
return resp.Value, nil
}
// GetApplicationLog returns a contract log based on the specified txid.
func (c *Client) GetApplicationLog(hash util.Uint256, trig *trigger.Type) (*result.ApplicationLog, error) {
var (
params = []interface{}{hash.StringLE()}
resp = new(result.ApplicationLog)
)
if trig != nil {
params = append(params, trig.String())
}
if err := c.performRequest("getapplicationlog", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetBestBlockHash returns the hash of the tallest block in the blockchain.
func (c *Client) GetBestBlockHash() (util.Uint256, error) {
var resp = util.Uint256{}
if err := c.performRequest("getbestblockhash", nil, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetBlockCount returns the number of blocks in the blockchain.
func (c *Client) GetBlockCount() (uint32, error) {
var resp uint32
if err := c.performRequest("getblockcount", nil, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetBlockByIndex returns a block by its height. You should initialize network magic
// with Init before calling GetBlockByIndex.
func (c *Client) GetBlockByIndex(index uint32) (*block.Block, error) {
return c.getBlock(index)
}
// GetBlockByHash returns a block by its hash. You should initialize network magic
// with Init before calling GetBlockByHash.
func (c *Client) GetBlockByHash(hash util.Uint256) (*block.Block, error) {
return c.getBlock(hash.StringLE())
}
func (c *Client) getBlock(param interface{}) (*block.Block, error) {
var (
resp []byte
err error
b *block.Block
)
if err = c.performRequest("getblock", []interface{}{param}, &resp); err != nil {
return nil, err
}
r := io.NewBinReaderFromBuf(resp)
sr, err := c.StateRootInHeader()
if err != nil {
return nil, err
}
b = block.New(sr)
b.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
}
return b, nil
}
// GetBlockByIndexVerbose returns a block wrapper with additional metadata by
// its height. You should initialize network magic with Init before calling GetBlockByIndexVerbose.
// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively.
func (c *Client) GetBlockByIndexVerbose(index uint32) (*result.Block, error) {
return c.getBlockVerbose(index)
}
// GetBlockByHashVerbose returns a block wrapper with additional metadata by
// its hash. You should initialize network magic with Init before calling GetBlockByHashVerbose.
func (c *Client) GetBlockByHashVerbose(hash util.Uint256) (*result.Block, error) {
return c.getBlockVerbose(hash.StringLE())
}
func (c *Client) getBlockVerbose(param interface{}) (*result.Block, error) {
var (
params = []interface{}{param, 1} // 1 for verbose.
resp = &result.Block{}
err error
)
sr, err := c.StateRootInHeader()
if err != nil {
return nil, err
}
resp.Header.StateRootEnabled = sr
if err = c.performRequest("getblock", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetBlockHash returns the hash value of the corresponding block based on the specified index.
func (c *Client) GetBlockHash(index uint32) (util.Uint256, error) {
var (
params = []interface{}{index}
resp = util.Uint256{}
)
if err := c.performRequest("getblockhash", params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetBlockHeader returns the corresponding block header information from a serialized hex string
// according to the specified script hash. You should initialize network magic
// with Init before calling GetBlockHeader.
func (c *Client) GetBlockHeader(hash util.Uint256) (*block.Header, error) {
var (
params = []interface{}{hash.StringLE()}
resp []byte
h *block.Header
)
if err := c.performRequest("getblockheader", params, &resp); err != nil {
return nil, err
}
sr, err := c.StateRootInHeader()
if err != nil {
return nil, err
}
r := io.NewBinReaderFromBuf(resp)
h = new(block.Header)
h.StateRootEnabled = sr
h.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
}
return h, nil
}
// GetBlockHeaderCount returns the number of headers in the main chain.
func (c *Client) GetBlockHeaderCount() (uint32, error) {
var resp uint32
if err := c.performRequest("getblockheadercount", nil, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetBlockHeaderVerbose returns the corresponding block header information from a Json format string
// according to the specified script hash.
func (c *Client) GetBlockHeaderVerbose(hash util.Uint256) (*result.Header, error) {
var (
params = []interface{}{hash.StringLE(), 1}
resp = &result.Header{}
)
if err := c.performRequest("getblockheader", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetBlockSysFee returns the system fees of the block based on the specified index.
func (c *Client) GetBlockSysFee(index uint32) (fixedn.Fixed8, error) {
var (
params = []interface{}{index}
resp fixedn.Fixed8
)
if err := c.performRequest("getblocksysfee", params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetConnectionCount returns the current number of the connections for the node.
func (c *Client) GetConnectionCount() (int, error) {
var resp int
if err := c.performRequest("getconnectioncount", nil, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetCommittee returns the current public keys of NEO nodes in the committee.
func (c *Client) GetCommittee() (keys.PublicKeys, error) {
var resp = new(keys.PublicKeys)
if err := c.performRequest("getcommittee", nil, resp); err != nil {
return nil, err
}
return *resp, nil
}
// GetContractStateByHash queries contract information according to the contract script hash.
func (c *Client) GetContractStateByHash(hash util.Uint160) (*state.Contract, error) {
return c.getContractState(hash.StringLE())
}
// GetContractStateByAddressOrName queries contract information according to the contract address or name.
func (c *Client) GetContractStateByAddressOrName(addressOrName string) (*state.Contract, error) {
return c.getContractState(addressOrName)
}
// GetContractStateByID queries contract information according to the contract ID.
func (c *Client) GetContractStateByID(id int32) (*state.Contract, error) {
return c.getContractState(id)
}
// getContractState is an internal representation of GetContractStateBy* methods.
func (c *Client) getContractState(param interface{}) (*state.Contract, error) {
var (
params = []interface{}{param}
resp = &state.Contract{}
)
if err := c.performRequest("getcontractstate", params, resp); err != nil {
return resp, err
}
return resp, nil
}
// GetNativeContracts queries information about native contracts.
func (c *Client) GetNativeContracts() ([]state.NativeContract, error) {
var resp []state.NativeContract
if err := c.performRequest("getnativecontracts", nil, &resp); err != nil {
return resp, err
}
// Update native contract hashes.
c.cacheLock.Lock()
for _, cs := range resp {
c.cache.nativeHashes[cs.Manifest.Name] = cs.Hash
}
c.cacheLock.Unlock()
return resp, nil
}
// GetNEP11Balances is a wrapper for getnep11balances RPC.
func (c *Client) GetNEP11Balances(address util.Uint160) (*result.NEP11Balances, error) {
params := []interface{}{address.StringLE()}
resp := new(result.NEP11Balances)
if err := c.performRequest("getnep11balances", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetNEP17Balances is a wrapper for getnep17balances RPC.
func (c *Client) GetNEP17Balances(address util.Uint160) (*result.NEP17Balances, error) {
params := []interface{}{address.StringLE()}
resp := new(result.NEP17Balances)
if err := c.performRequest("getnep17balances", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetNEP11Properties is a wrapper for getnep11properties RPC. We recommend using
// NEP11Properties method instead of this to receive proper VM types and work with them.
// This method is provided mostly for the sake of completeness. For well-known
// attributes like "description", "image", "name" and "tokenURI" it returns strings,
// while for all others []byte (which can be nil).
func (c *Client) GetNEP11Properties(asset util.Uint160, token []byte) (map[string]interface{}, error) {
params := []interface{}{asset.StringLE(), hex.EncodeToString(token)}
resp := make(map[string]interface{})
if err := c.performRequest("getnep11properties", params, &resp); err != nil {
return nil, err
}
for k, v := range resp {
if v == nil {
continue
}
str, ok := v.(string)
if !ok {
return nil, errors.New("value is not a string")
}
if result.KnownNEP11Properties[k] {
continue
}
val, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return nil, err
}
resp[k] = val
}
return resp, nil
}
// GetNEP11Transfers is a wrapper for getnep11transfers RPC. Address parameter
// is mandatory, while all others are optional. Limit and page parameters are
// only supported by NeoGo servers and can only be specified with start and stop.
func (c *Client) GetNEP11Transfers(address util.Uint160, start, stop *uint64, limit, page *int) (*result.NEP11Transfers, error) {
params, err := packTransfersParams(address, start, stop, limit, page)
if err != nil {
return nil, err
}
resp := new(result.NEP11Transfers)
if err := c.performRequest("getnep11transfers", params, resp); err != nil {
return nil, err
}
return resp, nil
}
func packTransfersParams(address util.Uint160, start, stop *uint64, limit, page *int) ([]interface{}, error) {
params := []interface{}{address.StringLE()}
if start != nil {
params = append(params, *start)
if stop != nil {
params = append(params, *stop)
if limit != nil {
params = append(params, *limit)
if page != nil {
params = append(params, *page)
}
} else if page != nil {
return nil, errors.New("bad parameters")
}
} else if limit != nil || page != nil {
return nil, errors.New("bad parameters")
}
} else if stop != nil || limit != nil || page != nil {
return nil, errors.New("bad parameters")
}
return params, nil
}
// GetNEP17Transfers is a wrapper for getnep17transfers RPC. Address parameter
// is mandatory while all the others are optional. Start and stop parameters
// are supported since neo-go 0.77.0 and limit and page since neo-go 0.78.0.
// These parameters are positional in the JSON-RPC call. For example, you can't specify the limit
// without specifying start/stop first.
func (c *Client) GetNEP17Transfers(address util.Uint160, start, stop *uint64, limit, page *int) (*result.NEP17Transfers, error) {
params, err := packTransfersParams(address, start, stop, limit, page)
if err != nil {
return nil, err
}
resp := new(result.NEP17Transfers)
if err := c.performRequest("getnep17transfers", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetPeers returns a list of the nodes that the node is currently connected to/disconnected from.
func (c *Client) GetPeers() (*result.GetPeers, error) {
var resp = &result.GetPeers{}
if err := c.performRequest("getpeers", nil, resp); err != nil {
return resp, err
}
return resp, nil
}
// GetRawMemPool returns a list of unconfirmed transactions in the memory.
func (c *Client) GetRawMemPool() ([]util.Uint256, error) {
var resp = new([]util.Uint256)
if err := c.performRequest("getrawmempool", nil, resp); err != nil {
return *resp, err
}
return *resp, nil
}
// GetRawTransaction returns a transaction by hash.
func (c *Client) GetRawTransaction(hash util.Uint256) (*transaction.Transaction, error) {
var (
params = []interface{}{hash.StringLE()}
resp []byte
err error
)
if err = c.performRequest("getrawtransaction", params, &resp); err != nil {
return nil, err
}
tx, err := transaction.NewTransactionFromBytes(resp)
if err != nil {
return nil, err
}
return tx, nil
}
// GetRawTransactionVerbose returns a transaction wrapper with additional
// metadata by transaction's hash.
// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively.
func (c *Client) GetRawTransactionVerbose(hash util.Uint256) (*result.TransactionOutputRaw, error) {
var (
params = []interface{}{hash.StringLE(), 1} // 1 for verbose.
resp = &result.TransactionOutputRaw{}
err error
)
if err = c.performRequest("getrawtransaction", params, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetState returns historical contract storage item state by the given stateroot,
// historical contract hash and historical item key.
func (c *Client) GetState(stateroot util.Uint256, historicalContractHash util.Uint160, historicalKey []byte) ([]byte, error) {
var (
params = []interface{}{stateroot.StringLE(), historicalContractHash.StringLE(), historicalKey}
resp []byte
)
if err := c.performRequest("getstate", params, &resp); err != nil {
return nil, err
}
return resp, nil
}
// FindStates returns historical contract storage item states by the given stateroot,
// historical contract hash and historical prefix. If `start` path is specified, items
// starting from `start` path are being returned (excluding item located at the start path).
// If `maxCount` specified, the maximum number of items to be returned equals to `maxCount`.
func (c *Client) FindStates(stateroot util.Uint256, historicalContractHash util.Uint160, historicalPrefix []byte,
start []byte, maxCount *int) (result.FindStates, error) {
if historicalPrefix == nil {
historicalPrefix = []byte{}
}
var (
params = []interface{}{stateroot.StringLE(), historicalContractHash.StringLE(), historicalPrefix}
resp result.FindStates
)
if start == nil && maxCount != nil {
start = []byte{}
}
if start != nil {
params = append(params, start)
}
if maxCount != nil {
params = append(params, *maxCount)
}
if err := c.performRequest("findstates", params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetStateRootByHeight returns the state root for the specified height.
func (c *Client) GetStateRootByHeight(height uint32) (*state.MPTRoot, error) {
return c.getStateRoot(height)
}
// GetStateRootByBlockHash returns the state root for the block with the specified hash.
func (c *Client) GetStateRootByBlockHash(hash util.Uint256) (*state.MPTRoot, error) {
return c.getStateRoot(hash)
}
func (c *Client) getStateRoot(param interface{}) (*state.MPTRoot, error) {
var resp = new(state.MPTRoot)
if err := c.performRequest("getstateroot", []interface{}{param}, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetStateHeight returns the current validated and local node state height.
func (c *Client) GetStateHeight() (*result.StateHeight, error) {
var resp = new(result.StateHeight)
if err := c.performRequest("getstateheight", nil, resp); err != nil {
return nil, err
}
return resp, nil
}
// GetStorageByID returns the stored value according to the contract ID and the stored key.
func (c *Client) GetStorageByID(id int32, key []byte) ([]byte, error) {
return c.getStorage([]interface{}{id, key})
}
// GetStorageByHash returns the stored value according to the contract script hash and the stored key.
func (c *Client) GetStorageByHash(hash util.Uint160, key []byte) ([]byte, error) {
return c.getStorage([]interface{}{hash.StringLE(), key})
}
func (c *Client) getStorage(params []interface{}) ([]byte, error) {
var resp []byte
if err := c.performRequest("getstorage", params, &resp); err != nil {
return nil, err
}
return resp, nil
}
// GetTransactionHeight returns the block index where the transaction is found.
func (c *Client) GetTransactionHeight(hash util.Uint256) (uint32, error) {
var (
params = []interface{}{hash.StringLE()}
resp uint32
)
if err := c.performRequest("gettransactionheight", params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetUnclaimedGas returns the unclaimed GAS amount for the specified address.
func (c *Client) GetUnclaimedGas(address string) (result.UnclaimedGas, error) {
var (
params = []interface{}{address}
resp result.UnclaimedGas
)
if err := c.performRequest("getunclaimedgas", params, &resp); err != nil {
return resp, err
}
return resp, nil
}
// GetCandidates returns the current list of NEO candidate node with voting data and
// validator status.
func (c *Client) GetCandidates() ([]result.Candidate, error) {
var resp = new([]result.Candidate)
if err := c.performRequest("getcandidates", nil, resp); err != nil {
return nil, err
}
return *resp, nil
}
// GetNextBlockValidators returns the current NEO consensus nodes information and voting data.
func (c *Client) GetNextBlockValidators() ([]result.Validator, error) {
var resp = new([]result.Validator)
if err := c.performRequest("getnextblockvalidators", nil, resp); err != nil {
return nil, err
}
return *resp, nil
}
// GetVersion returns the version information about the queried node.
func (c *Client) GetVersion() (*result.Version, error) {
var resp = &result.Version{}
if err := c.performRequest("getversion", nil, resp); err != nil {
return nil, err
}
return resp, nil
}
// InvokeScript returns the result of the given script after running it true the VM.
// NOTE: This is a test invoke and will not affect the blockchain.
func (c *Client) InvokeScript(script []byte, signers []transaction.Signer) (*result.Invoke, error) {
var p = []interface{}{script}
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 = []interface{}{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 = []interface{}{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 = []interface{}{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.
func (c *Client) InvokeFunction(contract util.Uint160, operation string, params []smartcontract.Parameter, signers []transaction.Signer) (*result.Invoke, error) {
var p = []interface{}{contract.StringLE(), operation, 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 = []interface{}{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 = []interface{}{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 = []interface{}{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.
func (c *Client) InvokeContractVerify(contract util.Uint160, params []smartcontract.Parameter, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
var p = []interface{}{contract.StringLE(), params}
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 = []interface{}{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 = []interface{}{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 = []interface{}{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 []interface{}, signers []transaction.Signer, witnesses ...transaction.Witness) (*result.Invoke, error) {
var resp = new(result.Invoke)
if signers != nil {
if witnesses == nil {
p = append(p, signers)
} else {
if len(witnesses) != len(signers) {
return nil, fmt.Errorf("number of witnesses should match number of signers, got %d vs %d", len(witnesses), len(signers))
}
signersWithWitnesses := make([]neorpc.SignerWithWitness, len(signers))
for i := range signersWithWitnesses {
signersWithWitnesses[i] = neorpc.SignerWithWitness{
Signer: signers[i],
Witness: witnesses[i],
}
}
p = append(p, signersWithWitnesses)
}
}
if err := c.performRequest(method, p, resp); err != nil {
return nil, err
}
return resp, nil
}
// SendRawTransaction broadcasts a transaction over the NEO network.
// The given hex string needs to be signed with a keypair.
// When the result of the response object is true, the TX has successfully
// been broadcasted to the network.
func (c *Client) SendRawTransaction(rawTX *transaction.Transaction) (util.Uint256, error) {
var (
params = []interface{}{rawTX.Bytes()}
resp = new(result.RelayResult)
)
if err := c.performRequest("sendrawtransaction", params, resp); err != nil {
return util.Uint256{}, err
}
return resp.Hash, nil
}
// SubmitBlock broadcasts a raw block over the NEO network.
func (c *Client) SubmitBlock(b block.Block) (util.Uint256, error) {
var (
params []interface{}
resp = new(result.RelayResult)
)
buf := io.NewBufBinWriter()
b.EncodeBinary(buf.BinWriter)
if err := buf.Err; err != nil {
return util.Uint256{}, err
}
params = []interface{}{buf.Bytes()}
if err := c.performRequest("submitblock", params, resp); err != nil {
return util.Uint256{}, err
}
return resp.Hash, nil
}
// SubmitRawOracleResponse submits a raw oracle response to the oracle node.
// Raw params are used to avoid excessive marshalling.
func (c *Client) SubmitRawOracleResponse(ps []interface{}) error {
return c.performRequest("submitoracleresponse", ps, new(result.RelayResult))
}
// SignAndPushInvocationTx signs and pushes the given script as an invocation
// transaction using the given wif to sign it and the given cosigners to cosign it if
// possible. It spends the amount of gas specified. It returns a hash of the
// invocation transaction and an error. If one of the cosigners accounts is
// neither contract-based nor unlocked, an error is returned.
func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sysfee int64, netfee fixedn.Fixed8, cosigners []SignerAccount) (util.Uint256, error) {
tx, err := c.CreateTxFromScript(script, acc, sysfee, int64(netfee), cosigners)
if err != nil {
return util.Uint256{}, fmt.Errorf("failed to create tx: %w", err)
}
return c.SignAndPushTx(tx, acc, cosigners)
}
// SignAndPushTx signs the given transaction using the given wif and cosigners and pushes
// it to the chain. It returns a hash of the transaction and an error. If one of
// the cosigners accounts is neither contract-based nor unlocked, an error is
// returned.
func (c *Client) SignAndPushTx(tx *transaction.Transaction, acc *wallet.Account, cosigners []SignerAccount) (util.Uint256, error) {
var (
txHash util.Uint256
err error
)
m, err := c.GetNetwork()
if err != nil {
return txHash, fmt.Errorf("failed to sign tx: %w", err)
}
if err = acc.SignTx(m, tx); err != nil {
return txHash, fmt.Errorf("failed to sign tx: %w", err)
}
// try to add witnesses for the rest of the signers
for i, signer := range tx.Signers[1:] {
var isOk bool
for _, cosigner := range cosigners {
if signer.Account == cosigner.Signer.Account {
err = cosigner.Account.SignTx(m, tx)
if err != nil { // then account is non-contract-based and locked, but let's provide more detailed error
if paramNum := len(cosigner.Account.Contract.Parameters); paramNum != 0 && cosigner.Account.Contract.Deployed {
return txHash, fmt.Errorf("failed to add contract-based witness for signer #%d (%s): "+
"%d parameters must be provided to construct invocation script", i, address.Uint160ToString(signer.Account), paramNum)
}
return txHash, fmt.Errorf("failed to add witness for signer #%d (%s): account should be unlocked to add the signature. "+
"Store partially-signed transaction and then use 'wallet sign' command to cosign it", i, address.Uint160ToString(signer.Account))
}
isOk = true
break
}
}
if !isOk {
return txHash, fmt.Errorf("failed to add witness for signer #%d (%s): account wasn't provided", i, address.Uint160ToString(signer.Account))
}
}
txHash = tx.Hash()
actualHash, err := c.SendRawTransaction(tx)
if err != nil {
return txHash, fmt.Errorf("failed to send tx: %w", err)
}
if !actualHash.Equals(txHash) {
return actualHash, fmt.Errorf("sent and actual tx hashes mismatch:\n\tsent: %v\n\tactual: %v", txHash.StringLE(), actualHash.StringLE())
}
return txHash, nil
}
// getSigners returns an array of transaction signers and corresponding accounts from
// given sender and cosigners. If cosigners list already contains sender, the sender
// will be placed at the start of the list.
func getSigners(sender *wallet.Account, cosigners []SignerAccount) ([]transaction.Signer, []*wallet.Account, error) {
var (
signers []transaction.Signer
accounts []*wallet.Account
)
from, err := address.StringToUint160(sender.Address)
if err != nil {
return nil, nil, fmt.Errorf("bad sender account address: %v", err)
}
s := transaction.Signer{
Account: from,
Scopes: transaction.None,
}
for _, c := range cosigners {
if c.Signer.Account == from {
s = c.Signer
continue
}
signers = append(signers, c.Signer)
accounts = append(accounts, c.Account)
}
signers = append([]transaction.Signer{s}, signers...)
accounts = append([]*wallet.Account{sender}, accounts...)
return signers, accounts, nil
}
// SignAndPushP2PNotaryRequest creates and pushes a P2PNotary request constructed from the main
// and fallback transactions using the given wif to sign it. It returns the request and an error.
// Fallback transaction is constructed from the given script using the amount of gas specified.
// For successful fallback transaction validation at least 2*transaction.NotaryServiceFeePerKey
// GAS should be deposited to the Notary contract.
// Main transaction should be constructed by the user. Several rules should be met for
// successful main transaction acceptance:
// 1. Native Notary contract should be a signer of the main transaction.
// 2. Notary signer should have None scope.
// 3. Main transaction should have dummy contract witness for Notary signer.
// 4. Main transaction should have NotaryAssisted attribute with NKeys specified.
// 5. NotaryAssisted attribute and dummy Notary witness (as long as the other incomplete witnesses)
// should be paid for. Use CalculateNotaryWitness to calculate the amount of network fee to pay
// for the attribute and Notary witness.
// 6. Main transaction either shouldn't have all witnesses attached (in this case none of them
// can be multisignature), or it only should have a partial multisignature.
// Note: client should be initialized before SignAndPushP2PNotaryRequest call.
func (c *Client) SignAndPushP2PNotaryRequest(mainTx *transaction.Transaction, fallbackScript []byte, fallbackSysFee int64, fallbackNetFee int64, fallbackValidFor uint32, acc *wallet.Account) (*payload.P2PNotaryRequest, error) {
var err error
notaryHash, err := c.GetNativeContractHash(nativenames.Notary)
if err != nil {
return nil, fmt.Errorf("failed to get native Notary hash: %w", err)
}
from, err := address.StringToUint160(acc.Address)
if err != nil {
return nil, fmt.Errorf("bad account address: %v", err)
}
signers := []transaction.Signer{{Account: notaryHash}, {Account: from}}
if fallbackSysFee < 0 {
result, err := c.InvokeScript(fallbackScript, signers)
if err != nil {
return nil, fmt.Errorf("can't add system fee to fallback transaction: %w", err)
}
if result.State != "HALT" {
return nil, fmt.Errorf("can't add system fee to fallback transaction: bad vm state %s due to an error: %s", result.State, result.FaultException)
}
fallbackSysFee = result.GasConsumed
}
maxNVBDelta, err := c.GetMaxNotValidBeforeDelta()
if err != nil {
return nil, fmt.Errorf("failed to get MaxNotValidBeforeDelta")
}
if int64(fallbackValidFor) > maxNVBDelta {
return nil, fmt.Errorf("fallback transaction should be valid for not more than %d blocks", maxNVBDelta)
}
fallbackTx := transaction.New(fallbackScript, fallbackSysFee)
fallbackTx.Signers = signers
fallbackTx.ValidUntilBlock = mainTx.ValidUntilBlock
fallbackTx.Attributes = []transaction.Attribute{
{
Type: transaction.NotaryAssistedT,
Value: &transaction.NotaryAssisted{NKeys: 0},
},
{
Type: transaction.NotValidBeforeT,
Value: &transaction.NotValidBefore{Height: fallbackTx.ValidUntilBlock - fallbackValidFor + 1},
},
{
Type: transaction.ConflictsT,
Value: &transaction.Conflicts{Hash: mainTx.Hash()},
},
}
extraNetFee, err := c.CalculateNotaryFee(0)
if err != nil {
return nil, err
}
fallbackNetFee += extraNetFee
dummyAccount := &wallet.Account{Contract: &wallet.Contract{Deployed: false}} // don't call `verify` for Notary contract witness, because it will fail
err = c.AddNetworkFee(fallbackTx, fallbackNetFee, dummyAccount, acc)
if err != nil {
return nil, fmt.Errorf("failed to add network fee: %w", err)
}
fallbackTx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...),
VerificationScript: []byte{},
},
}
m, err := c.GetNetwork()
if err != nil {
return nil, fmt.Errorf("failed to sign fallback tx: %w", err)
}
if err = acc.SignTx(m, fallbackTx); err != nil {
return nil, fmt.Errorf("failed to sign fallback tx: %w", err)
}
fallbackHash := fallbackTx.Hash()
req := &payload.P2PNotaryRequest{
MainTransaction: mainTx,
FallbackTransaction: fallbackTx,
}
req.Witness = transaction.Witness{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(m), req)...),
VerificationScript: acc.GetVerificationScript(),
}
actualHash, err := c.SubmitP2PNotaryRequest(req)
if err != nil {
return req, fmt.Errorf("failed to submit notary request: %w", err)
}
if !actualHash.Equals(fallbackHash) {
return req, fmt.Errorf("sent and actual fallback tx hashes mismatch:\n\tsent: %v\n\tactual: %v", fallbackHash.StringLE(), actualHash.StringLE())
}
return req, nil
}
// CalculateNotaryFee calculates network fee for one dummy Notary witness and NotaryAssisted attribute with NKeys specified.
// The result should be added to the transaction's net fee for successful verification.
func (c *Client) CalculateNotaryFee(nKeys uint8) (int64, error) {
baseExecFee, err := c.GetExecFeeFactor()
if err != nil {
return 0, fmt.Errorf("failed to get BaseExecFeeFactor: %w", err)
}
feePerByte, err := c.GetFeePerByte()
if err != nil {
return 0, fmt.Errorf("failed to get FeePerByte: %w", err)
}
feePerKey, err := c.GetNotaryServiceFeePerKey()
if err != nil {
return 0, fmt.Errorf("failed to get NotaryServiceFeePerKey: %w", err)
}
return int64((nKeys+1))*feePerKey + // fee for NotaryAssisted attribute
fee.Opcode(baseExecFee, // Notary node witness
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // System.Contract.CallNative
nativeprices.NotaryVerificationPrice*baseExecFee + // Notary witness verification price
feePerByte*int64(io.GetVarSize(make([]byte, 66))) + // invocation script per-byte fee
feePerByte*int64(io.GetVarSize([]byte{})), // verification script per-byte fee
nil
}
// SubmitP2PNotaryRequest submits given P2PNotaryRequest payload to the RPC node.
func (c *Client) SubmitP2PNotaryRequest(req *payload.P2PNotaryRequest) (util.Uint256, error) {
var resp = new(result.RelayResult)
bytes, err := req.Bytes()
if err != nil {
return util.Uint256{}, fmt.Errorf("failed to encode request: %w", err)
}
params := []interface{}{bytes}
if err := c.performRequest("submitnotaryrequest", params, resp); err != nil {
return util.Uint256{}, err
}
return resp.Hash, nil
}
// ValidateAddress verifies that the address is a correct NEO address.
func (c *Client) ValidateAddress(address string) error {
var (
params = []interface{}{address}
resp = &result.ValidateAddress{}
)
if err := c.performRequest("validateaddress", params, resp); err != nil {
return err
}
if !resp.IsValid {
return errors.New("validateaddress returned false")
}
return nil
}
// CalculateValidUntilBlock calculates ValidUntilBlock field for tx as
// current blockchain height + number of validators. Number of validators
// is the length of blockchain validators list got from GetNextBlockValidators()
// method. Validators count is being cached and updated every 100 blocks.
func (c *Client) CalculateValidUntilBlock() (uint32, error) {
var (
result uint32
validatorsCount uint32
)
blockCount, err := c.GetBlockCount()
if err != nil {
return result, fmt.Errorf("can't get block count: %w", err)
}
c.cacheLock.RLock()
if c.cache.calculateValidUntilBlock.expiresAt > blockCount {
validatorsCount = c.cache.calculateValidUntilBlock.validatorsCount
c.cacheLock.RUnlock()
} else {
c.cacheLock.RUnlock()
validators, err := c.GetNextBlockValidators()
if err != nil {
return result, fmt.Errorf("can't get validators: %w", err)
}
validatorsCount = uint32(len(validators))
c.cacheLock.Lock()
c.cache.calculateValidUntilBlock = calculateValidUntilBlockCache{
validatorsCount: validatorsCount,
expiresAt: blockCount + cacheTimeout,
}
c.cacheLock.Unlock()
}
return blockCount + validatorsCount + 1, nil
}
// AddNetworkFee adds network fee for each witness script and optional extra
// network fee to transaction. `accs` is an array signer's accounts.
func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error {
if len(tx.Signers) != len(accs) {
return errors.New("number of signers must match number of scripts")
}
size := io.GetVarSize(tx)
var ef int64
for i, cosigner := range tx.Signers {
if accs[i].Contract.Deployed {
res, err := c.InvokeContractVerify(cosigner.Account, []smartcontract.Parameter{}, tx.Signers)
if err != nil {
return fmt.Errorf("failed to invoke verify: %w", err)
}
if res.State != "HALT" {
return fmt.Errorf("invalid VM state %s due to an error: %s", res.State, res.FaultException)
}
if l := len(res.Stack); l != 1 {
return fmt.Errorf("result stack length should be equal to 1, got %d", l)
}
r, err := topIntFromStack(res.Stack)
if err != nil {
return fmt.Errorf("signer #%d: failed to get `verify` result from stack: %w", i, err)
}
if r == 0 {
return fmt.Errorf("signer #%d: `verify` returned `false`", i)
}
tx.NetworkFee += res.GasConsumed
size += io.GetVarSize([]byte{}) * 2 // both scripts are empty
continue
}
if ef == 0 {
var err error
ef, err = c.GetExecFeeFactor()
if err != nil {
return fmt.Errorf("can't get `ExecFeeFactor`: %w", err)
}
}
netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script)
tx.NetworkFee += netFee
size += sizeDelta
}
fee, err := c.GetFeePerByte()
if err != nil {
return err
}
tx.NetworkFee += int64(size)*fee + extraFee
return nil
}
// GetNetwork returns the network magic of the RPC node the client connected to.
func (c *Client) GetNetwork() (netmode.Magic, error) {
c.cacheLock.RLock()
defer c.cacheLock.RUnlock()
if !c.cache.initDone {
return 0, errNetworkNotInitialized
}
return c.cache.network, nil
}
// StateRootInHeader returns true if the state root is contained in the block header.
// You should initialize Client cache with Init() before calling StateRootInHeader.
func (c *Client) StateRootInHeader() (bool, error) {
c.cacheLock.RLock()
defer c.cacheLock.RUnlock()
if !c.cache.initDone {
return false, errNetworkNotInitialized
}
return c.cache.stateRootInHeader, nil
}
// GetNativeContractHash returns native contract hash by its name.
func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) {
c.cacheLock.RLock()
hash, ok := c.cache.nativeHashes[name]
c.cacheLock.RUnlock()
if ok {
return hash, nil
}
cs, err := c.GetContractStateByAddressOrName(name)
if err != nil {
return util.Uint160{}, err
}
c.cacheLock.Lock()
c.cache.nativeHashes[name] = cs.Hash
c.cacheLock.Unlock()
return cs.Hash, nil
}
// TraverseIterator returns a set of iterator values (maxItemsCount at max) for
// the specified iterator and session. If result contains no elements, then either
// Iterator has no elements or session was expired and terminated by the server.
// If maxItemsCount is non-positive, then config.DefaultMaxIteratorResultItems
// iterator values will be returned using single `traverseiterator` call.
// Note that iterator session lifetime is restricted by the RPC-server
// configuration and is being reset each time iterator is accessed. If session
// won't be accessed within session expiration time, then it will be terminated
// by the RPC-server automatically.
func (c *Client) TraverseIterator(sessionID, iteratorID uuid.UUID, maxItemsCount int) ([]stackitem.Item, error) {
if maxItemsCount <= 0 {
maxItemsCount = config.DefaultMaxIteratorResultItems
}
var (
params = []interface{}{sessionID.String(), iteratorID.String(), maxItemsCount}
resp []json.RawMessage
)
if err := c.performRequest("traverseiterator", params, &resp); err != nil {
return nil, err
}
result := make([]stackitem.Item, len(resp))
for i, iBytes := range resp {
itm, err := stackitem.FromJSONWithTypes(iBytes)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal %d-th iterator value: %w", i, err)
}
result[i] = itm
}
return result, nil
}
// TerminateSession tries to terminate the specified session and returns `true` iff
// the specified session was found on server.
func (c *Client) TerminateSession(sessionID uuid.UUID) (bool, error) {
var resp bool
params := []interface{}{sessionID.String()}
if err := c.performRequest("terminatesession", params, &resp); err != nil {
return false, err
}
return resp, nil
}