[#746] morph: Implement and use multi-client

There is a need to work with a set of Neo RPC nodes in order not to depend
on the failure of some nodes while others are active.

Support "multi-client" mode of morph `Client` entity. If instance is not
"multi-client", it works as before. Constructor `New` creates multi-client,
and each method performs iterating over the fixed set of endpoints until
success. Opened client connections are cached (without eviction for now).

Storage (as earlier) and IR (from now) nodes can be configured with multiple
Neo endpoints. As above, `New` creates multi-client instance, so we don't
need initialization changes on app-side.

`Wait` and `GetDesignateHash` methods of `Client` return an error from now
to detect connection errors. `NotaryEnabled` method is removed as unused.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2021-08-26 10:59:02 +03:00 committed by Alex Vanin
parent f9218bf84f
commit ad7ad12a0c
7 changed files with 345 additions and 89 deletions

View file

@ -75,16 +75,25 @@ func (c *Client) EnableNotarySupport(opts ...NotaryOption) error {
opt(cfg)
}
notaryContract, err := c.client.GetNativeContractHash(nativenames.Notary)
if err != nil {
return fmt.Errorf("can't get notary contract script hash: %w", err)
}
if cfg.proxy.Equals(util.Uint160{}) {
return errors.New("proxy contract hash is missing")
}
c.notary = &notary{
var (
notaryContract util.Uint160
err error
)
if err = c.iterateClients(func(c *Client) error {
notaryContract, err = c.client.GetNativeContractHash(nativenames.Notary)
return err
}); err != nil {
return fmt.Errorf("can't get notary contract script hash: %w", err)
}
c.clientsMtx.Lock()
c.sharedNotary = &notary{
notary: notaryContract,
proxy: cfg.proxy,
txValidTime: cfg.txValidTime,
@ -93,17 +102,27 @@ func (c *Client) EnableNotarySupport(opts ...NotaryOption) error {
alphabetSource: cfg.alphabetSource,
}
// update client cache
for _, cached := range c.clients {
cached.notary = c.sharedNotary
}
c.clientsMtx.Unlock()
return nil
}
// NotaryEnabled returns true if notary support was enabled in this instance
// of client by providing notary options on client creation. Otherwise returns false.
func (c *Client) NotaryEnabled() bool {
return c.notary != nil
}
// ProbeNotary checks if native `Notary` contract is presented on chain.
func (c *Client) ProbeNotary() bool {
func (c *Client) ProbeNotary() (res bool) {
if c.multiClient != nil {
_ = c.multiClient.iterateClients(func(c *Client) error {
res = c.ProbeNotary()
return nil
})
return
}
_, err := c.client.GetNativeContractHash(nativenames.Notary)
return err == nil
}
@ -115,7 +134,14 @@ func (c *Client) ProbeNotary() bool {
// use this function.
//
// This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) DepositNotary(amount fixedn.Fixed8, delta uint32) (util.Uint256, error) {
func (c *Client) DepositNotary(amount fixedn.Fixed8, delta uint32) (res util.Uint256, err error) {
if c.multiClient != nil {
return res, c.multiClient.iterateClients(func(c *Client) error {
res, err = c.DepositNotary(amount, delta)
return err
})
}
if c.notary == nil {
panic(notaryNotEnabledPanicMsg)
}
@ -150,7 +176,14 @@ func (c *Client) DepositNotary(amount fixedn.Fixed8, delta uint32) (util.Uint256
// Notary support should be enabled in client to use this function.
//
// This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) GetNotaryDeposit() (int64, error) {
func (c *Client) GetNotaryDeposit() (res int64, err error) {
if c.multiClient != nil {
return res, c.multiClient.iterateClients(func(c *Client) error {
res, err = c.GetNotaryDeposit()
return err
})
}
if c.notary == nil {
panic(notaryNotEnabledPanicMsg)
}
@ -179,11 +212,17 @@ func (c *Client) GetNotaryDeposit() (int64, error) {
//
// This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) UpdateNotaryList(list keys.PublicKeys) error {
if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error {
return c.UpdateNotaryList(list)
})
}
if c.notary == nil {
panic(notaryNotEnabledPanicMsg)
}
return c.notaryInvokeAsCommittee(c.designate,
return c.notaryInvokeAsCommittee(
setDesignateMethod,
noderoles.P2PNotary,
list,
@ -196,11 +235,17 @@ func (c *Client) UpdateNotaryList(list keys.PublicKeys) error {
//
// This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) UpdateNeoFSAlphabetList(list keys.PublicKeys) error {
if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error {
return c.UpdateNeoFSAlphabetList(list)
})
}
if c.notary == nil {
panic(notaryNotEnabledPanicMsg)
}
return c.notaryInvokeAsCommittee(c.designate,
return c.notaryInvokeAsCommittee(
setDesignateMethod,
noderoles.NeoFSAlphabet,
list,
@ -213,6 +258,12 @@ func (c *Client) UpdateNeoFSAlphabetList(list keys.PublicKeys) error {
//
// This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, method string, args ...interface{}) error {
if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error {
return c.NotaryInvoke(contract, fee, method, args...)
})
}
if c.notary == nil {
return c.Invoke(contract, fee, method, args...)
}
@ -220,8 +271,13 @@ func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, method s
return c.notaryInvoke(false, contract, method, args...)
}
func (c *Client) notaryInvokeAsCommittee(contract util.Uint160, method string, args ...interface{}) error {
return c.notaryInvoke(true, contract, method, args...)
func (c *Client) notaryInvokeAsCommittee(method string, args ...interface{}) error {
designate, err := c.GetDesignateHash()
if err != nil {
return err
}
return c.notaryInvoke(true, designate, method, args...)
}
func (c *Client) notaryInvoke(committee bool, contract util.Uint160, method string, args ...interface{}) error {