rpcclient: add rolemgmt pkg for RoleManagement contract
And test it with RPC server.
This commit is contained in:
parent
a1a5db8fcd
commit
ee84a4ab32
4 changed files with 326 additions and 0 deletions
|
@ -52,6 +52,8 @@ func (c *Client) getFromNEO(meth string) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDesignatedByRole invokes `getDesignatedByRole` method on a native RoleManagement contract.
|
// GetDesignatedByRole invokes `getDesignatedByRole` method on a native RoleManagement contract.
|
||||||
|
//
|
||||||
|
// Deprecated: please use rolemgmt package.
|
||||||
func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.PublicKeys, error) {
|
func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.PublicKeys, error) {
|
||||||
rmHash, err := c.GetNativeContractHash(nativenames.Designation)
|
rmHash, err := c.GetNativeContractHash(nativenames.Designation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
119
pkg/rpcclient/rolemgmt/roles.go
Normal file
119
pkg/rpcclient/rolemgmt/roles.go
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
Package rolemgmt allows to work with the native RoleManagement contract via RPC.
|
||||||
|
|
||||||
|
Safe methods are encapsulated into ContractReader structure while Contract provides
|
||||||
|
various methods to perform the only RoleManagement state-changing call.
|
||||||
|
*/
|
||||||
|
package rolemgmt
|
||||||
|
|
||||||
|
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/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various methods.
|
||||||
|
type Invoker interface {
|
||||||
|
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor is used by Contract to create and send transactions.
|
||||||
|
type Actor interface {
|
||||||
|
Invoker
|
||||||
|
|
||||||
|
MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error)
|
||||||
|
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error)
|
||||||
|
SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash stores the hash of the native RoleManagement contract.
|
||||||
|
var Hash = state.CreateNativeContractHash(nativenames.Designation)
|
||||||
|
|
||||||
|
const designateMethod = "designateAsRole"
|
||||||
|
|
||||||
|
// ContractReader provides an interface to call read-only RoleManagement
|
||||||
|
// contract's methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
invoker Invoker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract represents a RoleManagement contract client that can be used to
|
||||||
|
// invoke all of its methods.
|
||||||
|
type Contract struct {
|
||||||
|
ContractReader
|
||||||
|
|
||||||
|
actor Actor
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesignationEvent represents an event emitted by RoleManagement contract when
|
||||||
|
// a new role designation is done.
|
||||||
|
type DesignationEvent struct {
|
||||||
|
Role noderoles.Role
|
||||||
|
BlockIndex uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader creates an instance of ContractReader that can be used to read
|
||||||
|
// data from the contract.
|
||||||
|
func NewReader(invoker Invoker) *ContractReader {
|
||||||
|
return &ContractReader{invoker}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates an instance of Contract to perform actions using
|
||||||
|
// the given Actor. Notice that RoleManagement's state can be changed
|
||||||
|
// only by the network's committee, so the Actor provided must be a committee
|
||||||
|
// actor for designation methods to work properly.
|
||||||
|
func New(actor Actor) *Contract {
|
||||||
|
return &Contract{*NewReader(actor), actor}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDesignatedByRole returns the list of the keys designated to serve for the
|
||||||
|
// given role at the given height. The list can be empty if no keys are
|
||||||
|
// configured for this role/height.
|
||||||
|
func (c *ContractReader) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.PublicKeys, error) {
|
||||||
|
arr, err := unwrap.Array(c.invoker.Call(Hash, "getDesignatedByRole", int64(role), index))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pks := make(keys.PublicKeys, len(arr))
|
||||||
|
for i, item := range arr {
|
||||||
|
val, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesignateAsRole creates and sends a transaction that sets the keys used for
|
||||||
|
// the given node role. The action is successful when transaction ends in HALT
|
||||||
|
// state. The returned values are transaction hash, its ValidUntilBlock value
|
||||||
|
// and an error if any.
|
||||||
|
func (c *Contract) DesignateAsRole(role noderoles.Role, pubs keys.PublicKeys) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(Hash, designateMethod, int(role), pubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesignateAsRoleTransaction creates a transaction that sets the keys for the
|
||||||
|
// given node role. This transaction is signed, but not sent to the network,
|
||||||
|
// instead it's returned to the caller.
|
||||||
|
func (c *Contract) DesignateAsRoleTransaction(role noderoles.Role, pubs keys.PublicKeys) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(Hash, designateMethod, int(role), pubs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DesignateAsRoleUnsigned creates a transaction that sets the keys for the
|
||||||
|
// given node role. This transaction is not signed and just returned to the
|
||||||
|
// caller.
|
||||||
|
func (c *Contract) DesignateAsRoleUnsigned(role noderoles.Role, pubs keys.PublicKeys) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(Hash, designateMethod, nil, int(role), pubs)
|
||||||
|
}
|
151
pkg/rpcclient/rolemgmt/roles_test.go
Normal file
151
pkg/rpcclient/rolemgmt/roles_test.go
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
package rolemgmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testAct struct {
|
||||||
|
err error
|
||||||
|
res *result.Invoke
|
||||||
|
tx *transaction.Transaction
|
||||||
|
txh util.Uint256
|
||||||
|
vub uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||||
|
return t.res, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) {
|
||||||
|
return t.txh, t.vub, t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderGetDesignatedByRole(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
rc := NewReader(ta)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(100500),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Null{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nodes, err := rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, nodes)
|
||||||
|
require.Equal(t, 0, len(nodes))
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{stackitem.Null{}}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{stackitem.Make(42)}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
k, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{stackitem.Make(k.PublicKey().Bytes())}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nodes, err = rc.GetDesignatedByRole(noderoles.Oracle, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, nodes)
|
||||||
|
require.Equal(t, 1, len(nodes))
|
||||||
|
require.Equal(t, k.PublicKey(), nodes[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignateAsRole(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
rc := New(ta)
|
||||||
|
|
||||||
|
k, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ks := keys.PublicKeys{k.PublicKey()}
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, _, err = rc.DesignateAsRole(noderoles.Oracle, ks)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{1, 2, 3}
|
||||||
|
ta.vub = 42
|
||||||
|
h, vub, err := rc.DesignateAsRole(noderoles.Oracle, ks)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, h)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDesignateAsRoleTransaction(t *testing.T) {
|
||||||
|
ta := new(testAct)
|
||||||
|
rc := New(ta)
|
||||||
|
|
||||||
|
k, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ks := keys.PublicKeys{k.PublicKey()}
|
||||||
|
|
||||||
|
for _, fun := range []func(r noderoles.Role, pubs keys.PublicKeys) (*transaction.Transaction, error){
|
||||||
|
rc.DesignateAsRoleTransaction,
|
||||||
|
rc.DesignateAsRoleUnsigned,
|
||||||
|
} {
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := fun(noderoles.P2PNotary, ks)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := fun(noderoles.P2PNotary, ks)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/fee"
|
"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/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"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/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -33,6 +34,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
@ -90,6 +92,58 @@ func TestClient_NEP17(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientRoleManagement(t *testing.T) {
|
||||||
|
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||||
|
defer chain.Close()
|
||||||
|
defer rpcSrv.Shutdown()
|
||||||
|
|
||||||
|
c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, c.Init())
|
||||||
|
|
||||||
|
act, err := actor.New(c, []actor.SignerAccount{{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: testchain.CommitteeScriptHash(),
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
},
|
||||||
|
Account: &wallet.Account{
|
||||||
|
Address: testchain.CommitteeAddress(),
|
||||||
|
Contract: &wallet.Contract{
|
||||||
|
Script: testchain.CommitteeVerificationScript(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
height, err := c.GetBlockCount()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rm := rolemgmt.New(act)
|
||||||
|
ks, err := rm.GetDesignatedByRole(noderoles.Oracle, height)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, len(ks))
|
||||||
|
|
||||||
|
testKeys := keys.PublicKeys{
|
||||||
|
testchain.PrivateKeyByID(0).PublicKey(),
|
||||||
|
testchain.PrivateKeyByID(1).PublicKey(),
|
||||||
|
testchain.PrivateKeyByID(2).PublicKey(),
|
||||||
|
testchain.PrivateKeyByID(3).PublicKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := rm.DesignateAsRoleUnsigned(noderoles.Oracle, testKeys)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx.Scripts[0].InvocationScript = testchain.SignCommittee(tx)
|
||||||
|
bl := testchain.NewBlock(t, chain, 1, 0, tx)
|
||||||
|
_, err = c.SubmitBlock(*bl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sort.Sort(testKeys)
|
||||||
|
ks, err = rm.GetDesignatedByRole(noderoles.Oracle, height+1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, testKeys, ks)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
|
func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
|
||||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||||
defer chain.Close()
|
defer chain.Close()
|
||||||
|
|
Loading…
Reference in a new issue