Merge pull request #1426 from nspcc-dev/rpc/getcontractstate_by_id_or_name

rpc: allow to `getcontractstate` by id or name
This commit is contained in:
Roman Khimov 2020-11-05 13:17:42 +03:00 committed by GitHub
commit 679846c1a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 347 additions and 117 deletions

View file

@ -11,7 +11,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -91,7 +90,7 @@ func TestSignMultisigTx(t *testing.T) {
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", wallet1Path, "--address", multisigAddr, "--wallet", wallet1Path, "--address", multisigAddr,
"--out", txPath, "--out", txPath,
client.NeoContractHash.StringLE(), "transfer", e.Chain.GoverningTokenHash().StringLE(), "transfer",
"bytes:"+multisigHash.StringBE(), "bytes:"+multisigHash.StringBE(),
"bytes:"+priv.GetScriptHash().StringBE(), "bytes:"+priv.GetScriptHash().StringBE(),
"int:1", "int:1",

View file

@ -10,7 +10,6 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -138,6 +137,8 @@ func TestNEP5MultiTransfer(t *testing.T) {
e := newExecutor(t, true) e := newExecutor(t, true)
defer e.Close(t) defer e.Close(t)
neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo")
require.NoError(t, err)
args := []string{ args := []string{
"neo-go", "wallet", "nep5", "multitransfer", "neo-go", "wallet", "nep5", "multitransfer",
"--rpc-endpoint", "http://" + e.RPC.Addr, "--rpc-endpoint", "http://" + e.RPC.Addr,
@ -145,7 +146,7 @@ func TestNEP5MultiTransfer(t *testing.T) {
"--from", validatorAddr, "--from", validatorAddr,
"neo:" + privs[0].Address() + ":42", "neo:" + privs[0].Address() + ":42",
"GAS:" + privs[1].Address() + ":7", "GAS:" + privs[1].Address() + ":7",
client.NeoContractHash.StringLE() + ":" + privs[2].Address() + ":13", neoContractHash.StringLE() + ":" + privs[2].Address() + ":13",
} }
e.In.WriteString("one\r") e.In.WriteString("one\r")
@ -168,27 +169,31 @@ func TestNEP5ImportToken(t *testing.T) {
walletPath := path.Join(tmpDir, "walletForImport.json") walletPath := path.Join(tmpDir, "walletForImport.json")
defer os.Remove(walletPath) defer os.Remove(walletPath)
neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo")
require.NoError(t, err)
gasContractHash, err := e.Chain.GetNativeContractScriptHash("gas")
require.NoError(t, err)
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
e.Run(t, "neo-go", "wallet", "nep5", "import", e.Run(t, "neo-go", "wallet", "nep5", "import",
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", walletPath, "--wallet", walletPath,
"--token", client.GasContractHash.StringLE()) "--token", gasContractHash.StringLE())
e.Run(t, "neo-go", "wallet", "nep5", "import", e.Run(t, "neo-go", "wallet", "nep5", "import",
"--rpc-endpoint", "http://"+e.RPC.Addr, "--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", walletPath, "--wallet", walletPath,
"--token", client.NeoContractHash.StringLE()) "--token", neoContractHash.StringLE())
t.Run("Info", func(t *testing.T) { t.Run("Info", func(t *testing.T) {
checkGASInfo := func(t *testing.T) { checkGASInfo := func(t *testing.T) {
e.checkNextLine(t, "^Name:\\s*GAS") e.checkNextLine(t, "^Name:\\s*GAS")
e.checkNextLine(t, "^Symbol:\\s*gas") e.checkNextLine(t, "^Symbol:\\s*gas")
e.checkNextLine(t, "^Hash:\\s*"+client.GasContractHash.StringLE()) e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE())
e.checkNextLine(t, "^Decimals:\\s*8") e.checkNextLine(t, "^Decimals:\\s*8")
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.GasContractHash)) e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash))
} }
t.Run("WithToken", func(t *testing.T) { t.Run("WithToken", func(t *testing.T) {
e.Run(t, "neo-go", "wallet", "nep5", "info", e.Run(t, "neo-go", "wallet", "nep5", "info",
"--wallet", walletPath, "--token", client.GasContractHash.StringLE()) "--wallet", walletPath, "--token", gasContractHash.StringLE())
checkGASInfo(t) checkGASInfo(t)
}) })
t.Run("NoToken", func(t *testing.T) { t.Run("NoToken", func(t *testing.T) {
@ -199,14 +204,14 @@ func TestNEP5ImportToken(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
e.checkNextLine(t, "^Name:\\s*NEO") e.checkNextLine(t, "^Name:\\s*NEO")
e.checkNextLine(t, "^Symbol:\\s*neo") e.checkNextLine(t, "^Symbol:\\s*neo")
e.checkNextLine(t, "^Hash:\\s*"+client.NeoContractHash.StringLE()) e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE())
e.checkNextLine(t, "^Decimals:\\s*0") e.checkNextLine(t, "^Decimals:\\s*0")
e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(client.NeoContractHash)) e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash))
}) })
t.Run("Remove", func(t *testing.T) { t.Run("Remove", func(t *testing.T) {
e.In.WriteString("y\r") e.In.WriteString("y\r")
e.Run(t, "neo-go", "wallet", "nep5", "remove", e.Run(t, "neo-go", "wallet", "nep5", "remove",
"--wallet", walletPath, "--token", client.NeoContractHash.StringLE()) "--wallet", walletPath, "--token", neoContractHash.StringLE())
e.Run(t, "neo-go", "wallet", "nep5", "info", e.Run(t, "neo-go", "wallet", "nep5", "info",
"--wallet", walletPath) "--wallet", walletPath)
checkGASInfo(t) checkGASInfo(t)

View file

@ -2,8 +2,6 @@ package options
import ( import (
"flag" "flag"
"net/http"
"net/http/httptest"
"testing" "testing"
"time" "time"
@ -57,34 +55,3 @@ func TestGetTimeoutContext(t *testing.T) {
require.True(t, start.Before(dl) && dl.Before(end.Add(time.Nanosecond*20))) require.True(t, start.Before(dl) && dl.Before(end.Add(time.Nanosecond*20)))
}) })
} }
func TestGetRPCClient(t *testing.T) {
// need test server for proper client.Init() handling
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
response := `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`
_, err := w.Write([]byte(response))
if err != nil {
t.Fatalf("Error writing response: %s", err.Error())
}
}))
defer srv.Close()
t.Run("no endpoint", func(t *testing.T) {
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
ctx := cli.NewContext(cli.NewApp(), set, nil)
gctx, _ := GetTimeoutContext(ctx)
_, ec := GetRPCClient(gctx, ctx)
require.Equal(t, 1, ec.ExitCode())
})
t.Run("success", func(t *testing.T) {
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
set.String(RPCEndpointFlag, srv.URL, "")
ctx := cli.NewContext(cli.NewApp(), set, nil)
gctx, _ := GetTimeoutContext(ctx)
_, ec := GetRPCClient(gctx, ctx)
require.Nil(t, ec)
})
}

32
cli/options_test.go Normal file
View file

@ -0,0 +1,32 @@
package main
import (
"flag"
"testing"
"github.com/nspcc-dev/neo-go/cli/options"
"github.com/stretchr/testify/require"
"github.com/urfave/cli"
)
func TestGetRPCClient(t *testing.T) {
e := newExecutor(t, true)
defer e.Close(t)
t.Run("no endpoint", func(t *testing.T) {
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
ctx := cli.NewContext(cli.NewApp(), set, nil)
gctx, _ := options.GetTimeoutContext(ctx)
_, ec := options.GetRPCClient(gctx, ctx)
require.Equal(t, 1, ec.ExitCode())
})
t.Run("success", func(t *testing.T) {
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addr, "")
ctx := cli.NewContext(cli.NewApp(), set, nil)
gctx, _ := options.GetTimeoutContext(ctx)
_, ec := options.GetRPCClient(gctx, ctx)
require.Nil(t, ec)
})
}

View file

@ -80,7 +80,7 @@ func signMultisig(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
res, err := c.SendRawTransaction(tx) res, err := c.SendRawTransaction(tx)
if err != nil { if err != nil {

View file

@ -15,11 +15,6 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var (
neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0)
gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8)
)
var ( var (
tokenFlag = cli.StringFlag{ tokenFlag = cli.StringFlag{
Name: "token", Name: "token",
@ -161,7 +156,11 @@ func getNEP5Balance(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
}
err = c.Init()
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1)
} }
name := ctx.String("token") name := ctx.String("token")
@ -210,12 +209,6 @@ func getNEP5Balance(ctx *cli.Context) error {
} }
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) { func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string) (*wallet.Token, error) {
switch strings.ToLower(name) {
case "neo", client.NeoContractHash.StringLE():
return neoToken, nil
case "gas", client.GasContractHash.StringLE():
return gasToken, nil
}
return getMatchingTokenAux(ctx, func(i int) *wallet.Token { return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
return w.Extra.Tokens[i] return w.Extra.Tokens[i]
}, len(w.Extra.Tokens), name) }, len(w.Extra.Tokens), name)
@ -278,7 +271,7 @@ func importNEP5Token(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
tok, err := c.NEP5TokenInfo(tokenHash) tok, err := c.NEP5TokenInfo(tokenHash)
@ -371,7 +364,11 @@ func multiTransferNEP5(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
}
err = c.Init()
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1)
} }
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
@ -434,7 +431,11 @@ func transferNEP5(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
}
err = c.Init()
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to init RPC client: %w", err), 1)
} }
toFlag := ctx.Generic("to").(*flags.Address) toFlag := ctx.Generic("to").(*flags.Address)

View file

@ -10,7 +10,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -96,12 +95,16 @@ func handleCandidate(ctx *cli.Context, method string) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
neoContractHash, err := c.GetNativeContractHash("neo")
if err != nil {
return err
}
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, method, acc.PrivateKey().PublicKey().Bytes()) emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, method, acc.PrivateKey().PublicKey().Bytes())
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{
Account: acc.Contract.ScriptHash(), Account: acc.Contract.ScriptHash(),
@ -149,7 +152,7 @@ func handleVote(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
var pubArg interface{} var pubArg interface{}
@ -158,8 +161,12 @@ func handleVote(ctx *cli.Context) error {
} }
gas := flags.Fixed8FromContext(ctx, "gas") gas := flags.Fixed8FromContext(ctx, "gas")
neoContractHash, err := c.GetNativeContractHash("neo")
if err != nil {
return cli.NewExitError(err, 1)
}
w := io.NewBufBinWriter() w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, client.NeoContractHash, "vote", addr.BytesBE(), pubArg) emit.AppCallWithOperationAndArgs(w.BinWriter, neoContractHash, "vote", addr.BytesBE(), pubArg)
emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.Opcodes(w.BinWriter, opcode.ASSERT)
tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{ tx, err := c.CreateTxFromScript(w.Bytes(), acc, -1, int64(gas), transaction.Signer{

View file

@ -12,7 +12,6 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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/address"
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -231,10 +230,14 @@ func claimGas(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
hash, err := c.TransferNEP5(acc, scriptHash, client.NeoContractHash, 0, 0) neoContractHash, err := c.GetNativeContractHash("neo")
if err != nil {
return cli.NewExitError(err, 1)
}
hash, err := c.TransferNEP5(acc, scriptHash, neoContractHash, 0, 0)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -423,10 +426,10 @@ func importDeployed(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return err return cli.NewExitError(err, 1)
} }
cs, err := c.GetContractState(h) cs, err := c.GetContractStateByHash(h)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1) return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1)
} }

View file

@ -70,13 +70,20 @@ key in the server which doesn't fit the model of our node-client interactions.
Lacking this signature the transaction is almost useless, so there is no point Lacking this signature the transaction is almost useless, so there is no point
in returning it. in returning it.
Both methods also don't currently support arrays in function parameters. It's possible to use `invokefunction` not only with contract scripthash, but also
with contract name (for native contracts) or contract ID (for all contracts). This
feature is not supported by the C# node.
##### `getunclaimedgas` ##### `getunclaimedgas`
It's possible to call this method for any address with neo-go, unlike with C# It's possible to call this method for any address with neo-go, unlike with C#
node where it only works for addresses from opened wallet. node where it only works for addresses from opened wallet.
##### `getcontractstate`
It's possible to get non-native contract state by its ID, unlike with C# node where
it only works for native contracts.
### Unsupported methods ### Unsupported methods
Methods listed down below are not going to be supported for various reasons Methods listed down below are not going to be supported for various reasons

View file

@ -1073,6 +1073,15 @@ func (bc *Blockchain) GetContractScriptHash(id int32) (util.Uint160, error) {
return bc.dao.GetContractScriptHash(id) return bc.dao.GetContractScriptHash(id)
} }
// GetNativeContractScriptHash returns native contract script hash by its name.
func (bc *Blockchain) GetNativeContractScriptHash(name string) (util.Uint160, error) {
c := bc.contracts.ByName(name)
if c != nil {
return c.Metadata().Hash, nil
}
return util.Uint160{}, errors.New("Unknown native contract")
}
// GetConfig returns the config stored in the blockchain. // GetConfig returns the config stored in the blockchain.
func (bc *Blockchain) GetConfig() config.ProtocolConfiguration { func (bc *Blockchain) GetConfig() config.ProtocolConfiguration {
return bc.config return bc.config

View file

@ -39,6 +39,7 @@ type Blockchainer interface {
HasBlock(util.Uint256) bool HasBlock(util.Uint256) bool
HasTransaction(util.Uint256) bool HasTransaction(util.Uint256) bool
GetAppExecResult(util.Uint256) (*state.AppExecResult, error) GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
GetNativeContractScriptHash(string) (util.Uint160, error)
GetNextBlockValidators() ([]*keys.PublicKey, error) GetNextBlockValidators() ([]*keys.PublicKey, error)
GetNEP5Balances(util.Uint160) *state.NEP5Balances GetNEP5Balances(util.Uint160) *state.NEP5Balances
GetValidators() ([]*keys.PublicKey, error) GetValidators() ([]*keys.PublicKey, error)

View file

@ -2,6 +2,7 @@ package native
import ( import (
"errors" "errors"
"strings"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -35,6 +36,17 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract {
return nil return nil
} }
// ByName returns native contract with the specified name.
func (cs *Contracts) ByName(name string) interop.Contract {
name = strings.ToLower(name)
for _, ctr := range cs.Contracts {
if strings.ToLower(ctr.Metadata().Name) == name {
return ctr
}
}
return nil
}
// NewContracts returns new set of native contracts with new GAS, NEO and Policy // NewContracts returns new set of native contracts with new GAS, NEO and Policy
// contracts. // contracts.
func NewContracts() *Contracts { func NewContracts() *Contracts {

View file

@ -89,6 +89,9 @@ func (chain testChain) GetContractState(hash util.Uint160) *state.Contract {
func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) { func (chain testChain) GetContractScriptHash(id int32) (util.Uint160, error) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetNativeContractScriptHash(name string) (util.Uint160, error) {
panic("TODO")
}
func (chain testChain) GetHeaderHash(int) util.Uint256 { func (chain testChain) GetHeaderHash(int) util.Uint256 {
return util.Uint256{} return util.Uint256{}
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response" "github.com/nspcc-dev/neo-go/pkg/rpc/response"
"github.com/nspcc-dev/neo-go/pkg/util"
) )
const ( const (
@ -53,6 +54,7 @@ type Options struct {
// cache stores cache values for the RPC client methods // cache stores cache values for the RPC client methods
type cache struct { type cache struct {
calculateValidUntilBlock calculateValidUntilBlockCache calculateValidUntilBlock calculateValidUntilBlockCache
nativeHashes map[string]util.Uint160
} }
// calculateValidUntilBlockCache stores cached number of validators and // calculateValidUntilBlockCache stores cached number of validators and
@ -95,21 +97,34 @@ func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
ctx: ctx, ctx: ctx,
cli: httpClient, cli: httpClient,
endpoint: url, endpoint: url,
cache: cache{
nativeHashes: make(map[string]util.Uint160),
},
} }
cl.opts = opts cl.opts = opts
cl.requestF = cl.makeHTTPRequest cl.requestF = cl.makeHTTPRequest
return cl, nil return cl, nil
} }
// Init sets magic of the network client connected to. This method should be called // Init sets magic of the network client connected to and native NEO and GAS
// before any transaction-, header- or block-related requests in order to deserialize // contracts scripthashes. This method should be called before any transaction-,
// responses properly. // header- or block-related requests in order to deserialize responses properly.
func (c *Client) Init() error { func (c *Client) Init() error {
version, err := c.GetVersion() version, err := c.GetVersion()
if err != nil { if err != nil {
return fmt.Errorf("failed to get network magic: %w", err) return fmt.Errorf("failed to get network magic: %w", err)
} }
c.network = version.Magic c.network = version.Magic
neoContractHash, err := c.GetContractStateByAddressOrName("neo")
if err != nil {
return fmt.Errorf("failed to get NEO contract scripthash: %w", err)
}
c.cache.nativeHashes["neo"] = neoContractHash.ScriptHash()
gasContractHash, err := c.GetContractStateByAddressOrName("gas")
if err != nil {
return fmt.Errorf("failed to get GAS contract scripthash: %w", err)
}
c.cache.nativeHashes["gas"] = gasContractHash.ScriptHash()
c.initDone = true c.initDone = true
return nil return nil
} }

View file

@ -23,13 +23,6 @@ type TransferTarget struct {
Amount int64 Amount int64
} }
var (
// NeoContractHash is a hash of the NEO native contract.
NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde")
// GasContractHash is a hash of the GAS native contract.
GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66")
)
// NEP5Decimals invokes `decimals` NEP5 method on a specified contract. // NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) { func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) {
result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil) result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil)

View file

@ -4,6 +4,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
@ -208,10 +209,25 @@ func (c *Client) GetCommittee() (keys.PublicKeys, error) {
return *resp, nil return *resp, nil
} }
// GetContractState queries contract information, according to the contract script hash. // GetContractStateByHash queries contract information, according to the contract script hash.
func (c *Client) GetContractState(hash util.Uint160) (*state.Contract, error) { 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 ( var (
params = request.NewRawParams(hash.StringLE()) params = request.NewRawParams(param)
resp = &state.Contract{} resp = &state.Contract{}
) )
if err := c.performRequest("getcontractstate", params, resp); err != nil { if err := c.performRequest("getcontractstate", params, resp); err != nil {
@ -597,3 +613,18 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
func (c *Client) GetNetwork() netmode.Magic { func (c *Client) GetNetwork() netmode.Magic {
return c.network return c.network
} }
// GetNativeContractHash returns native contract hash by its name. It is not case-sensitive.
func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) {
lowercasedName := strings.ToLower(name)
hash, ok := c.cache.nativeHashes[lowercasedName]
if ok {
return hash, nil
}
cs, err := c.GetContractStateByAddressOrName(name)
if err != nil {
return util.Uint160{}, err
}
c.cache.nativeHashes[lowercasedName] = cs.ScriptHash()
return cs.ScriptHash(), nil
}

View file

@ -315,13 +315,57 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}, },
"getcontractstate": { "getcontractstate": {
{ {
name: "positive", name: "positive, by hash",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176") hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetContractState(hash) return c.GetContractStateByHash(hash)
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
result: func(c *Client) interface{} {
script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
if err != nil {
panic(err)
}
m := manifest.NewManifest(hash.Hash160(script))
m.Features = smartcontract.HasStorage
cs := &state.Contract{
ID: 0,
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},
{
name: "positive, by address",
invoke: func(c *Client) (interface{}, error) {
return c.GetContractStateByAddressOrName("NWiu5oejTu925aeL9Hc1LX8SvaJhE23h15")
},
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
result: func(c *Client) interface{} {
script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
if err != nil {
panic(err)
}
m := manifest.NewManifest(hash.Hash160(script))
m.Features = smartcontract.HasStorage
cs := &state.Contract{
ID: 0,
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},
{
name: "positive, by id",
invoke: func(c *Client) (interface{}, error) {
return c.GetContractStateByID(0)
}, },
serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"supportedstandards":[],"safemethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
@ -645,7 +689,7 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
}, },
"invokefunction": { "invokefunction": {
{ {
name: "positive", name: "positive, by scripthash",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26") hash, err := util.Uint160DecodeStringLE("91b83e96f2a7c4fdf0c1688441ec61986c7cae26")
if err != nil { if err != nil {
@ -991,7 +1035,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "getcontractstate_invalid_params_error", name: "getcontractstate_invalid_params_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetContractState(util.Uint160{}) return c.GetContractStateByHash(util.Uint160{})
}, },
}, },
{ {
@ -1178,7 +1222,7 @@ var rpcClientErrorCases = map[string][]rpcClientErrorCase{
{ {
name: "getcontractstate_unmarshalling_error", name: "getcontractstate_unmarshalling_error",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
return c.GetContractState(util.Uint160{}) return c.GetContractStateByHash(util.Uint160{})
}, },
}, },
{ {
@ -1372,13 +1416,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server {
if err != nil { if err != nil {
t.Fatalf("Cannot decode request body: %s", req.Body) t.Fatalf("Cannot decode request body: %s", req.Body)
} }
var response string response := wrapInitResponse(r, resp)
switch r.Method {
case "getversion":
response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`
default:
response = resp
}
ws.SetWriteDeadline(time.Now().Add(2 * time.Second)) ws.SetWriteDeadline(time.Now().Add(2 * time.Second))
err = ws.WriteMessage(1, []byte(response)) err = ws.WriteMessage(1, []byte(response))
if err != nil { if err != nil {
@ -1393,27 +1431,49 @@ func initTestServer(t *testing.T, resp string) *httptest.Server {
if err != nil { if err != nil {
t.Fatalf("Cannot decode request body: %s", req.Body) t.Fatalf("Cannot decode request body: %s", req.Body)
} }
requestHandler(t, r.Method, w, resp) requestHandler(t, r, w, resp)
})) }))
return srv return srv
} }
func requestHandler(t *testing.T, method string, w http.ResponseWriter, resp string) { func requestHandler(t *testing.T, r *request.In, w http.ResponseWriter, resp string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
var response string response := wrapInitResponse(r, resp)
switch method {
case "getversion":
response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`
default:
response = resp
}
_, err := w.Write([]byte(response)) _, err := w.Write([]byte(response))
if err != nil { if err != nil {
t.Fatalf("Error writing response: %s", err.Error()) t.Fatalf("Error writing response: %s", err.Error())
} }
} }
func wrapInitResponse(r *request.In, resp string) string {
var response string
switch r.Method {
case "getversion":
response = `{"id":1,"jsonrpc":"2.0","result":{"magic":42,"tcpport":20332,"wsport":20342,"nonce":2153672787,"useragent":"/NEO-GO:0.73.1-pre-273-ge381358/"}}`
case "getcontractstate":
p, err := r.Params()
if err != nil {
response = resp
}
name, err := p.ValueWithType(0, request.StringT).GetString()
if err != nil {
response = resp
}
switch name {
case "neo":
response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getСommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getСommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}`
case "gas":
response = `{"id":1,"jsonrpc":"2.0","result":{"id":-2,"script":"DANHQVNBa2d4Cw==","manifest":{"abi":{"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"features":{"payable":false,"storage":false},"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf"],"extra":null},"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc"}}`
default:
response = resp
}
default:
response = resp
}
return response
}
func TestCalculateValidUntilBlock(t *testing.T) { func TestCalculateValidUntilBlock(t *testing.T) {
var ( var (
getBlockCountCalled int getBlockCountCalled int
@ -1434,7 +1494,7 @@ func TestCalculateValidUntilBlock(t *testing.T) {
getValidatorsCalled++ getValidatorsCalled++
response = `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}` response = `{"id":1,"jsonrpc":"2.0","result":[{"publickey":"02b3622bf4017bdfe317c58aed5f4c753f206b7db896046fa7d774bbc4bf7f8dc2","votes":"0","active":true},{"publickey":"02103a7f7dd016558597f7960d27c516a4394fd968b9e65155eb4b013e4040406e","votes":"0","active":true},{"publickey":"03d90c07df63e690ce77912e10ab51acc944b66860237b608c4f8f8309e71ee699","votes":"0","active":true},{"publickey":"02a7bc55fe8684e0119768d104ba30795bdcc86619e864add26156723ed185cd62","votes":"0","active":true}]}`
} }
requestHandler(t, r.Method, w, response) requestHandler(t, r, w, response)
})) }))
defer srv.Close() defer srv.Close()
@ -1462,8 +1522,13 @@ func TestCalculateValidUntilBlock(t *testing.T) {
func TestGetNetwork(t *testing.T) { func TestGetNetwork(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
r := request.NewIn()
err := r.DecodeData(req.Body)
if err != nil {
t.Fatalf("Cannot decode request body: %s", req.Body)
}
// request handler already have `getversion` response wrapper // request handler already have `getversion` response wrapper
requestHandler(t, "getversion", w, "") requestHandler(t, r, w, "")
})) }))
defer srv.Close() defer srv.Close()
endpoint := srv.URL endpoint := srv.URL

View file

@ -277,7 +277,10 @@ func TestCreateNEP5TransferTx(t *testing.T) {
acc, err := wallet.NewAccountFromWIF(priv.WIF()) acc, err := wallet.NewAccountFromWIF(priv.WIF())
require.NoError(t, err) require.NoError(t, err)
tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, client.GasContractHash, 1000, 0) gasContractHash, err := c.GetNativeContractHash("gas")
require.NoError(t, err)
tx, err := c.CreateNEP5TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0)
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, acc.SignTx(tx)) require.NoError(t, acc.SignTx(tx))
require.NoError(t, chain.VerifyTx(tx)) require.NoError(t, chain.VerifyTx(tx))

View file

@ -765,6 +765,42 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err
return result, nil return result, nil
} }
// getContractScriptHashFromParam returns the contract script hash by hex contract hash, address, id or native contract name.
func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160, *response.Error) {
var result util.Uint160
if param == nil {
return result, response.ErrInvalidParams
}
switch param.Type {
case request.StringT:
var err error
result, err = param.GetUint160FromAddressOrHex()
if err == nil {
return result, nil
}
name, err := param.GetString()
if err != nil {
return result, response.ErrInvalidParams
}
result, err = s.chain.GetNativeContractScriptHash(name)
if err != nil {
return result, response.NewRPCError("Unknown contract: querying by name is supported for native contracts only", "", nil)
}
case request.NumberT:
id, err := param.GetInt()
if err != nil {
return result, response.ErrInvalidParams
}
result, err = s.chain.GetContractScriptHash(int32(id))
if err != nil {
return result, response.NewRPCError("Unknown contract", "", err)
}
default:
return result, response.ErrInvalidParams
}
return result, nil
}
func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) { func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
id, rErr := s.contractIDFromParam(ps.Value(0)) id, rErr := s.contractIDFromParam(ps.Value(0))
if rErr == response.ErrUnknown { if rErr == response.ErrUnknown {
@ -828,11 +864,12 @@ func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response
return height, nil return height, nil
} }
// getContractState returns contract state (contract information, according to the contract script hash). // getContractState returns contract state (contract information, according to the contract script hash,
// contract id or native contract name).
func (s *Server) getContractState(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) getContractState(reqParams request.Params) (interface{}, *response.Error) {
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex() scriptHash, err := s.contractScriptHashFromParam(reqParams.Value(0))
if err != nil { if err != nil {
return nil, response.ErrInvalidParams return nil, err
} }
cs := s.chain.GetContractState(scriptHash) cs := s.chain.GetContractState(scriptHash)
if cs == nil { if cs == nil {
@ -947,9 +984,9 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
// invokeFunction implements the `invokeFunction` RPC call. // invokeFunction implements the `invokeFunction` RPC call.
func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) { func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
scriptHash, err := reqParams.ValueWithType(0, request.StringT).GetUint160FromHex() scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
if err != nil { if responseErr != nil {
return nil, response.ErrInvalidParams return nil, responseErr
} }
tx := &transaction.Transaction{} tx := &transaction.Transaction{}
checkWitnessHashesIndex := len(reqParams) checkWitnessHashesIndex := len(reqParams)

View file

@ -96,7 +96,7 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
"getcontractstate": { "getcontractstate": {
{ {
name: "positive", name: "positive, by hash",
params: fmt.Sprintf(`["%s"]`, testContractHash), params: fmt.Sprintf(`["%s"]`, testContractHash),
result: func(e *executor) interface{} { return &state.Contract{} }, result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) { check: func(t *testing.T, e *executor, cs interface{}) {
@ -106,10 +106,50 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
}, },
{ {
name: "negative", name: "positive, by id",
params: `[0]`,
result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*state.Contract)
require.True(t, ok)
assert.Equal(t, int32(0), res.ID)
},
},
{
name: "positive, native by id",
params: `[-3]`,
result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*state.Contract)
require.True(t, ok)
assert.Equal(t, int32(-3), res.ID)
},
},
{
name: "positive, native by name",
params: `["Policy"]`,
result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*state.Contract)
require.True(t, ok)
assert.Equal(t, int32(-3), res.ID)
},
},
{
name: "negative, bad hash",
params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`, params: `["6d1eeca891ee93de2b7a77eb91c26f3b3c04d6c3"]`,
fail: true, fail: true,
}, },
{
name: "negative, bad ID",
params: `[-8]`,
fail: true,
},
{
name: "negative, bad native name",
params: `["unknown_native"]`,
fail: true,
},
{ {
name: "no params", name: "no params",
params: `[]`, params: `[]`,