diff --git a/pkg/morph/client/notary.go b/pkg/morph/client/notary.go index e30cd3c1..663c5854 100644 --- a/pkg/morph/client/notary.go +++ b/pkg/morph/client/notary.go @@ -1,6 +1,7 @@ package client import ( + "encoding/binary" "errors" "fmt" "strings" @@ -243,14 +244,31 @@ func (c *Client) GetNotaryDeposit() (res int64, err error) { 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 // committee multi signature. // // 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 { 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) } + 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( setDesignateMethod, - 1, // FIXME: do not use constant nonce for alphabet NR: #844 + nonce, + vub, 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. // As for side chain list should contain all inner ring nodes. // Requires committee multi signature. // // 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 { 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) } + 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( setDesignateMethod, - 1, // FIXME: do not use constant nonce for alphabet NR: #844 + nonce, + vub, 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 // it fallbacks to a simple `Invoke()`. // -// This function must be invoked with notary enabled otherwise it throws panic. -func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, method string, args ...interface{}) error { +// `nonce` and `vub` are used only if notary is enabled. +func (c *Client) NotaryInvoke(contract util.Uint160, fee fixedn.Fixed8, nonce uint32, vub *uint32, method string, args ...interface{}) error { if c.multiClient != nil { 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.notaryInvoke(false, true, contract, nonce, method, args...) + return c.notaryInvoke(false, true, contract, nonce, vub, method, args...) } // 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.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 @@ -376,16 +423,16 @@ func (c *Client) NotarySignAndInvokeTX(mainTx *transaction.Transaction) error { 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() if err != nil { 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 if err != nil { return err @@ -427,9 +474,15 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint return err } - until, err := c.notaryTxValidationLimit() - if err != nil { - return err + var until uint32 + + if vub != nil { + until = *vub + } else { + until, err = c.notaryTxValidationLimit() + if err != nil { + return err + } } // prepare main tx @@ -774,3 +827,24 @@ func CalculateNotaryDepositAmount(c *Client, gasMul, gasDiv int64) (fixedn.Fixed 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 +} diff --git a/pkg/morph/client/static.go b/pkg/morph/client/static.go index 30f19b25..efa3d9f3 100644 --- a/pkg/morph/client/static.go +++ b/pkg/morph/client/static.go @@ -2,6 +2,7 @@ package client import ( "errors" + "fmt" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/util" @@ -71,34 +72,106 @@ func (s StaticClient) Morph() *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. // Supported args types are the same as in Client. // -// If TryNotary is provided, calls NotaryInvoke on Client. -func (s StaticClient) Invoke(method string, args ...interface{}) error { +// If TryNotary is provided: +// - if AsAlphabet is provided, calls NotaryInvoke; +// - otherwise, calls NotaryInvokeNotAlpha. +func (s StaticClient) Invoke(prm InvokePrm) error { if s.tryNotary { if s.alpha { - // FIXME: do not use constant nonce for alphabet NR: #844 - return s.client.NotaryInvoke(s.scScriptHash, s.fee, 1, method, args...) + var ( + 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) + } + + vubP = &vub + } + + return s.client.NotaryInvoke(s.scScriptHash, s.fee, nonce, vubP, prm.method, prm.args...) } - return s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, method, args...) + return s.client.NotaryInvokeNotAlpha(s.scScriptHash, s.fee, prm.method, prm.args...) } return s.client.Invoke( s.scScriptHash, s.fee, - method, - args..., + prm.method, + 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. -func (s StaticClient) TestInvoke(method string, args ...interface{}) ([]stackitem.Item, error) { +func (s StaticClient) TestInvoke(prm TestInvokePrm) ([]stackitem.Item, error) { return s.client.TestInvoke( s.scScriptHash, - method, - args..., + prm.method, + prm.args..., ) }