From 30e7d9a8b0d681e19b3c6ac80fcaae914bb0cab9 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 23 Mar 2021 22:05:14 +0300 Subject: [PATCH 1/8] rpc: remove duplicated code from CreateNEP17MultiTransferTx --- pkg/rpc/client/nep17.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index f9ce80a7f..1c0cf5e4b 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -143,13 +143,9 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, reci if w.Err != nil { return nil, fmt.Errorf("failed to create transfer script: %w", w.Err) } - accAddr, err := address.StringToUint160(acc.Address) - if err != nil { - return nil, fmt.Errorf("bad account address: %v", err) - } return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{ Signer: transaction.Signer{ - Account: accAddr, + Account: from, Scopes: transaction.CalledByEntry, }, Account: acc, From 347f7ed576d33aa74a749c906bfbe33e47ed9bac Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 23 Mar 2021 22:50:15 +0300 Subject: [PATCH 2/8] rpc: move all `top*FromStack` client functions to a separate file Some of them are shared between multiple RPC client APIs, so let's keep them in a separate place for better maintainability. --- pkg/rpc/client/helper.go | 77 ++++++++++++++++++++++++++++++++++++++++ pkg/rpc/client/native.go | 27 -------------- pkg/rpc/client/nep17.go | 32 ----------------- pkg/rpc/client/policy.go | 11 ------ 4 files changed, 77 insertions(+), 70 deletions(-) create mode 100644 pkg/rpc/client/helper.go diff --git a/pkg/rpc/client/helper.go b/pkg/rpc/client/helper.go new file mode 100644 index 000000000..421f18c52 --- /dev/null +++ b/pkg/rpc/client/helper.go @@ -0,0 +1,77 @@ +package client + +import ( + "crypto/elliptic" + "errors" + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" +) + +// getInvocationError returns an error in case of bad VM state or empty stack. +func getInvocationError(result *result.Invoke) error { + if result.State != "HALT" { + return fmt.Errorf("invocation failed: %s", result.FaultException) + } + if len(result.Stack) == 0 { + return errors.New("result stack is empty") + } + return nil +} + +// topBoolFromStack returns the top boolean value from stack. +func topBoolFromStack(st []stackitem.Item) (bool, error) { + index := len(st) - 1 // top stack element is last in the array + result, ok := st[index].Value().(bool) + if !ok { + return false, fmt.Errorf("invalid stack item type: %s", st[index].Type()) + } + return result, nil +} + +// topIntFromStack returns the top integer value from stack. +func topIntFromStack(st []stackitem.Item) (int64, error) { + index := len(st) - 1 // top stack element is last in the array + bi, err := st[index].TryInteger() + if err != nil { + return 0, err + } + return bi.Int64(), nil +} + +// topPublicKeysFromStack returns the top array of public keys from stack. +func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) { + index := len(st) - 1 // top stack element is last in the array + var ( + pks keys.PublicKeys + err error + ) + items, ok := st[index].Value().([]stackitem.Item) + if !ok { + return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type()) + } + pks = make(keys.PublicKeys, len(items)) + for i, item := range items { + val, ok := item.Value().([]byte) + if !ok { + return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type()) + } + pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256()) + if err != nil { + return nil, err + } + } + return pks, nil +} + +// top string from stack returns the top string from stack. +func topStringFromStack(st []stackitem.Item) (string, error) { + index := len(st) - 1 // top stack element is last in the array + bs, err := st[index].TryBytes() + if err != nil { + return "", err + } + return string(bs), nil +} diff --git a/pkg/rpc/client/native.go b/pkg/rpc/client/native.go index 883910354..770139790 100644 --- a/pkg/rpc/client/native.go +++ b/pkg/rpc/client/native.go @@ -3,14 +3,12 @@ package client // Various non-policy things from native contracts. import ( - "crypto/elliptic" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // GetOraclePrice invokes `getPrice` method on a native Oracle contract. @@ -65,28 +63,3 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu } return topPublicKeysFromStack(result.Stack) } - -// topPublicKeysFromStack returns the top array of public keys from stack. -func topPublicKeysFromStack(st []stackitem.Item) (keys.PublicKeys, error) { - index := len(st) - 1 // top stack element is last in the array - var ( - pks keys.PublicKeys - err error - ) - items, ok := st[index].Value().([]stackitem.Item) - if !ok { - return nil, fmt.Errorf("invalid stack item type: %s", st[index].Type()) - } - pks = make(keys.PublicKeys, len(items)) - for i, item := range items { - val, ok := item.Value().([]byte) - if !ok { - return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type()) - } - pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256()) - if err != nil { - return nil, err - } - } - return pks, nil -} diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index 1c0cf5e4b..7aba4cdab 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -1,19 +1,16 @@ package client import ( - "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" - "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/callflag" "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/opcode" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" ) @@ -221,32 +218,3 @@ func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients [ return c.SendRawTransaction(tx) } - -func topIntFromStack(st []stackitem.Item) (int64, error) { - index := len(st) - 1 // top stack element is last in the array - bi, err := st[index].TryInteger() - if err != nil { - return 0, err - } - return bi.Int64(), nil -} - -func topStringFromStack(st []stackitem.Item) (string, error) { - index := len(st) - 1 // top stack element is last in the array - bs, err := st[index].TryBytes() - if err != nil { - return "", err - } - return string(bs), nil -} - -// getInvocationError returns an error in case of bad VM state or empty stack. -func getInvocationError(result *result.Invoke) error { - if result.State != "HALT" { - return fmt.Errorf("invocation failed: %s", result.FaultException) - } - if len(result.Stack) == 0 { - return errors.New("result stack is empty") - } - return nil -} diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index 674d7314e..e8644a025 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) // GetFeePerByte invokes `getFeePerByte` method on a native Policy contract. @@ -80,13 +79,3 @@ func (c *Client) IsBlocked(hash util.Uint160) (bool, error) { } return topBoolFromStack(result.Stack) } - -// topBoolFromStack returns the top boolean value from stack -func topBoolFromStack(st []stackitem.Item) (bool, error) { - index := len(st) - 1 // top stack element is last in the array - result, ok := st[index].Value().(bool) - if !ok { - return false, fmt.Errorf("invalid stack item type: %s", st[index].Type()) - } - return result, nil -} From a2a9d7ff05640d60c198a14b773af93e61d87292 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 13:16:38 +0300 Subject: [PATCH 3/8] core: move NNS record types to a separate package We need this to avoid `native` dependency in RPC client. --- pkg/compiler/native_test.go | 9 +-- pkg/core/native/name_service.go | 40 +++++-------- pkg/core/native/name_service_test.go | 73 ++++++++++++------------ pkg/core/native/nnsrecords/nnsrecords.go | 12 ++++ pkg/core/native_name_service_test.go | 63 ++++++++++---------- 5 files changed, 101 insertions(+), 96 deletions(-) create mode 100644 pkg/core/native/nnsrecords/nnsrecords.go diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 457c2b8df..a8ac1361e 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/gas" @@ -77,10 +78,10 @@ func TestRoleManagementRole(t *testing.T) { } func TestNameServiceRecordType(t *testing.T) { - require.EqualValues(t, native.RecordTypeA, nameservice.TypeA) - require.EqualValues(t, native.RecordTypeCNAME, nameservice.TypeCNAME) - require.EqualValues(t, native.RecordTypeTXT, nameservice.TypeTXT) - require.EqualValues(t, native.RecordTypeAAAA, nameservice.TypeAAAA) + require.EqualValues(t, nnsrecords.A, nameservice.TypeA) + require.EqualValues(t, nnsrecords.CNAME, nameservice.TypeCNAME) + require.EqualValues(t, nnsrecords.TXT, nameservice.TypeTXT) + require.EqualValues(t, nnsrecords.AAAA, nameservice.TypeAAAA) } func TestCryptoLibNamedCurve(t *testing.T) { diff --git a/pkg/core/native/name_service.go b/pkg/core/native/name_service.go index 4edb45155..d39d8e3c4 100644 --- a/pkg/core/native/name_service.go +++ b/pkg/core/native/name_service.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" @@ -42,17 +43,6 @@ type nameState struct { Admin util.Uint160 } -// RecordType represents name record type. -type RecordType byte - -// Pre-defined record types. -const ( - RecordTypeA RecordType = 1 - RecordTypeCNAME RecordType = 5 - RecordTypeTXT RecordType = 16 - RecordTypeAAAA RecordType = 28 -) - const ( nameServiceID = -10 @@ -448,19 +438,19 @@ func (n *NameService) setRecord(ic *interop.Context, args []stackitem.Item) stac return stackitem.Null{} } -func checkName(rt RecordType, name string) { +func checkName(rt nnsrecords.Type, name string) { var valid bool switch rt { - case RecordTypeA: + case nnsrecords.A: // We can't rely on `len(ip) == net.IPv4len` because // IPv4 can be parsed to mapped representation. valid = ipv4Regex.MatchString(name) && net.ParseIP(name) != nil - case RecordTypeCNAME: + case nnsrecords.CNAME: valid = matchName(name) - case RecordTypeTXT: + case nnsrecords.TXT: valid = utf8.RuneCountInString(name) <= 255 - case RecordTypeAAAA: + case nnsrecords.AAAA: valid = ipv6Regex.MatchString(name) && net.ParseIP(name) != nil } @@ -513,7 +503,7 @@ func (n *NameService) resolve(ic *interop.Context, args []stackitem.Item) stacki return stackitem.NewByteArray([]byte(result)) } -func (n *NameService) resolveInternal(ic *interop.Context, name string, t RecordType, redirect int) (string, bool) { +func (n *NameService) resolveInternal(ic *interop.Context, name string, t nnsrecords.Type, redirect int) (string, bool) { if redirect < 0 { panic("invalid redirect") } @@ -521,26 +511,26 @@ func (n *NameService) resolveInternal(ic *interop.Context, name string, t Record if data, ok := records[t]; ok { return data, true } - data, ok := records[RecordTypeCNAME] + data, ok := records[nnsrecords.CNAME] if !ok { return "", false } return n.resolveInternal(ic, data, t, redirect-1) } -func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[RecordType]string { +func (n *NameService) getRecordsInternal(d dao.DAO, name string) map[nnsrecords.Type]string { domain := toDomain(name) key := makeRecordKey(domain, name, 0) key = key[:len(key)-1] - res := make(map[RecordType]string) + res := make(map[nnsrecords.Type]string) d.Seek(n.ID, key, func(k, v []byte) { - rt := RecordType(k[len(k)-1]) + rt := nnsrecords.Type(k[len(k)-1]) res[rt] = string(v) }) return res } -func makeRecordKey(domain, name string, rt RecordType) []byte { +func makeRecordKey(domain, name string, rt nnsrecords.Type) []byte { key := make([]byte, 1+util.Uint160Size+util.Uint160Size+1) key[0] = prefixRecord i := 1 @@ -647,7 +637,7 @@ func toDomain(name string) string { return domain } -func toRecordType(item stackitem.Item) RecordType { +func toRecordType(item stackitem.Item) nnsrecords.Type { bi, err := item.TryInteger() if err != nil || !bi.IsInt64() { panic("invalid record type") @@ -656,8 +646,8 @@ func toRecordType(item stackitem.Item) RecordType { if val > math.MaxUint8 { panic("invalid record type") } - switch rt := RecordType(val); rt { - case RecordTypeA, RecordTypeCNAME, RecordTypeTXT, RecordTypeAAAA: + switch rt := nnsrecords.Type(val); rt { + case nnsrecords.A, nnsrecords.CNAME, nnsrecords.TXT, nnsrecords.AAAA: return rt default: panic("invalid record type") diff --git a/pkg/core/native/name_service_test.go b/pkg/core/native/name_service_test.go index c8fccf0e7..bc741051f 100644 --- a/pkg/core/native/name_service_test.go +++ b/pkg/core/native/name_service_test.go @@ -3,6 +3,7 @@ package native import ( "testing" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard" "github.com/stretchr/testify/require" @@ -36,45 +37,45 @@ func TestParseDomain(t *testing.T) { func TestNameService_CheckName(t *testing.T) { // tests are got from the C# implementation testCases := []struct { - Type RecordType + Type nnsrecords.Type Name string ShouldFail bool }{ - {Type: RecordTypeA, Name: "0.0.0.0"}, - {Type: RecordTypeA, Name: "10.10.10.10"}, - {Type: RecordTypeA, Name: "255.255.255.255"}, - {Type: RecordTypeA, Name: "192.168.1.1"}, - {Type: RecordTypeA, Name: "1a", ShouldFail: true}, - {Type: RecordTypeA, Name: "256.0.0.0", ShouldFail: true}, - {Type: RecordTypeA, Name: "01.01.01.01", ShouldFail: true}, - {Type: RecordTypeA, Name: "00.0.0.0", ShouldFail: true}, - {Type: RecordTypeA, Name: "0.0.0.-1", ShouldFail: true}, - {Type: RecordTypeA, Name: "0.0.0.0.1", ShouldFail: true}, - {Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true}, - {Type: RecordTypeA, Name: "11111111.11111111.11111111.11111111", ShouldFail: true}, - {Type: RecordTypeA, Name: "ff.ff.ff.ff", ShouldFail: true}, - {Type: RecordTypeA, Name: "0.0.256", ShouldFail: true}, - {Type: RecordTypeA, Name: "0.0.0", ShouldFail: true}, - {Type: RecordTypeA, Name: "0.257", ShouldFail: true}, - {Type: RecordTypeA, Name: "1.1", ShouldFail: true}, - {Type: RecordTypeA, Name: "257", ShouldFail: true}, - {Type: RecordTypeA, Name: "1", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "2001:db8::8:800:200c:417a"}, - {Type: RecordTypeAAAA, Name: "ff01::101"}, - {Type: RecordTypeAAAA, Name: "::1"}, - {Type: RecordTypeAAAA, Name: "::"}, - {Type: RecordTypeAAAA, Name: "2001:db8:0:0:8:800:200c:417a"}, - {Type: RecordTypeAAAA, Name: "ff01:0:0:0:0:0:0:101"}, - {Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:1"}, - {Type: RecordTypeAAAA, Name: "0:0:0:0:0:0:0:0"}, - {Type: RecordTypeAAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "FF01::101", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "fF01::101", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "::ffff:1.01.1.01", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true}, - {Type: RecordTypeAAAA, Name: "::13.1.68.3", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.0.0.0"}, + {Type: nnsrecords.A, Name: "10.10.10.10"}, + {Type: nnsrecords.A, Name: "255.255.255.255"}, + {Type: nnsrecords.A, Name: "192.168.1.1"}, + {Type: nnsrecords.A, Name: "1a", ShouldFail: true}, + {Type: nnsrecords.A, Name: "256.0.0.0", ShouldFail: true}, + {Type: nnsrecords.A, Name: "01.01.01.01", ShouldFail: true}, + {Type: nnsrecords.A, Name: "00.0.0.0", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.0.0.-1", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.0.0.0.1", ShouldFail: true}, + {Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true}, + {Type: nnsrecords.A, Name: "11111111.11111111.11111111.11111111", ShouldFail: true}, + {Type: nnsrecords.A, Name: "ff.ff.ff.ff", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.0.256", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.0.0", ShouldFail: true}, + {Type: nnsrecords.A, Name: "0.257", ShouldFail: true}, + {Type: nnsrecords.A, Name: "1.1", ShouldFail: true}, + {Type: nnsrecords.A, Name: "257", ShouldFail: true}, + {Type: nnsrecords.A, Name: "1", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "2001:db8::8:800:200c:417a"}, + {Type: nnsrecords.AAAA, Name: "ff01::101"}, + {Type: nnsrecords.AAAA, Name: "::1"}, + {Type: nnsrecords.AAAA, Name: "::"}, + {Type: nnsrecords.AAAA, Name: "2001:db8:0:0:8:800:200c:417a"}, + {Type: nnsrecords.AAAA, Name: "ff01:0:0:0:0:0:0:101"}, + {Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:1"}, + {Type: nnsrecords.AAAA, Name: "0:0:0:0:0:0:0:0"}, + {Type: nnsrecords.AAAA, Name: "2001:DB8::8:800:200C:417A", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "FF01::101", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "fF01::101", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:417A", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "FF01:0:0:0:0:0:0:101", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "::ffff:1.01.1.01", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "2001:DB8:0:0:8:800:200C:4Z", ShouldFail: true}, + {Type: nnsrecords.AAAA, Name: "::13.1.68.3", ShouldFail: true}, } for _, testCase := range testCases { if testCase.ShouldFail { diff --git a/pkg/core/native/nnsrecords/nnsrecords.go b/pkg/core/native/nnsrecords/nnsrecords.go new file mode 100644 index 000000000..958f3f1bb --- /dev/null +++ b/pkg/core/native/nnsrecords/nnsrecords.go @@ -0,0 +1,12 @@ +package nnsrecords + +// Type represents name record type. +type Type byte + +// Pre-defined record types. +const ( + A Type = 1 + CNAME Type = 5 + TXT Type = 16 + AAAA Type = 28 +) diff --git a/pkg/core/native_name_service_test.go b/pkg/core/native_name_service_test.go index e79e559d4..808544454 100644 --- a/pkg/core/native_name_service_test.go +++ b/pkg/core/native_name_service_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" @@ -67,7 +68,7 @@ func TestExpiration(t *testing.T) { true, "first.com", acc.Contract.ScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, - "setRecord", stackitem.Null{}, "first.com", int64(native.RecordTypeTXT), "sometext") + "setRecord", stackitem.Null{}, "first.com", int64(nnsrecords.TXT), "sometext") b1 := bc.topBlock.Load().(*block.Block) tx, err := prepareContractMethodInvokeGeneric(bc, defaultRegisterSysfee, bc.contracts.NameService.Hash, @@ -108,7 +109,7 @@ func TestExpiration(t *testing.T) { checkResult(t, &aer[0], stackitem.NewBool(false)) tx, err = prepareContractMethodInvokeGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, - "getRecord", acc, "first.com", int64(native.RecordTypeTXT)) + "getRecord", acc, "first.com", int64(nnsrecords.TXT)) require.NoError(t, err) b5 := newBlockCustom(bc.GetConfig(), func(b *block.Block) { b.Index = b4.Index + 1 @@ -182,36 +183,36 @@ func TestSetGetRecord(t *testing.T) { testNameServiceInvoke(t, bc, "addRoot", stackitem.Null{}, "com") t.Run("set before register", func(t *testing.T) { - testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeTXT), "sometext") + testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.TXT), "sometext") }) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "register", true, "neo.com", testchain.CommitteeScriptHash()) t.Run("invalid parameters", func(t *testing.T) { testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(0xFF), "1.2.3.4") - testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(native.RecordTypeA), "not.an.ip.address") + testNameServiceInvoke(t, bc, "setRecord", nil, "neo.com", int64(nnsrecords.A), "not.an.ip.address") }) t.Run("invalid witness", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil, - "neo.com", int64(native.RecordTypeA), "1.2.3.4") + "neo.com", int64(nnsrecords.A), "1.2.3.4") }) - testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA)) - testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") - testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) - testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeA), "1.2.3.4") - testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) - testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeAAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf") - testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME), "nspcc.ru") - testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeTXT), "sometext") + testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A)) + testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4") + testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A)) + testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.A), "1.2.3.4") + testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A)) + testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.AAAA), "2001:0000:1f1f:0000:0000:0100:11a0:addf") + testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME), "nspcc.ru") + testNameServiceInvoke(t, bc, "setRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.TXT), "sometext") // Delete record. t.Run("invalid witness", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", nil, - "neo.com", int64(native.RecordTypeCNAME)) + "neo.com", int64(nnsrecords.CNAME)) }) - testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(native.RecordTypeCNAME)) - testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME)) - testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(native.RecordTypeCNAME)) - testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(native.RecordTypeA)) + testNameServiceInvoke(t, bc, "getRecord", "nspcc.ru", "neo.com", int64(nnsrecords.CNAME)) + testNameServiceInvoke(t, bc, "deleteRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME)) + testNameServiceInvoke(t, bc, "getRecord", stackitem.Null{}, "neo.com", int64(nnsrecords.CNAME)) + testNameServiceInvoke(t, bc, "getRecord", "1.2.3.4", "neo.com", int64(nnsrecords.A)) } func TestSetAdmin(t *testing.T) { @@ -239,20 +240,20 @@ func TestSetAdmin(t *testing.T) { t.Run("set and delete by admin", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeTXT), "sometext") + "neo.com", int64(nnsrecords.TXT), "sometext") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, guest, "deleteRecord", nil, - "neo.com", int64(native.RecordTypeTXT)) + "neo.com", int64(nnsrecords.TXT)) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeTXT)) + "neo.com", int64(nnsrecords.TXT)) }) t.Run("set admin to null", func(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "setRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeTXT), "sometext") + "neo.com", int64(nnsrecords.TXT), "sometext") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, owner, "setAdmin", stackitem.Null{}, "neo.com", nil) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, admin, "deleteRecord", nil, - "neo.com", int64(native.RecordTypeTXT)) + "neo.com", int64(nnsrecords.TXT)) }) } @@ -267,7 +268,7 @@ func TestTransfer(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "register", true, "neo.com", from.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, from, "setRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeA), "1.2.3.4") + "neo.com", int64(nnsrecords.A), "1.2.3.4") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, from, "transfer", nil, to.Contract.ScriptHash().BytesBE(), []byte("not.exists")) testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, true, "transfer", @@ -355,23 +356,23 @@ func TestResolve(t *testing.T) { testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register", true, "neo.com", acc.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeA), "1.2.3.4") + "neo.com", int64(nnsrecords.A), "1.2.3.4") testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "neo.com", int64(native.RecordTypeCNAME), "alias.com") + "neo.com", int64(nnsrecords.CNAME), "alias.com") testNameServiceInvokeAux(t, bc, defaultRegisterSysfee, acc, "register", true, "alias.com", acc.PrivateKey().GetScriptHash()) testNameServiceInvokeAux(t, bc, defaultNameServiceSysfee, acc, "setRecord", stackitem.Null{}, - "alias.com", int64(native.RecordTypeTXT), "sometxt") + "alias.com", int64(nnsrecords.TXT), "sometxt") testNameServiceInvoke(t, bc, "resolve", "1.2.3.4", - "neo.com", int64(native.RecordTypeA)) + "neo.com", int64(nnsrecords.A)) testNameServiceInvoke(t, bc, "resolve", "alias.com", - "neo.com", int64(native.RecordTypeCNAME)) + "neo.com", int64(nnsrecords.CNAME)) testNameServiceInvoke(t, bc, "resolve", "sometxt", - "neo.com", int64(native.RecordTypeTXT)) + "neo.com", int64(nnsrecords.TXT)) testNameServiceInvoke(t, bc, "resolve", stackitem.Null{}, - "neo.com", int64(native.RecordTypeAAAA)) + "neo.com", int64(nnsrecords.AAAA)) } const ( From ad9ede455e710d048381ea20c804ed579ac15710 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 15:36:10 +0300 Subject: [PATCH 4/8] rpc: add NNS-connected transactions to the test chain It is needed for RPC client integration tests. --- pkg/core/helper_test.go | 16 ++++++ pkg/rpc/server/server_test.go | 66 +++++++++++++++++++------ pkg/rpc/server/testdata/testblocks.acc | Bin 11104 -> 14439 bytes 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 447975817..5d8d8c47d 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -23,6 +23,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" @@ -484,6 +485,21 @@ func initBasicChain(t *testing.T, bc *Blockchain) { require.NoError(t, bc.AddBlock(b)) checkTxHalt(t, bc, txDeploy3.Hash()) + // register `neo.com` with A record type and priv0 owner via NNS + transferFundsToCommittee(t, bc) // block #11 + res, err := invokeContractMethodGeneric(bc, defaultNameServiceSysfee, + bc.contracts.NameService.Hash, "addRoot", true, "com") // block #12 + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + res, err = invokeContractMethodGeneric(bc, native.DefaultDomainPrice+defaultNameServiceSysfee, + bc.contracts.NameService.Hash, "register", acc0, "neo.com", priv0ScriptHash) // block #13 + require.NoError(t, err) + checkResult(t, res, stackitem.NewBool(true)) + res, err = invokeContractMethodGeneric(bc, defaultNameServiceSysfee, bc.contracts.NameService.Hash, + "setRecord", acc0, "neo.com", int64(nnsrecords.A), "1.2.3.4") // block #14 + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + // Compile contract to test `invokescript` RPC call _, _ = newDeployTx(t, bc, priv0ScriptHash, prefix+"invokescript_contract.go", "ContractForInvokescriptTest", nil) } diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index c29c4cdd3..3e3ecd01b 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -60,7 +60,7 @@ type rpcTestCase struct { } const testContractHash = "c6ca2347bb84b99807221365c900ec069a265e7c" -const deploymentTxHash = "fdd4f9252cde778010d14e9710efeeb80796fd38d778e9943c1d5bb2dc656c99" +const deploymentTxHash = "26692315f71f4790263160c0f570828be77c6927493ae6657a6e5a6a09229eb9" const genesisBlockHash = "5b60644c6c6f58faca72c70689d7ed1f40c2e795772bd0de5a88e983ad55080c" const verifyContractHash = "5bb4bac40e961e334ba7bd36d2496010f67e246e" @@ -654,7 +654,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) expected := result.UnclaimedGas{ Address: testchain.MultisigScriptHash(), - Unclaimed: *big.NewInt(5000), + Unclaimed: *big.NewInt(7000), } assert.Equal(t, expected, *actual) }, @@ -1415,7 +1415,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 11, actual.Confirmations) + assert.Equal(t, 15, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) @@ -1533,12 +1533,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{6, 7, 8, 9}, []int{1, 2}) }) + t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{8, 9, 10, 11}, []int{2, 3}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{3, 4}, []int{0}) }) - t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{6}, []int{1}) }) - t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{5, 6}, []int{1}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{7, 8}, []int{2}) }) + t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{5, 6}, []int{1}) }) + t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{8}, []int{2}) }) + t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{7, 8}, []int{2}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{9, 10}, []int{3}) }) }) } @@ -1639,8 +1639,8 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "68992456820", - LastUpdated: 10, + Amount: "67960042780", + LastUpdated: 14, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), } @@ -1649,7 +1649,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { } func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, []int{0, 1, 2, 3, 4, 5, 6}) + checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, []int{0, 1, 2, 3, 4, 5, 6, 7}) } func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -1658,6 +1658,19 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc rublesHash, err := util.Uint160DecodeStringLE(testContractHash) require.NoError(t, err) + blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(14)) // add type A record to `neo.com` domain via NNS + require.NoError(t, err) + require.Equal(t, 1, len(blockSetRecord.Transactions)) + txSetRecord := blockSetRecord.Transactions[0] + + blockRegisterDomain, err := e.chain.GetBlock(e.chain.GetHeaderHash(13)) // register `neo.com` domain via NNS + require.NoError(t, err) + require.Equal(t, 1, len(blockRegisterDomain.Transactions)) + txRegisterDomain := blockRegisterDomain.Transactions[0] + + blockGASBounty2, err := e.chain.GetBlock(e.chain.GetHeaderHash(12)) // size of committee = 6 + require.NoError(t, err) + blockDeploy3, err := e.chain.GetBlock(e.chain.GetHeaderHash(10)) // deploy verification_with_args_contract.go require.NoError(t, err) require.Equal(t, 1, len(blockDeploy3.Transactions)) @@ -1677,7 +1690,7 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc require.NoError(t, err) require.Equal(t, 1, len(blockSendRubles.Transactions)) txSendRubles := blockSendRubles.Transactions[0] - blockGASBounty := blockSendRubles // index 6 = size of committee + blockGASBounty1 := blockSendRubles // index 6 = size of committee blockReceiveRubles, err := e.chain.GetBlock(e.chain.GetHeaderHash(5)) require.NoError(t, err) @@ -1716,6 +1729,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc // duplicate the Server method. expected := result.NEP17Transfers{ Sent: []result.NEP17Transfer{ + { + Timestamp: blockSetRecord.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txSetRecord.SystemFee + txSetRecord.NetworkFee).String(), + Index: 14, + TxHash: blockSetRecord.Hash(), + }, + { + Timestamp: blockRegisterDomain.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txRegisterDomain.SystemFee + txRegisterDomain.NetworkFee).String(), + Index: 13, + TxHash: blockRegisterDomain.Hash(), + }, { Timestamp: blockDeploy3.Timestamp, Asset: e.chain.UtilityTokenHash(), @@ -1818,13 +1847,22 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc }, Received: []result.NEP17Transfer{ { - Timestamp: blockGASBounty.Timestamp, + Timestamp: blockGASBounty2.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", + Amount: "50000000", + Index: 12, + NotifyIndex: 0, + TxHash: blockGASBounty2.Hash(), + }, + { + Timestamp: blockGASBounty1.Timestamp, Asset: e.chain.UtilityTokenHash(), Address: "", Amount: "50000000", Index: 6, NotifyIndex: 0, - TxHash: blockGASBounty.Hash(), + TxHash: blockGASBounty1.Hash(), }, { Timestamp: blockReceiveRubles.Timestamp, diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index fd8c3bc97bf90122dbf379112f7bfc257d0b9b19..a5e2a6d209f22720a248f60a56659540f5baf72a 100644 GIT binary patch delta 5356 zcmc(jRaDeboW~i48u-)QGk~;oO85sHLb_uJrMp48yKx8!K}it>0YO3}1VOry7z9Z{ zB^3~mMb>@Wv-`5=?0vWo_wD!je(v|2dzXziJs~8S5OtiVKiO_>qNGl`TSJbRuNq09ozdW60<`f0;^+*S1N+%YRlW;X+dC#zoA? zv?w17fSj8~Lb~~Sp9`9%5NqP_QmNsC*ftbSW&^rE`I*UgeZ|St0M4RQ%yFC-KbK(t z>V~1`q+ZqVcX(gj&=m3>j}QQ2DWu2GGn7xwS?pcjcAFf?NTEtFcGLOlcP!t3l9uWy z8CRMxWpdtX5@7w6TtJUaasGMx^tUzqaaM86y%zNc=uy0^hK~>o^p1YV+EkUyKF;=L z)Eq;R0|s%#k>PsrZI%A9A_cr{U#+Ex4d!g-9%Q$1OO4AZWHUL+^JnKhVa^N{)?;dR zC(e>9Ea+`qM0mPv5)~~ZZ~A!5i0rV$EzaJ1=$HS}ZsMaIGGlLgH&r?E#Y9Bc1B*2G zMPCj1ndPt^)(8}PO(^^!n>*b>$a4TZ^(i9##5r>&*0R0J^u^)edkX}vBWk@kRIlyJ zNfpMd1=2O1XUbN$z_~jph}1PCEuYG6cG(&!AfK*=0pLZb3QE($etz}QbJNqp zAnlr|ix&nyZfhcS{wh%}i%+cd4PN^$Hm1_$8Y?+D%ptmPOfwu+?4pBEdA(>wY>q1~ zKhbCcZFCQQAbOQx0*yzQkCr7cMmNHA8=erZK*6Ed^}YyYMp9ELS<)^#Zk_Wg3btA$ zJxIZg&>*5tySKly#uq=O%1tct)@3>1<#!maI{y$7SZW z`BCBAGRRtb1w8KV=5l|WDoHY+LgM5D$fFm!Q*g zFWtfm!-VBskERq|v8qYc9C%_d385f8sbuoFt7V3~im7 z$T!PfHs^|DGp8-aI}&u%sHROoZP#VcageWN|JdMMvzcRQlWC73nequ?PS7Nd8yugz zv#>rD>=4n{D*M2DlNlqE`A3$vb@g}px_iReh#J|(uO{>Ig^S!fw~Pz4zr6rJln(Fs z{R1iEpOdU#N}o7qd*;kXQsr(qyk22?JpC-PEp*bw%h4DE`z)#c^wc0^VP zP5QMp#-TR#X0#_299I4h0PZya3nd<5o*5^g0a+VY`MAd+Rd4-P{l|h&F`=42-Zt6n z-*;i-s9-#>NNI5p!L95r8sVn-6b_mBREAgPCCw911b}*>F-2Cjg61;&LgZde0TNG> znF5>}*Bt1WZii^*YeY+PNw_tx79PTEu(-Kr|)s z@yM35YUicrmsF{cBa%0w?O&-B2-yu2pYSafDSsW-Kdef0VSJ(g@L9Q*XbB@G?)@NZ z+nUz>W(!W3KJRs15!1Yf!1)>0&7jovob^IWNl-tiEa{#3k{{8%x6MXKHAa1BCDD7d za9G7Z0(jR1oRLR$0SfOQW=Qz8@7$pYKS$06FSE1dh-3fuPVS2g$lCZwG#XD6m$p21 zTE19wAGHx2Eg4)V`s-a0e!0te8UUJq5x4KKXUf%hvaWGzM0VQjY4tFIsiaBFDP+CJ zgS3`@zZVx?baqZ}nX6~h5YJiPK=94Knn2lzRMG7m8Ar4WfW;R3T}I9e0<9f@Z-r8w7cr^5NN!)FtZ`okK^0^a1XP)*P`XvJ#r}7E_dYl0f?ztfI zvic>BQ~7icpZyd)QQ##`ZU6LV6X?Z_9G|w|v$ILUV$WJW$<4lhWI&LORLXg~80}RZ z>+mLsQ7pxQP!IqyCvJUSLAJKzrbBrpFOqwi*4|}lvUv{fp({P>?tMiz;W6`VjZxL= zTO=f>NKkS;A2knw`mdgtmocX^AzAZN0nkI=&*rxdZ79?idwU^y=Ri$!RXUWp6&)Db z{U@9*w&#K5EnND8LNVImR5H~;<(aXq$3z$E1W%WkkF5MQEbjiDy+MQN7B)P;VAoL0 zYw$s8%wlmG&Z6w-1+W&B>^}>%Sw&ja<@U&F4bWMVi;=vcm+0?wCb(;Hj zC#QPs$MUI9Uu1pxsFI!Mb+uMjDf*%pa2A1I+l#MQlO zo84tlKIR-Xw`^VC7f>LSRs#r4Iw6hQ3;@uJ+tM;lmkAxaGdw<>qMz6FRs=`1wAe~% zV3dswbyP$gvb|}vK(Rk_tRSbvfTJ?~&?mR{LoqjZ)d8_DOHE>Lc>ebL}?1ka) zR!#;4Hd>y*SU9`geMo8hHCo)>&{JUj$vJuncX55I72Uz8#7-HAk-~-)C9!mkEM@6=N~Eb}K_RGkTR+&2rVytZ%&s2eGL zj6-fD^uD@_kGgv#RK3W?v*)73W!Ztuw2TJ8ZcP%g`f`gF)&|=?Y5#d6JMYL)IkN&V zQV8lV7whc#X6d;mx!(5KZ&E9G22mtxJ0Y}p^$)jh>Ab=vU%SlV->wV4AbXUrtiweSx9JuTv6|?ml=Gd!L`5 zdGC?}S!@)Zq<2urW#XQ~ARC{s!xP}4MaB9@^xdZ~@#fZ6kDvdOpYU3KhJCUXckani z=|@``F)wOPsfTlUXy2H%79f*gyu||Dw;Y!-hoige6U`kh8?D}yI$mAx1aBC0YE4t% z;OC?Z17NM$u-F|f&IO|lm|KPewu3BN{P{YDC02LlUI>-y{F^L7MT5ymr!tS4Ae=ww zSIz;=>Z@QXwblpyY!MeE&O#3WiXU{iXjaas#a`-X)MH&V6+mYOs7~l@Z1|>3u!mLc zF?w2m}=YQ+RJg{XzqU-x1+cM-O3Efni|Rm40D7(pGw z9Mb7G4nY1+Cj`O^1`NMXzJF#$(nLC#7H5z5y1_HhUf|ut;S#4ZDuN^=s$D(6Iig9? zPs_fm`X3~sYe?~%8Z;%gm*CE8=@VJPI=wDGHtkmK<lm1#!DjO^y)l9Ko=j~Y- z&5q1Q<*!z59flZ10oCl$^?k>DBnJS-1oor}ca*2UKfyZhKYxaK5qw8qBYUuWh-Ud) z^U~R&I%8nkS@j2m=5y}Pmg=%qx+vrR^0oLW-y0Nc2&9aeC;+Z~9jN9XfBUIAMrE}l zkC^-bP>6%+!{%l57O~TFP;EOc+`1py)C_S7QD6T}1Dx!gYzxo{j;@l%D ztqLj`BOmsZBVxKi&)3doI4uC!;woH45t(tYsEQA89jmR z(R#VyapuT=~V*l6RZLwNJX`0TLvXk7?(DeY;`0-&aO zDo1>iRQr?_zI(2E)57d#09Xe!e}!F&*vR@pQx$C^dTu*`<~iiU#YL|uOZ2mvE^TJh zbuc0(fg;EhS62Xxn_aZ0{%raFaXhlV;bsyq>v}p7cyH1jA(69zlCtl_$|+#+)xr`G z-%llage`{~X>V0FJ(+!U`9wk3a%FP%uQ=h2xXKAwX4cq$DH(jWzJ>){?U4UY6bKeS z0JZUOBkyDd@bBB&>Unth0#q)heDU@}$h6-<87fBB!w;dO=|KwCp(s7bb;%<>a@0B4 z*3QPgf9r0wSR8V;vY~8Z z4I4F_gBGqr9d&q1Bl~tk1gTRL8300NxZ-i0nTRD+f5$1ZD9IKCv;D%gVt?yA#x*&) zv*o<&iLYYgp)^$!$vGC3BH{g#q(czHj3b7zd!E2Mi| z=1tl+gQOqCrFE-r2-F_Qw176f{HG)FwT_Y7+Rf$WbnQYCS+C2c6@#I+nj83C3lqX?k9^dKmjL5kJQ4Ir=^toe>0!JpAXB%EsTrs3WM z)Z$jW&{vGZj(W?etkJfeVH0NXLeL(6;^sq9MK3aY>WoJ1+pCt=Udct@Bn1j-v3?=z zSLDpK36jcc{)IiQ%#VK<6r`Ij{Zwz2uj7 zfAM<+9Cizu;6MgsaGcgDX+&Qnd$d$|q?a|VjFDF8*T5yE`zeD&;XJiKlHmkeo& ze;0cWm1Is{4yFC%SBww|=HXUxxjXnFERXEEKLGd|#E<|0 delta 3250 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 From 2bb3ff2affbd6dcd14ac751bac333d7c3eeab611 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 17:05:05 +0300 Subject: [PATCH 5/8] vm: return more detailed error from emit.Array --- pkg/vm/emit/emit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/vm/emit/emit.go b/pkg/vm/emit/emit.go index 00f7c1b44..55949f8cb 100644 --- a/pkg/vm/emit/emit.go +++ b/pkg/vm/emit/emit.go @@ -94,7 +94,7 @@ func Array(w *io.BinWriter, es ...interface{}) { Bool(w, e) default: if es[i] != nil { - w.Err = errors.New("unsupported type") + w.Err = fmt.Errorf("unsupported type: %v", e) return } Opcodes(w, opcode.PUSHNULL) From f95558937065f1217139cf098973612e4cf11b12 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Tue, 23 Mar 2021 22:04:34 +0300 Subject: [PATCH 6/8] rpc: add NEP11 API support to the RPC client --- pkg/rpc/client/helper.go | 20 +++++ pkg/rpc/client/nep.go | 72 +++++++++++++++ pkg/rpc/client/nep11.go | 165 ++++++++++++++++++++++++++++++++++ pkg/rpc/client/nep17.go | 48 +--------- pkg/rpc/server/client_test.go | 56 ++++++++++++ 5 files changed, 317 insertions(+), 44 deletions(-) create mode 100644 pkg/rpc/client/nep.go create mode 100644 pkg/rpc/client/nep11.go diff --git a/pkg/rpc/client/helper.go b/pkg/rpc/client/helper.go index 421f18c52..74f02d8cb 100644 --- a/pkg/rpc/client/helper.go +++ b/pkg/rpc/client/helper.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -75,3 +76,22 @@ func topStringFromStack(st []stackitem.Item) (string, error) { } return string(bs), nil } + +// topUint160FromStack returns the top util.Uint160 from stack. +func topUint160FromStack(st []stackitem.Item) (util.Uint160, error) { + index := len(st) - 1 // top stack element is last in the array + bs, err := st[index].TryBytes() + if err != nil { + return util.Uint160{}, err + } + return util.Uint160DecodeBytesBE(bs) +} + +// topMapFromStack returns the top stackitem.Map from stack. +func topMapFromStack(st []stackitem.Item) (*stackitem.Map, error) { + index := len(st) - 1 // top stack element is last in the array + if t := st[index].Type(); t != stackitem.MapT { + return nil, fmt.Errorf("invalid return stackitem type: %s", t.String()) + } + return st[index].(*stackitem.Map), nil +} diff --git a/pkg/rpc/client/nep.go b/pkg/rpc/client/nep.go new file mode 100644 index 000000000..092669eb1 --- /dev/null +++ b/pkg/rpc/client/nep.go @@ -0,0 +1,72 @@ +package client + +import ( + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// nepDecimals invokes `decimals` NEP* method on a specified contract. +func (c *Client) nepDecimals(tokenHash util.Uint160) (int64, error) { + result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil) + if err != nil { + return 0, err + } + err = getInvocationError(result) + if err != nil { + return 0, err + } + + return topIntFromStack(result.Stack) +} + +// nepSymbol invokes `symbol` NEP* method on a specified contract. +func (c *Client) nepSymbol(tokenHash util.Uint160) (string, error) { + result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil) + if err != nil { + return "", err + } + err = getInvocationError(result) + if err != nil { + return "", err + } + + return topStringFromStack(result.Stack) +} + +// nepTotalSupply invokes `totalSupply` NEP* method on a specified contract. +func (c *Client) nepTotalSupply(tokenHash util.Uint160) (int64, error) { + result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil) + if err != nil { + return 0, err + } + err = getInvocationError(result) + if err != nil { + return 0, err + } + + return topIntFromStack(result.Stack) +} + +// nepBalanceOf invokes `balanceOf` NEP* method on a specified contract. +func (c *Client) nepBalanceOf(tokenHash, acc util.Uint160, tokenID *string) (int64, error) { + params := []smartcontract.Parameter{{ + Type: smartcontract.Hash160Type, + Value: acc, + }} + if tokenID != nil { + params = append(params, smartcontract.Parameter{ + Type: smartcontract.StringType, + Value: *tokenID, + }) + } + result, err := c.InvokeFunction(tokenHash, "balanceOf", params, nil) + if err != nil { + return 0, err + } + err = getInvocationError(result) + if err != nil { + return 0, err + } + + return topIntFromStack(result.Stack) +} diff --git a/pkg/rpc/client/nep11.go b/pkg/rpc/client/nep11.go new file mode 100644 index 000000000..f313815b3 --- /dev/null +++ b/pkg/rpc/client/nep11.go @@ -0,0 +1,165 @@ +package client + +import ( + "fmt" + + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "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/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" +) + +// NEP11Decimals invokes `decimals` NEP11 method on a specified contract. +func (c *Client) NEP11Decimals(tokenHash util.Uint160) (int64, error) { + return c.nepDecimals(tokenHash) +} + +// NEP11Symbol invokes `symbol` NEP11 method on a specified contract. +func (c *Client) NEP11Symbol(tokenHash util.Uint160) (string, error) { + return c.nepSymbol(tokenHash) +} + +// NEP11TotalSupply invokes `totalSupply` NEP11 method on a specified contract. +func (c *Client) NEP11TotalSupply(tokenHash util.Uint160) (int64, error) { + return c.nepTotalSupply(tokenHash) +} + +// NEP11BalanceOf invokes `balanceOf` NEP11 method on a specified contract. +func (c *Client) NEP11BalanceOf(tokenHash, owner util.Uint160) (int64, error) { + return c.nepBalanceOf(tokenHash, owner, nil) +} + +// TransferNEP11 creates an invocation transaction that invokes 'transfer' method +// on a given token to move the whole NEP11 token with the specified token ID to +// given account and sends it to the network returning just a hash of it. +func (c *Client) TransferNEP11(acc *wallet.Account, to util.Uint160, + tokenHash util.Uint160, tokenID string, gas int64) (util.Uint256, error) { + if !c.initDone { + return util.Uint256{}, errNetworkNotInitialized + } + tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, to, tokenID) + if err != nil { + return util.Uint256{}, err + } + + if err := acc.SignTx(c.GetNetwork(), tx); err != nil { + return util.Uint256{}, fmt.Errorf("can't sign NEP11 transfer tx: %w", err) + } + + return c.SendRawTransaction(tx) +} + +// createNEP11TransferTx is an internal helper for TransferNEP11 and +// TransferNEP11D which creates an invocation transaction for the +// 'transfer' method of a given contract (token) to move the whole (or the +// specified amount of) NEP11 token with the specified token ID to given account +// and returns it. The returned transaction is not signed. +// `args` for TransferNEP11: to util.Uint160, tokenID string; +// `args` for TransferNEP11D: from, to util.Uint160, amount int64, tokenID string. +func (c *Client) createNEP11TransferTx(acc *wallet.Account, tokenHash util.Uint160, + gas int64, args ...interface{}) (*transaction.Transaction, error) { + w := io.NewBufBinWriter() + emit.AppCall(w.BinWriter, tokenHash, "transfer", callflag.All, args...) + emit.Opcodes(w.BinWriter, opcode.ASSERT) + if w.Err != nil { + return nil, fmt.Errorf("failed to create NEP11 transfer script: %w", w.Err) + } + from, err := address.StringToUint160(acc.Address) + if err != nil { + return nil, fmt.Errorf("bad account address: %w", err) + } + return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, []SignerAccount{{ + Signer: transaction.Signer{ + Account: from, + Scopes: transaction.CalledByEntry, + }, + Account: acc, + }}) +} + +// Non-divisible NFT methods section start. + +// NEP11NDOwnerOf invokes `ownerOf` non-devisible NEP11 method with the +// specified token ID on a specified contract. +func (c *Client) NEP11NDOwnerOf(tokenHash util.Uint160, tokenID string) (util.Uint160, error) { + result, err := c.InvokeFunction(tokenHash, "ownerOf", []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: tokenID, + }, + }, nil) + if err != nil { + return util.Uint160{}, err + } + err = getInvocationError(result) + if err != nil { + return util.Uint160{}, err + } + + return topUint160FromStack(result.Stack) +} + +// Non-divisible NFT methods section end. + +// Divisible NFT methods section start. + +// TransferNEP11D creates an invocation transaction that invokes 'transfer' +// method on a given token to move specified amount of divisible NEP11 assets +// (in FixedN format using contract's number of decimals) to given account and +// sends it to the network returning just a hash of it. +func (c *Client) TransferNEP11D(acc *wallet.Account, to util.Uint160, + tokenHash util.Uint160, amount int64, tokenID string, gas int64) (util.Uint256, error) { + if !c.initDone { + return util.Uint256{}, errNetworkNotInitialized + } + from, err := address.StringToUint160(acc.Address) + if err != nil { + return util.Uint256{}, fmt.Errorf("bad account address: %w", err) + } + tx, err := c.createNEP11TransferTx(acc, tokenHash, gas, acc.Address, from, to, amount, tokenID) + if err != nil { + return util.Uint256{}, err + } + + if err := acc.SignTx(c.GetNetwork(), tx); err != nil { + return util.Uint256{}, fmt.Errorf("can't sign NEP11 divisible transfer tx: %w", err) + } + + return c.SendRawTransaction(tx) +} + +// NEP11DBalanceOf invokes `balanceOf` divisible NEP11 method on a +// specified contract. +func (c *Client) NEP11DBalanceOf(tokenHash, owner util.Uint160, tokenID string) (int64, error) { + return c.nepBalanceOf(tokenHash, owner, &tokenID) +} + +// Divisible NFT methods section end. + +// Optional NFT methods section start. + +// NEP11Properties invokes `properties` optional NEP11 method on a +// specified contract. +func (c *Client) NEP11Properties(tokenHash util.Uint160, tokenID string) (*stackitem.Map, error) { + result, err := c.InvokeFunction(tokenHash, "properties", []smartcontract.Parameter{{ + Type: smartcontract.StringType, + Value: tokenID, + }}, nil) + if err != nil { + return nil, err + } + err = getInvocationError(result) + if err != nil { + return nil, err + } + + return topMapFromStack(result.Stack) +} + +// Optional NFT methods section end. diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index 7aba4cdab..619fabf83 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -6,7 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -30,61 +29,22 @@ type SignerAccount struct { // NEP17Decimals invokes `decimals` NEP17 method on a specified contract. func (c *Client) NEP17Decimals(tokenHash util.Uint160) (int64, error) { - result, err := c.InvokeFunction(tokenHash, "decimals", []smartcontract.Parameter{}, nil) - if err != nil { - return 0, err - } - err = getInvocationError(result) - if err != nil { - return 0, fmt.Errorf("failed to get NEP17 decimals: %w", err) - } - - return topIntFromStack(result.Stack) + return c.nepDecimals(tokenHash) } // NEP17Symbol invokes `symbol` NEP17 method on a specified contract. func (c *Client) NEP17Symbol(tokenHash util.Uint160) (string, error) { - result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil) - if err != nil { - return "", err - } - err = getInvocationError(result) - if err != nil { - return "", fmt.Errorf("failed to get NEP17 symbol: %w", err) - } - - return topStringFromStack(result.Stack) + return c.nepSymbol(tokenHash) } // NEP17TotalSupply invokes `totalSupply` NEP17 method on a specified contract. func (c *Client) NEP17TotalSupply(tokenHash util.Uint160) (int64, error) { - result, err := c.InvokeFunction(tokenHash, "totalSupply", []smartcontract.Parameter{}, nil) - if err != nil { - return 0, err - } - err = getInvocationError(result) - if err != nil { - return 0, fmt.Errorf("failed to get NEP17 total supply: %w", err) - } - - return topIntFromStack(result.Stack) + return c.nepTotalSupply(tokenHash) } // NEP17BalanceOf invokes `balanceOf` NEP17 method on a specified contract. func (c *Client) NEP17BalanceOf(tokenHash, acc util.Uint160) (int64, error) { - result, err := c.InvokeFunction(tokenHash, "balanceOf", []smartcontract.Parameter{{ - Type: smartcontract.Hash160Type, - Value: acc, - }}, nil) - if err != nil { - return 0, err - } - err = getInvocationError(result) - if err != nil { - return 0, fmt.Errorf("failed to get NEP17 balance: %w", err) - } - - return topIntFromStack(result.Stack) + return c.nepBalanceOf(tokenHash, acc, nil) } // NEP17TokenInfo returns full NEP17 token info. diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 41305f332..ebc323554 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -19,6 +19,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "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" "github.com/stretchr/testify/require" ) @@ -476,3 +477,58 @@ func TestClient_GetNativeContracts(t *testing.T) { require.NoError(t, err) require.Equal(t, chain.GetNatives(), cs) } + +func TestClient_NEP11(t *testing.T) { + chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) + defer chain.Close() + defer rpcSrv.Shutdown() + + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) + require.NoError(t, err) + require.NoError(t, c.Init()) + + h, err := chain.GetNativeContractScriptHash(nativenames.NameService) + require.NoError(t, err) + acc := testchain.PrivateKeyByID(0).GetScriptHash() + + t.Run("Decimals", func(t *testing.T) { + d, err := c.NEP11Decimals(h) + require.NoError(t, err) + require.EqualValues(t, 0, d) // non-divisible + }) + t.Run("TotalSupply", func(t *testing.T) { + s, err := c.NEP11TotalSupply(h) + require.NoError(t, err) + require.EqualValues(t, 1, s) // the only `neo.com` of acc0 + }) + t.Run("Symbol", func(t *testing.T) { + sym, err := c.NEP11Symbol(h) + require.NoError(t, err) + require.Equal(t, "NNS", sym) + }) + t.Run("BalanceOf", func(t *testing.T) { + b, err := c.NEP11BalanceOf(h, acc) + require.NoError(t, err) + require.EqualValues(t, 1, b) + }) + t.Run("OwnerOf", func(t *testing.T) { + b, err := c.NEP11NDOwnerOf(h, "neo.com") + require.NoError(t, err) + require.EqualValues(t, acc, b) + }) + t.Run("Properties", func(t *testing.T) { + p, err := c.NEP11Properties(h, "neo.com") + require.NoError(t, err) + blockRegisterDomain, err := chain.GetBlock(chain.GetHeaderHash(13)) // `neo.com` domain was registered in 13th block + require.NoError(t, err) + require.Equal(t, 1, len(blockRegisterDomain.Transactions)) + expected := stackitem.NewMap() + expected.Add(stackitem.Make([]byte("name")), stackitem.Make([]byte("neo.com"))) + expected.Add(stackitem.Make([]byte("expiration")), stackitem.Make(blockRegisterDomain.Timestamp/1000+365*24*3600)) // expiration formula + require.EqualValues(t, expected, p) + }) + t.Run("Transfer", func(t *testing.T) { + _, err := c.TransferNEP11(wallet.NewAccountFromPrivateKey(testchain.PrivateKeyByID(0)), testchain.PrivateKeyByID(1).GetScriptHash(), h, "neo.com", 0) + require.NoError(t, err) + }) +} From 626ec8a82b1abf7ef2f035b1063d757bf69fd8e5 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 13:12:33 +0300 Subject: [PATCH 7/8] rpc: add `resolve` and `isAvailable` NNS API to RPC client --- pkg/rpc/client/native.go | 53 +++++++++++++++++++++++++++++++++++ pkg/rpc/server/client_test.go | 35 +++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/pkg/rpc/client/native.go b/pkg/rpc/client/native.go index 770139790..8794af862 100644 --- a/pkg/rpc/client/native.go +++ b/pkg/rpc/client/native.go @@ -3,9 +3,11 @@ package client // Various non-policy things from native contracts. import ( + "errors" "fmt" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -63,3 +65,54 @@ func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.Pu } return topPublicKeysFromStack(result.Stack) } + +// NNSResolve invokes `resolve` method on a native NameService contract. +func (c *Client) NNSResolve(name string, typ nnsrecords.Type) (string, error) { + if typ == nnsrecords.CNAME { + return "", errors.New("can't resolve CNAME record type") + } + rmHash, err := c.GetNativeContractHash(nativenames.NameService) + if err != nil { + return "", fmt.Errorf("failed to get native NameService hash: %w", err) + } + result, err := c.InvokeFunction(rmHash, "resolve", []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: name, + }, + { + Type: smartcontract.IntegerType, + Value: int64(typ), + }, + }, nil) + if err != nil { + return "", err + } + err = getInvocationError(result) + if err != nil { + return "", fmt.Errorf("`resolve`: %w", err) + } + return topStringFromStack(result.Stack) +} + +// NNSIsAvailable invokes `isAvailable` method on a native NameService contract. +func (c *Client) NNSIsAvailable(name string) (bool, error) { + rmHash, err := c.GetNativeContractHash(nativenames.NameService) + if err != nil { + return false, fmt.Errorf("failed to get native NameService hash: %w", err) + } + result, err := c.InvokeFunction(rmHash, "isAvailable", []smartcontract.Parameter{ + { + Type: smartcontract.StringType, + Value: name, + }, + }, nil) + if err != nil { + return false, err + } + err = getInvocationError(result) + if err != nil { + return false, fmt.Errorf("`isAvailable`: %w", err) + } + return topBoolFromStack(result.Stack) +} diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index ebc323554..51c3fd6fe 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/internal/testchain" "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/nnsrecords" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -532,3 +533,37 @@ func TestClient_NEP11(t *testing.T) { require.NoError(t, err) }) } + +func TestClient_NNS(t *testing.T) { + chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) + defer chain.Close() + defer rpcSrv.Shutdown() + + c, err := client.New(context.Background(), httpSrv.URL, client.Options{}) + require.NoError(t, err) + require.NoError(t, c.Init()) + + t.Run("NNSIsAvailable, false", func(t *testing.T) { + b, err := c.NNSIsAvailable("neo.com") + require.NoError(t, err) + require.Equal(t, false, b) + }) + t.Run("NNSIsAvailable, true", func(t *testing.T) { + b, err := c.NNSIsAvailable("neogo.com") + require.NoError(t, err) + require.Equal(t, true, b) + }) + t.Run("NNSResolve, good", func(t *testing.T) { + b, err := c.NNSResolve("neo.com", nnsrecords.A) + require.NoError(t, err) + require.Equal(t, "1.2.3.4", b) + }) + t.Run("NNSResolve, bad", func(t *testing.T) { + _, err := c.NNSResolve("neogo.com", nnsrecords.A) + require.Error(t, err) + }) + t.Run("NNSResolve, forbidden", func(t *testing.T) { + _, err := c.NNSResolve("neogo.com", nnsrecords.CNAME) + require.Error(t, err) + }) +} From ffc2ad3cc345a64be398d3fedad5f9c9b984f44a Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 24 Mar 2021 13:12:33 +0300 Subject: [PATCH 8/8] rpc: check RPC client is initialised where appropriate We don't need magic to create tx anymore, but we need it to sign tx. --- pkg/rpc/client/nep17.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pkg/rpc/client/nep17.go b/pkg/rpc/client/nep17.go index 619fabf83..51d7b5ed7 100644 --- a/pkg/rpc/client/nep17.go +++ b/pkg/rpc/client/nep17.go @@ -129,9 +129,6 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, sysFee = result.GasConsumed } - if !c.initDone { - return nil, errNetworkNotInitialized - } tx := transaction.New(script, sysFee) tx.Signers = signers @@ -153,6 +150,10 @@ func (c *Client) CreateTxFromScript(script []byte, acc *wallet.Account, sysFee, // using contract's number of decimals) to given account with data specified and // sends it to the network returning just a hash of it. func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util.Uint160, amount int64, gas int64, data interface{}) (util.Uint256, error) { + if !c.initDone { + return util.Uint256{}, errNetworkNotInitialized + } + tx, err := c.CreateNEP17TransferTx(acc, to, token, amount, gas, data) if err != nil { return util.Uint256{}, err @@ -167,6 +168,10 @@ func (c *Client) TransferNEP17(acc *wallet.Account, to util.Uint160, token util. // MultiTransferNEP17 is similar to TransferNEP17, buf allows to have multiple recipients. func (c *Client) MultiTransferNEP17(acc *wallet.Account, gas int64, recipients []TransferTarget, data []interface{}) (util.Uint256, error) { + if !c.initDone { + return util.Uint256{}, errNetworkNotInitialized + } + tx, err := c.CreateNEP17MultiTransferTx(acc, gas, recipients, data) if err != nil { return util.Uint256{}, err