[#971] morph/client: Group Invoke and TestInvoke params

Also:
- add `validUntilBlock` and nonce calculation;
- pass notification hashes to notary calls.

Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
This commit is contained in:
Pavel Karpy 2021-11-09 17:52:48 +03:00 committed by Alex Vanin
parent 8e1f187822
commit 3849d13e0b
2 changed files with 176 additions and 29 deletions

View file

@ -1,6 +1,7 @@
package client package client
import ( import (
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"strings" "strings"
@ -243,14 +244,31 @@ func (c *Client) GetNotaryDeposit() (res int64, err error) {
return bigIntDeposit.Int64(), nil return bigIntDeposit.Int64(), nil
} }
// UpdateNotaryListPrm groups parameters of UpdateNotaryList operation.
type UpdateNotaryListPrm struct {
list keys.PublicKeys
hash util.Uint256
}
// SetList sets list of the new notary role keys.
func (u *UpdateNotaryListPrm) SetList(list keys.PublicKeys) {
u.list = list
}
// SetHash sets hash of the transaction that led to the update
// of the notary role in the designate contract.
func (u *UpdateNotaryListPrm) SetHash(hash util.Uint256) {
u.hash = hash
}
// UpdateNotaryList updates list of notary nodes in designate contract. Requires // UpdateNotaryList updates list of notary nodes in designate contract. Requires
// committee multi signature. // committee multi signature.
// //
// This function must be invoked with notary enabled otherwise it throws panic. // This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) UpdateNotaryList(list keys.PublicKeys) error { func (c *Client) UpdateNotaryList(prm UpdateNotaryListPrm) error {
if c.multiClient != nil { if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error { return c.multiClient.iterateClients(func(c *Client) error {
return c.UpdateNotaryList(list) return c.UpdateNotaryList(prm)
}) })
} }
@ -258,23 +276,46 @@ func (c *Client) UpdateNotaryList(list keys.PublicKeys) error {
panic(notaryNotEnabledPanicMsg) panic(notaryNotEnabledPanicMsg)
} }
nonce, vub, err := c.CalculateNonceAndVUB(prm.hash)
if err != nil {
return fmt.Errorf("could not calculate nonce and `valicUntilBlock` values: %w", err)
}
return c.notaryInvokeAsCommittee( return c.notaryInvokeAsCommittee(
setDesignateMethod, setDesignateMethod,
1, // FIXME: do not use constant nonce for alphabet NR: #844 nonce,
vub,
noderoles.P2PNotary, noderoles.P2PNotary,
list, prm.list,
) )
} }
// UpdateAlphabetListPrm groups parameters of UpdateNeoFSAlphabetList operation.
type UpdateAlphabetListPrm struct {
list keys.PublicKeys
hash util.Uint256
}
// SetList sets list of the new alphabet role keys.
func (u *UpdateAlphabetListPrm) SetList(list keys.PublicKeys) {
u.list = list
}
// SetHash sets hash of the transaction that led to the update
// of the alphabet role in the designate contract.
func (u *UpdateAlphabetListPrm) SetHash(hash util.Uint256) {
u.hash = hash
}
// UpdateNeoFSAlphabetList updates list of alphabet nodes in designate contract. // UpdateNeoFSAlphabetList updates list of alphabet nodes in designate contract.
// As for side chain list should contain all inner ring nodes. // As for side chain list should contain all inner ring nodes.
// Requires committee multi signature. // Requires committee multi signature.
// //
// This function must be invoked with notary enabled otherwise it throws panic. // This function must be invoked with notary enabled otherwise it throws panic.
func (c *Client) UpdateNeoFSAlphabetList(list keys.PublicKeys) error { func (c *Client) UpdateNeoFSAlphabetList(prm UpdateAlphabetListPrm) error {
if c.multiClient != nil { if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error { return c.multiClient.iterateClients(func(c *Client) error {
return c.UpdateNeoFSAlphabetList(list) return c.UpdateNeoFSAlphabetList(prm)
}) })
} }
@ -282,11 +323,17 @@ func (c *Client) UpdateNeoFSAlphabetList(list keys.PublicKeys) error {
panic(notaryNotEnabledPanicMsg) panic(notaryNotEnabledPanicMsg)
} }
nonce, vub, err := c.CalculateNonceAndVUB(prm.hash)
if err != nil {
return fmt.Errorf("could not calculate nonce and `valicUntilBlock` values: %w", err)
}
return c.notaryInvokeAsCommittee( return c.notaryInvokeAsCommittee(
setDesignateMethod, setDesignateMethod,
1, // FIXME: do not use constant nonce for alphabet NR: #844 nonce,
vub,
noderoles.NeoFSAlphabet, noderoles.NeoFSAlphabet,
list, prm.list,
) )
} }
@ -294,11 +341,11 @@ func (c *Client) UpdateNeoFSAlphabetList(list keys.PublicKeys) error {
// blockchain. Fallback tx is a `RET`. If Notary support is not enabled // blockchain. Fallback tx is a `RET`. If Notary support is not enabled
// it fallbacks to a simple `Invoke()`. // it fallbacks to a simple `Invoke()`.
// //
// This function must be invoked with notary enabled otherwise it throws panic. // `nonce` and `vub` are used only if notary is enabled.
func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, method string, args ...interface{}) error { func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...interface{}) error {
if c.multiClient != nil { if c.multiClient != nil {
return c.multiClient.iterateClients(func(c *Client) error { return c.multiClient.iterateClients(func(c *Client) error {
return c.NotaryInvoke(contract, fee, nonce, method, args...) return c.NotaryInvoke(contract, fee, nonce, vub, method, args...)
}) })
} }
@ -306,7 +353,7 @@ func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce ui
return c.Invoke(contract, fee, method, args...) return c.Invoke(contract, fee, method, args...)
} }
return c.notaryInvoke(false, true, contract, nonce, method, args...) return c.notaryInvoke(false, true, contract, nonce, vub, method, args...)
} }
// randSource is a source of random numbers. // randSource is a source of random numbers.
@ -328,7 +375,7 @@ func (c *Client) NotaryInvokeNotAlpha(contract util.Uint160, fee fixedn.Fixed8,
return c.Invoke(contract, fee, method, args...) return c.Invoke(contract, fee, method, args...)
} }
return c.notaryInvoke(false, false, contract, randSource.Uint32(), method, args...) return c.notaryInvoke(false, false, contract, randSource.Uint32(), nil, method, args...)
} }
// NotarySignAndInvokeTX signs and sends notary request that was received from // NotarySignAndInvokeTX signs and sends notary request that was received from
@ -376,16 +423,16 @@ func (c *Client) NotarySignAndInvokeTX(mainTx *transaction.Transaction) error {
return nil return nil
} }
func (c *Client) notaryInvokeAsCommittee(method string, nonce uint32, args ...interface{}) error { func (c *Client) notaryInvokeAsCommittee(method string, nonce, vub uint32, args ...interface{}) error {
designate, err := c.GetDesignateHash() designate, err := c.GetDesignateHash()
if err != nil { if err != nil {
return err return err
} }
return c.notaryInvoke(true, true, designate, nonce, method, args...) return c.notaryInvoke(true, true, designate, nonce, &vub, method, args...)
} }
func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint160, nonce uint32, method string, args ...interface{}) error { func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint160, nonce uint32, vub *uint32, method string, args ...interface{}) error {
alphabetList, err := c.notary.alphabetSource() // prepare arguments for test invocation alphabetList, err := c.notary.alphabetSource() // prepare arguments for test invocation
if err != nil { if err != nil {
return err return err
@ -427,10 +474,16 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
return err return err
} }
until, err := c.notaryTxValidationLimit() var until uint32
if vub != nil {
until = *vub
} else {
until, err = c.notaryTxValidationLimit()
if err != nil { if err != nil {
return err return err
} }
}
// prepare main tx // prepare main tx
mainTx := &transaction.Transaction{ mainTx := &transaction.Transaction{
@ -774,3 +827,24 @@ func CalculateNotaryDepositAmount(c *Client, gasMul, gasDiv int64) (fixedn.Fixed
return fixedn.Fixed8(depositAmount), nil return fixedn.Fixed8(depositAmount), nil
} }
// CalculateNonceAndVUB calculates nonce and ValidUntilBlock values
// based on transaction hash. Uses MurmurHash3.
func (c *Client) CalculateNonceAndVUB(hash util.Uint256) (nonce uint32, vub uint32, err error) {
if c.multiClient != nil {
return nonce, vub, c.multiClient.iterateClients(func(c *Client) error {
nonce, vub, err = c.CalculateNonceAndVUB(hash)
return err
})
}
// TODO: cache values since some operations uses same TX as triggers
nonce = binary.LittleEndian.Uint32(hash.BytesLE())
height, err := c.client.GetTransactionHeight(hash)
if err != nil {
return 0, 0, fmt.Errorf("could not get transaction height: %w", err)
}
return nonce, height + c.notary.txValidTime, nil
}

View file

@ -2,6 +2,7 @@ package client
import ( import (
"errors" "errors"
"fmt"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
@ -71,34 +72,106 @@ func (s StaticClient) Morph() *Client {
return s.client return s.client
} }
// InvokePrm groups parameters of the Invoke operation.
type InvokePrm struct {
// required parameters
method string
args []interface{}
// optional parameters
InvokePrmOptional
}
// InvokePrmOptional groups optional parameters of the Invoke operation.
type InvokePrmOptional struct {
// hash is an optional hash of the transaction
// that generated the notification that required
// to invoke notary request.
// It is used to generate same but unique nonce and
// `validUntilBlock` values by all notification
// receivers.
hash *util.Uint256
}
// SetMethod sets method of the contract to call.
func (i *InvokePrm) SetMethod(method string) {
i.method = method
}
// SetArgs sets arguments of the contact call.
func (i *InvokePrm) SetArgs(args ...interface{}) {
i.args = args
}
// SetHash sets optional hash of the transaction.
// If hash is set and notary is enabled, StaticClient
// uses it for notary nonce and `validUntilBlock`
// calculation.
func (i *InvokePrmOptional) SetHash(hash util.Uint256) {
i.hash = &hash
}
// Invoke calls Invoke method of Client with static internal script hash and fee. // Invoke calls Invoke method of Client with static internal script hash and fee.
// Supported args types are the same as in Client. // Supported args types are the same as in Client.
// //
// If TryNotary is provided, calls NotaryInvoke on Client. // If TryNotary is provided:
func (s StaticClient) Invoke(method string, args ...interface{}) error { // - if AsAlphabet is provided, calls NotaryInvoke;
// - otherwise, calls NotaryInvokeNotAlpha.
func (s StaticClient) Invoke(prm InvokePrm) error {
if s.tryNotary { if s.tryNotary {
if s.alpha { if s.alpha {
// FIXME: do not use constant nonce for alphabet NR: #844 var (
return s.client.NotaryInvoke(s.scScriptHash, s.fee, 1, method, args...) nonce uint32 = 1
vubP *uint32
vub uint32
err error
)
if prm.hash != nil {
nonce, vub, err = s.client.CalculateNonceAndVUB(*prm.hash)
if err != nil {
return fmt.Errorf("could not calculate nonce and VUB for notary alphabet invoke: %w", err)
} }
return s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, method, args...) vubP = &vub
}
return s.client.NotaryInvoke(s.scScriptHash, s.fee, nonce, vubP, prm.method, prm.args...)
}
return s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, prm.method, prm.args...)
} }
return s.client.Invoke( return s.client.Invoke(
s.scScriptHash, s.scScriptHash,
s.fee, s.fee,
method, prm.method,
args..., prm.args...,
) )
} }
// TestInvokePrm groups parameters of the TestInvoke operation.
type TestInvokePrm struct {
method string
args []interface{}
}
// SetMethod sets method of the contract to call.
func (ti *TestInvokePrm) SetMethod(method string) {
ti.method = method
}
// SetArgs sets arguments of the contact call.
func (ti *TestInvokePrm) SetArgs(args ...interface{}) {
ti.args = args
}
// TestInvoke calls TestInvoke method of Client with static internal script hash. // TestInvoke calls TestInvoke method of Client with static internal script hash.
func (s StaticClient) TestInvoke(method string, args ...interface{}) ([]stackitem.Item, error) { func (s StaticClient) TestInvoke(prm TestInvokePrm) ([]stackitem.Item, error) {
return s.client.TestInvoke( return s.client.TestInvoke(
s.scScriptHash, s.scScriptHash,
method, prm.method,
args..., prm.args...,
) )
} }