package client 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/network/payload" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/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([]request.SignerWithWitness, len(signers)) for i := range signersWithWitnesses { signersWithWitnesses[i] = request.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.Params{}, 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 }