[#337] morph: Use Notary Actor for notary requests
Signed-off-by: Anna Shaleva <anna@nspcc.ru> Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
parent
35fdf6f315
commit
ddcc156ecc
4 changed files with 281 additions and 336 deletions
|
@ -153,8 +153,6 @@ func (e *notHaltStateError) Error() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errEmptyInvocationScript = errors.New("got empty invocation script from neo node")
|
|
||||||
|
|
||||||
// implementation of error interface for FrostFS-specific errors.
|
// implementation of error interface for FrostFS-specific errors.
|
||||||
type frostfsError struct {
|
type frostfsError struct {
|
||||||
err error
|
err error
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/elliptic"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -18,10 +19,12 @@ import (
|
||||||
"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/neorpc"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||||
sc "github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
sc "github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
@ -408,32 +411,32 @@ func (c *Client) NotarySignAndInvokeTX(mainTx *transaction.Transaction) error {
|
||||||
return fmt.Errorf("could not fetch current alphabet keys: %w", err)
|
return fmt.Errorf("could not fetch current alphabet keys: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
multiaddrAccount, err := c.notaryMultisigAccount(alphabetList, false, true)
|
cosigners, err := c.notaryCosignersFromTx(mainTx, alphabetList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// mainTX is expected to be pre-validated: second witness must exist and be empty
|
nAct, err := notary.NewActor(c.client, cosigners, c.acc)
|
||||||
mainTx.Scripts[1].VerificationScript = multiaddrAccount.GetVerificationScript()
|
if err != nil {
|
||||||
mainTx.Scripts[1].InvocationScript = append(
|
return err
|
||||||
[]byte{byte(opcode.PUSHDATA1), 64},
|
}
|
||||||
multiaddrAccount.SignHashable(c.rpcActor.GetNetwork(), mainTx)...,
|
|
||||||
)
|
// Sign exactly the same transaction we've got from the received Notary request.
|
||||||
|
err = nAct.Sign(mainTx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("faield to sign notary request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainH, fbH, untilActual, err := nAct.Notarize(mainTx, nil)
|
||||||
|
|
||||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
|
||||||
resp, err := c.client.SignAndPushP2PNotaryRequest(mainTx,
|
|
||||||
[]byte{byte(opcode.RET)},
|
|
||||||
-1,
|
|
||||||
0,
|
|
||||||
c.notary.fallbackTime,
|
|
||||||
c.acc)
|
|
||||||
if err != nil && !alreadyOnChainError(err) {
|
if err != nil && !alreadyOnChainError(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Debug(logs.ClientNotaryRequestWithPreparedMainTXInvoked,
|
c.logger.Debug(logs.ClientNotaryRequestWithPreparedMainTXInvoked,
|
||||||
zap.Uint32("fallback_valid_for", c.notary.fallbackTime),
|
zap.String("tx_hash", mainH.StringLE()),
|
||||||
zap.Stringer("tx_hash", resp.Hash().Reverse()))
|
zap.Uint32("valid_until_block", untilActual),
|
||||||
|
zap.String("fallback_hash", fbH.StringLE()))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -449,70 +452,147 @@ func (c *Client) notaryInvoke(committee, invokedByAlpha bool, contract util.Uint
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cosigners, err := c.notaryCosigners(invokedByAlpha, alphabetList, committee)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
params, err := invocationParams(args...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
test, err := c.makeTestInvocation(contract, method, params, cosigners)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
multiaddrAccount, err := c.notaryMultisigAccount(alphabetList, committee, invokedByAlpha)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
until, err := c.getUntilValue(vub)
|
until, err := c.getUntilValue(vub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mainTx, err := c.buildMainTx(invokedByAlpha, nonce, alphabetList, test, cosigners, multiaddrAccount, until)
|
cosigners, err := c.notaryCosigners(invokedByAlpha, alphabetList, committee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
nAct, err := notary.NewActor(c.client, cosigners, c.acc)
|
||||||
resp, err := c.client.SignAndPushP2PNotaryRequest(mainTx,
|
if err != nil {
|
||||||
[]byte{byte(opcode.RET)},
|
return err
|
||||||
-1,
|
}
|
||||||
0,
|
|
||||||
c.notary.fallbackTime,
|
mainH, fbH, untilActual, err := nAct.Notarize(nAct.MakeTunedCall(contract, method, nil, func(r *result.Invoke, t *transaction.Transaction) error {
|
||||||
c.acc)
|
if r.State != vmstate.Halt.String() {
|
||||||
|
return wrapFrostFSError(¬HaltStateError{state: r.State, exception: r.FaultException})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.ValidUntilBlock = until
|
||||||
|
t.Nonce = nonce
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, args...))
|
||||||
|
|
||||||
if err != nil && !alreadyOnChainError(err) {
|
if err != nil && !alreadyOnChainError(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Debug(logs.ClientNotaryRequestInvoked,
|
c.logger.Debug(logs.ClientNotaryRequestInvoked,
|
||||||
zap.String("method", method),
|
zap.String("method", method),
|
||||||
zap.Uint32("valid_until_block", until),
|
zap.Uint32("valid_until_block", untilActual),
|
||||||
zap.Uint32("fallback_valid_for", c.notary.fallbackTime),
|
zap.String("tx_hash", mainH.StringLE()),
|
||||||
zap.Stringer("tx_hash", resp.Hash().Reverse()))
|
zap.String("fallback_hash", fbH.StringLE()))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) makeTestInvocation(contract util.Uint160, method string, params []sc.Parameter, cosigners []transaction.Signer) (*result.Invoke, error) {
|
func (c *Client) notaryCosignersFromTx(mainTx *transaction.Transaction, alphabetList keys.PublicKeys) ([]actor.SignerAccount, error) {
|
||||||
test, err := c.client.InvokeFunction(contract, method, params, cosigners)
|
multiaddrAccount, err := c.notaryMultisigAccount(alphabetList, false, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if test.State != HaltState {
|
// Here we need to add a committee signature (second witness) to the pre-validated
|
||||||
return nil, wrapFrostFSError(¬HaltStateError{state: test.State, exception: test.FaultException})
|
// main transaction without creating a new one. However, Notary actor demands the
|
||||||
|
// proper set of signers for constructor, thus, fill it from the main transaction's signers list.
|
||||||
|
s := make([]actor.SignerAccount, 2, 3)
|
||||||
|
s[0] = actor.SignerAccount{
|
||||||
|
// Proxy contract that will pay for the execution.
|
||||||
|
Signer: mainTx.Signers[0],
|
||||||
|
Account: notary.FakeContractAccount(mainTx.Signers[0].Account),
|
||||||
|
}
|
||||||
|
s[1] = actor.SignerAccount{
|
||||||
|
// Inner ring multisignature.
|
||||||
|
Signer: mainTx.Signers[1],
|
||||||
|
Account: multiaddrAccount,
|
||||||
|
}
|
||||||
|
if len(mainTx.Signers) > 3 {
|
||||||
|
// Invoker signature (simple signature account of storage node is expected).
|
||||||
|
var acc *wallet.Account
|
||||||
|
script := mainTx.Scripts[2].VerificationScript
|
||||||
|
if len(script) == 0 {
|
||||||
|
acc = notary.FakeContractAccount(mainTx.Signers[2].Account)
|
||||||
|
} else {
|
||||||
|
pubBytes, ok := vm.ParseSignatureContract(script)
|
||||||
|
if ok {
|
||||||
|
pub, err := keys.NewPublicKeyFromBytes(pubBytes, elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse verification script of signer #2: invalid public key: %w", err)
|
||||||
|
}
|
||||||
|
acc = notary.FakeSimpleAccount(pub)
|
||||||
|
} else {
|
||||||
|
m, pubsBytes, ok := vm.ParseMultiSigContract(script)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("failed to parse verification script of signer #2: unknown witness type")
|
||||||
|
}
|
||||||
|
pubs := make(keys.PublicKeys, len(pubsBytes))
|
||||||
|
for i := range pubs {
|
||||||
|
pubs[i], err = keys.NewPublicKeyFromBytes(pubsBytes[i], elliptic.P256())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse verification script of signer #2: invalid public key #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acc, err = notary.FakeMultisigAccount(m, pubs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create fake account for signer #2: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = append(s, actor.SignerAccount{
|
||||||
|
Signer: mainTx.Signers[2],
|
||||||
|
Account: acc,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(test.Script) == 0 {
|
return s, nil
|
||||||
return nil, wrapFrostFSError(errEmptyInvocationScript)
|
}
|
||||||
|
|
||||||
|
func (c *Client) notaryCosigners(invokedByAlpha bool, ir []*keys.PublicKey, committee bool) ([]actor.SignerAccount, error) {
|
||||||
|
multiaddrAccount, err := c.notaryMultisigAccount(ir, committee, invokedByAlpha)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return test, nil
|
s := make([]actor.SignerAccount, 2, 3)
|
||||||
|
// Proxy contract that will pay for the execution.
|
||||||
|
s[0] = actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: c.notary.proxy,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
Account: notary.FakeContractAccount(c.notary.proxy),
|
||||||
|
}
|
||||||
|
// Inner ring multisignature.
|
||||||
|
s[1] = actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: multiaddrAccount.ScriptHash(),
|
||||||
|
Scopes: c.cfg.signer.Scopes,
|
||||||
|
AllowedContracts: c.cfg.signer.AllowedContracts,
|
||||||
|
AllowedGroups: c.cfg.signer.AllowedGroups,
|
||||||
|
},
|
||||||
|
Account: multiaddrAccount,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !invokedByAlpha {
|
||||||
|
// Invoker signature.
|
||||||
|
s = append(s, actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: hash.Hash160(c.acc.GetVerificationScript()),
|
||||||
|
Scopes: c.cfg.signer.Scopes,
|
||||||
|
AllowedContracts: c.cfg.signer.AllowedContracts,
|
||||||
|
AllowedGroups: c.cfg.signer.AllowedGroups,
|
||||||
|
},
|
||||||
|
Account: c.acc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last one is Notary contract that will be added to the signers list
|
||||||
|
// by Notary actor automatically.
|
||||||
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getUntilValue(vub *uint32) (uint32, error) {
|
func (c *Client) getUntilValue(vub *uint32) (uint32, error) {
|
||||||
|
@ -522,195 +602,6 @@ func (c *Client) getUntilValue(vub *uint32) (uint32, error) {
|
||||||
return c.notaryTxValidationLimit()
|
return c.notaryTxValidationLimit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) buildMainTx(invokedByAlpha bool, nonce uint32, alphabetList keys.PublicKeys, test *result.Invoke,
|
|
||||||
cosigners []transaction.Signer, multiaddrAccount *wallet.Account, until uint32) (*transaction.Transaction, error) {
|
|
||||||
// after test invocation we build main multisig transaction
|
|
||||||
|
|
||||||
u8n := uint8(len(alphabetList))
|
|
||||||
|
|
||||||
if !invokedByAlpha {
|
|
||||||
u8n++
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare main tx
|
|
||||||
mainTx := &transaction.Transaction{
|
|
||||||
Nonce: nonce,
|
|
||||||
SystemFee: test.GasConsumed,
|
|
||||||
ValidUntilBlock: until,
|
|
||||||
Script: test.Script,
|
|
||||||
Attributes: []transaction.Attribute{
|
|
||||||
{
|
|
||||||
Type: transaction.NotaryAssistedT,
|
|
||||||
Value: &transaction.NotaryAssisted{NKeys: u8n},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Signers: cosigners,
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate notary fee
|
|
||||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
|
||||||
notaryFee, err := c.client.CalculateNotaryFee(u8n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add network fee for cosigners
|
|
||||||
//nolint:staticcheck // waits for neo-go v0.99.3 with notary actors
|
|
||||||
//lint:ignore SA1019 https://git.frostfs.info/TrueCloudLab/frostfs-node/issues/202
|
|
||||||
err = c.client.AddNetworkFee(
|
|
||||||
mainTx,
|
|
||||||
notaryFee,
|
|
||||||
c.notaryAccounts(invokedByAlpha, multiaddrAccount)...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// define witnesses
|
|
||||||
mainTx.Scripts = c.notaryWitnesses(invokedByAlpha, multiaddrAccount, mainTx)
|
|
||||||
|
|
||||||
return mainTx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) notaryCosigners(invokedByAlpha bool, ir []*keys.PublicKey, committee bool) ([]transaction.Signer, error) {
|
|
||||||
s := make([]transaction.Signer, 0, 4)
|
|
||||||
|
|
||||||
// first we have proxy contract signature, as it will pay for the execution
|
|
||||||
s = append(s, transaction.Signer{
|
|
||||||
Account: c.notary.proxy,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
})
|
|
||||||
|
|
||||||
// then we have inner ring multiaddress signature
|
|
||||||
m := sigCount(ir, committee)
|
|
||||||
|
|
||||||
multisigScript, err := sc.CreateMultiSigRedeemScript(m, ir)
|
|
||||||
if err != nil {
|
|
||||||
// wrap error as FrostFS-specific since the call is not related to any client
|
|
||||||
return nil, wrapFrostFSError(fmt.Errorf("can't create ir multisig redeem script: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
s = append(s, transaction.Signer{
|
|
||||||
Account: hash.Hash160(multisigScript),
|
|
||||||
Scopes: c.cfg.signer.Scopes,
|
|
||||||
AllowedContracts: c.cfg.signer.AllowedContracts,
|
|
||||||
AllowedGroups: c.cfg.signer.AllowedGroups,
|
|
||||||
})
|
|
||||||
|
|
||||||
if !invokedByAlpha {
|
|
||||||
// then we have invoker signature
|
|
||||||
s = append(s, transaction.Signer{
|
|
||||||
Account: hash.Hash160(c.acc.GetVerificationScript()),
|
|
||||||
Scopes: c.cfg.signer.Scopes,
|
|
||||||
AllowedContracts: c.cfg.signer.AllowedContracts,
|
|
||||||
AllowedGroups: c.cfg.signer.AllowedGroups,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// last one is a placeholder for notary contract signature
|
|
||||||
s = append(s, transaction.Signer{
|
|
||||||
Account: c.notary.notary,
|
|
||||||
Scopes: transaction.None,
|
|
||||||
})
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) notaryAccounts(invokedByAlpha bool, multiaddr *wallet.Account) []*wallet.Account {
|
|
||||||
if multiaddr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
a := make([]*wallet.Account, 0, 4)
|
|
||||||
|
|
||||||
// first we have proxy account, as it will pay for the execution
|
|
||||||
a = append(a, notary.FakeContractAccount(c.notary.proxy))
|
|
||||||
|
|
||||||
// then we have inner ring multiaddress account
|
|
||||||
a = append(a, multiaddr)
|
|
||||||
|
|
||||||
if !invokedByAlpha {
|
|
||||||
// then we have invoker account
|
|
||||||
a = append(a, c.acc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// last one is a placeholder for notary contract account
|
|
||||||
a = append(a, &wallet.Account{
|
|
||||||
Contract: &wallet.Contract{},
|
|
||||||
})
|
|
||||||
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) notaryWitnesses(invokedByAlpha bool, multiaddr *wallet.Account, tx *transaction.Transaction) []transaction.Witness {
|
|
||||||
if multiaddr == nil || tx == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w := make([]transaction.Witness, 0, 4)
|
|
||||||
|
|
||||||
// first we have empty proxy witness, because notary will execute `Verify`
|
|
||||||
// method on the proxy contract to check witness
|
|
||||||
w = append(w, transaction.Witness{
|
|
||||||
InvocationScript: []byte{},
|
|
||||||
VerificationScript: []byte{},
|
|
||||||
})
|
|
||||||
|
|
||||||
// then we have inner ring multiaddress witness
|
|
||||||
|
|
||||||
// invocation script should be of the form:
|
|
||||||
// { PUSHDATA1, 64, signatureBytes... }
|
|
||||||
// to pass Notary module verification
|
|
||||||
var invokeScript []byte
|
|
||||||
|
|
||||||
magicNumber := c.rpcActor.GetNetwork()
|
|
||||||
|
|
||||||
if invokedByAlpha {
|
|
||||||
invokeScript = append(
|
|
||||||
[]byte{byte(opcode.PUSHDATA1), 64},
|
|
||||||
multiaddr.SignHashable(magicNumber, tx)...,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// we can't provide alphabet node signature
|
|
||||||
// because Storage Node doesn't own alphabet's
|
|
||||||
// private key. Thus, add dummy witness with
|
|
||||||
// empty bytes instead of signature
|
|
||||||
invokeScript = append(
|
|
||||||
[]byte{byte(opcode.PUSHDATA1), 64},
|
|
||||||
make([]byte, 64)...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
w = append(w, transaction.Witness{
|
|
||||||
InvocationScript: invokeScript,
|
|
||||||
VerificationScript: multiaddr.GetVerificationScript(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if !invokedByAlpha {
|
|
||||||
// then we have invoker witness
|
|
||||||
invokeScript = append(
|
|
||||||
[]byte{byte(opcode.PUSHDATA1), 64},
|
|
||||||
c.acc.SignHashable(magicNumber, tx)...,
|
|
||||||
)
|
|
||||||
|
|
||||||
w = append(w, transaction.Witness{
|
|
||||||
InvocationScript: invokeScript,
|
|
||||||
VerificationScript: c.acc.GetVerificationScript(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// last one is a placeholder for notary contract witness
|
|
||||||
w = append(w, transaction.Witness{
|
|
||||||
InvocationScript: append(
|
|
||||||
[]byte{byte(opcode.PUSHDATA1), 64},
|
|
||||||
make([]byte, 64)...,
|
|
||||||
),
|
|
||||||
VerificationScript: []byte{},
|
|
||||||
})
|
|
||||||
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) notaryMultisigAccount(ir []*keys.PublicKey, committee, invokedByAlpha bool) (*wallet.Account, error) {
|
func (c *Client) notaryMultisigAccount(ir []*keys.PublicKey, committee, invokedByAlpha bool) (*wallet.Account, error) {
|
||||||
m := sigCount(ir, committee)
|
m := sigCount(ir, committee)
|
||||||
|
|
||||||
|
@ -767,21 +658,6 @@ func (c *Client) depositExpirationOf() (int64, error) {
|
||||||
return currentTillBig.Int64(), nil
|
return currentTillBig.Int64(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func invocationParams(args ...any) ([]sc.Parameter, error) {
|
|
||||||
params := make([]sc.Parameter, 0, len(args))
|
|
||||||
|
|
||||||
for i := range args {
|
|
||||||
param, err := toStackParameter(args[i])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
params = append(params, param)
|
|
||||||
}
|
|
||||||
|
|
||||||
return params, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sigCount returns the number of required signature.
|
// sigCount returns the number of required signature.
|
||||||
// For FrostFS Alphabet M is a 2/3+1 of it (like in dBFT).
|
// For FrostFS Alphabet M is a 2/3+1 of it (like in dBFT).
|
||||||
// If committee is true, returns M as N/2+1.
|
// If committee is true, returns M as N/2+1.
|
||||||
|
|
|
@ -185,15 +185,15 @@ func (p Preparator) validateNotaryRequest(nr *payload.P2PNotaryRequest) error {
|
||||||
}
|
}
|
||||||
invokerWitness := ln == 4
|
invokerWitness := ln == 4
|
||||||
|
|
||||||
multiInvScript := nr.MainTransaction.Scripts[1].InvocationScript
|
// alphabet node should handle only notary requests that do not yet have inner
|
||||||
|
// ring multisignature filled => such main TXs either have empty invocation script
|
||||||
// alphabet node should handle only notary requests
|
// of the inner ring witness (in case if Notary Actor is used to create request)
|
||||||
// that have been sent unsigned (by storage nodes) =>
|
// or have it filled with dummy bytes (if request was created manually with the old
|
||||||
// such main TXs should have either a dummy or an
|
// neo-go API)
|
||||||
// empty script as an invocation script
|
|
||||||
//
|
//
|
||||||
// this check prevents notary flow recursion
|
// this check prevents notary flow recursion
|
||||||
if len(multiInvScript) > 0 && !bytes.Equal(nr.MainTransaction.Scripts[1].InvocationScript, p.dummyInvocationScript) {
|
if !(len(nr.MainTransaction.Scripts[1].InvocationScript) == 0 ||
|
||||||
|
bytes.Equal(nr.MainTransaction.Scripts[1].InvocationScript, p.dummyInvocationScript)) { // compatibility with old version
|
||||||
return ErrTXAlreadyHandled
|
return ErrTXAlreadyHandled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +220,7 @@ func (p Preparator) validateNotaryRequest(nr *payload.P2PNotaryRequest) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate main TX expiration
|
// validate main TX expiration
|
||||||
err = p.validateExpiration(nr.FallbackTransaction)
|
return p.validateExpiration(nr.FallbackTransaction)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Preparator) validateParameterOpcodes(ops []Op) error {
|
func (p Preparator) validateParameterOpcodes(ops []Op) error {
|
||||||
|
@ -363,7 +358,9 @@ func (p Preparator) validateWitnesses(w []transaction.Witness, alphaKeys keys.Pu
|
||||||
|
|
||||||
// the last one must be a placeholder for notary contract witness
|
// the last one must be a placeholder for notary contract witness
|
||||||
last := len(w) - 1
|
last := len(w) - 1
|
||||||
if !bytes.Equal(w[last].InvocationScript, p.dummyInvocationScript) || len(w[last].VerificationScript) != 0 {
|
if !(len(w[last].InvocationScript) == 0 || // https://github.com/nspcc-dev/neo-go/pull/2981
|
||||||
|
bytes.Equal(w[last].InvocationScript, p.dummyInvocationScript)) || // compatibility with old version
|
||||||
|
len(w[last].VerificationScript) != 0 {
|
||||||
return errIncorrectNotaryPlaceholder
|
return errIncorrectNotaryPlaceholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package event
|
package event
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -24,8 +25,9 @@ var (
|
||||||
alphaKeys keys.PublicKeys
|
alphaKeys keys.PublicKeys
|
||||||
wrongAlphaKeys keys.PublicKeys
|
wrongAlphaKeys keys.PublicKeys
|
||||||
|
|
||||||
dummyInvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...)
|
dummyAlphabetInvocationScript = []byte{} // expected to be empty if generated by Notary Actor, as requester can't fill it in
|
||||||
wrongDummyInvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64, 1}, make([]byte, 63)...)
|
dummyAlphabetInvocationScriptOld = append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...) // expected to be dummy if generated manually
|
||||||
|
wrongDummyInvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64, 1}, make([]byte, 63)...)
|
||||||
|
|
||||||
scriptHash util.Uint160
|
scriptHash util.Uint160
|
||||||
)
|
)
|
||||||
|
@ -61,35 +63,37 @@ func TestPrepare_IncorrectScript(t *testing.T) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
t.Run("not contract call", func(t *testing.T) {
|
for _, dummyMultisig := range []bool{true, false} { // try both empty and dummy multisig/Notary invocation witness script
|
||||||
bw := io.NewBufBinWriter()
|
t.Run(fmt.Sprintf("not contract call, compat: %t", dummyMultisig), func(t *testing.T) {
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
|
||||||
emit.Int(bw.BinWriter, 4)
|
emit.Int(bw.BinWriter, 4)
|
||||||
emit.String(bw.BinWriter, "test")
|
emit.String(bw.BinWriter, "test")
|
||||||
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
||||||
emit.Syscall(bw.BinWriter, interopnames.SystemContractCallNative) // any != interopnames.SystemContractCall
|
emit.Syscall(bw.BinWriter, interopnames.SystemContractCallNative) // any != interopnames.SystemContractCall
|
||||||
|
|
||||||
nr := correctNR(bw.Bytes(), false)
|
nr := correctNR(bw.Bytes(), dummyMultisig, false)
|
||||||
|
|
||||||
_, err := preparator.Prepare(nr)
|
_, err := preparator.Prepare(nr)
|
||||||
|
|
||||||
require.EqualError(t, err, errNotContractCall.Error())
|
require.EqualError(t, err, errNotContractCall.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("incorrect ", func(t *testing.T) {
|
t.Run(fmt.Sprintf("incorrect, compat: %t", dummyMultisig), func(t *testing.T) {
|
||||||
bw := io.NewBufBinWriter()
|
bw := io.NewBufBinWriter()
|
||||||
|
|
||||||
emit.Int(bw.BinWriter, -1)
|
emit.Int(bw.BinWriter, -1)
|
||||||
emit.String(bw.BinWriter, "test")
|
emit.String(bw.BinWriter, "test")
|
||||||
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
||||||
emit.Syscall(bw.BinWriter, interopnames.SystemContractCall)
|
emit.Syscall(bw.BinWriter, interopnames.SystemContractCall)
|
||||||
|
|
||||||
nr := correctNR(bw.Bytes(), false)
|
nr := correctNR(bw.Bytes(), dummyMultisig, false)
|
||||||
|
|
||||||
_, err := preparator.Prepare(nr)
|
_, err := preparator.Prepare(nr)
|
||||||
|
|
||||||
require.EqualError(t, err, errIncorrectCallFlag.Error())
|
require.EqualError(t, err, errIncorrectCallFlag.Error())
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrepare_IncorrectNR(t *testing.T) {
|
func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
|
@ -209,7 +213,23 @@ func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
InvocationScript: make([]byte, 1),
|
InvocationScript: make([]byte, 1),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: dummyAlphabetInvocationScript,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: errIncorrectProxyWitnesses,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect main TX proxy witness compat",
|
||||||
|
addW: false,
|
||||||
|
mTX: mTX{
|
||||||
|
scripts: []transaction.Witness{
|
||||||
|
{
|
||||||
|
InvocationScript: make([]byte, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
},
|
},
|
||||||
|
@ -224,7 +244,22 @@ func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
VerificationScript: wrongAlphaVerificationScript,
|
VerificationScript: wrongAlphaVerificationScript,
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: dummyAlphabetInvocationScript,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: errIncorrectAlphabet,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect main TX Alphabet witness compat",
|
||||||
|
addW: false,
|
||||||
|
mTX: mTX{
|
||||||
|
scripts: []transaction.Witness{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
VerificationScript: wrongAlphaVerificationScript,
|
||||||
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
},
|
},
|
||||||
|
@ -239,7 +274,24 @@ func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
VerificationScript: alphaVerificationScript,
|
VerificationScript: alphaVerificationScript,
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: dummyAlphabetInvocationScript,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
InvocationScript: wrongDummyInvocationScript,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: errIncorrectNotaryPlaceholder,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect main TX Notary witness compat",
|
||||||
|
addW: false,
|
||||||
|
mTX: mTX{
|
||||||
|
scripts: []transaction.Witness{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
VerificationScript: alphaVerificationScript,
|
||||||
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InvocationScript: wrongDummyInvocationScript,
|
InvocationScript: wrongDummyInvocationScript,
|
||||||
|
@ -289,7 +341,23 @@ func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
VerificationScript: alphaVerificationScript,
|
VerificationScript: alphaVerificationScript,
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: dummyAlphabetInvocationScript,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expErr: errIncorrectInvokerWitnesses,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "incorrect invoker TX Alphabet witness compat",
|
||||||
|
addW: true,
|
||||||
|
mTX: mTX{
|
||||||
|
scripts: []transaction.Witness{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
VerificationScript: alphaVerificationScript,
|
||||||
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
|
@ -327,7 +395,7 @@ func TestPrepare_IncorrectNR(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
correctNR := correctNR(nil, test.addW)
|
correctNR := correctNR(nil, false, test.addW)
|
||||||
incorrectNR = setIncorrectFields(*correctNR, test.mTX, test.fbTX)
|
incorrectNR = setIncorrectFields(*correctNR, test.mTX, test.fbTX)
|
||||||
|
|
||||||
_, err = preparator.Prepare(&incorrectNR)
|
_, err = preparator.Prepare(&incorrectNR)
|
||||||
|
@ -372,40 +440,42 @@ func TestPrepare_CorrectNR(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
for i := 0; i < 1; i++ { // run tests against 3 and 4 witness NR
|
for i := 0; i < 1; i++ { // run tests against 3 and 4 witness NR
|
||||||
additionalWitness := i == 0
|
for _, dummyMultisig := range []bool{true, false} { // run tests against empty and dummy multisig/Notary witness
|
||||||
nr := correctNR(script(test.hash, test.method, test.args...), additionalWitness)
|
additionalWitness := i == 0
|
||||||
|
nr := correctNR(script(test.hash, test.method, test.args...), dummyMultisig, additionalWitness)
|
||||||
|
|
||||||
event, err := preparator.Prepare(nr)
|
event, err := preparator.Prepare(nr)
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, test.method, event.Type().String())
|
|
||||||
require.Equal(t, test.hash.StringLE(), event.ScriptHash().StringLE())
|
|
||||||
|
|
||||||
// check args parsing
|
|
||||||
bw := io.NewBufBinWriter()
|
|
||||||
emit.Array(bw.BinWriter, test.args...)
|
|
||||||
|
|
||||||
ctx := vm.NewContext(bw.Bytes())
|
|
||||||
|
|
||||||
opCode, param, err := ctx.Next()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for _, opGot := range event.Params() {
|
|
||||||
require.Equal(t, opCode, opGot.code)
|
|
||||||
require.Equal(t, param, opGot.param)
|
|
||||||
|
|
||||||
opCode, param, err = ctx.Next()
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.method, event.Type().String())
|
||||||
|
require.Equal(t, test.hash.StringLE(), event.ScriptHash().StringLE())
|
||||||
|
|
||||||
|
// check args parsing
|
||||||
|
bw := io.NewBufBinWriter()
|
||||||
|
emit.Array(bw.BinWriter, test.args...)
|
||||||
|
|
||||||
|
ctx := vm.NewContext(bw.Bytes())
|
||||||
|
|
||||||
|
opCode, param, err := ctx.Next()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, opGot := range event.Params() {
|
||||||
|
require.Equal(t, opCode, opGot.code)
|
||||||
|
require.Equal(t, param, opGot.param)
|
||||||
|
|
||||||
|
opCode, param, err = ctx.Next()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = ctx.Next() // PACK opcode
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, _, err = ctx.Next() // packing len opcode
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
opCode, _, err = ctx.Next()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, opcode.RET, opCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = ctx.Next() // PACK opcode
|
|
||||||
require.NoError(t, err)
|
|
||||||
_, _, err = ctx.Next() // packing len opcode
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
opCode, _, err = ctx.Next()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, opcode.RET, opCode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -428,7 +498,7 @@ func script(hash util.Uint160, method string, args ...any) []byte {
|
||||||
return bw.Bytes()
|
return bw.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func correctNR(script []byte, additionalWitness bool) *payload.P2PNotaryRequest {
|
func correctNR(script []byte, dummyMultisig, additionalWitness bool) *payload.P2PNotaryRequest {
|
||||||
alphaVerificationScript, _ := smartcontract.CreateMultiSigRedeemScript(len(alphaKeys)*2/3+1, alphaKeys)
|
alphaVerificationScript, _ := smartcontract.CreateMultiSigRedeemScript(len(alphaKeys)*2/3+1, alphaKeys)
|
||||||
|
|
||||||
signers := []transaction.Signer{
|
signers := []transaction.Signer{
|
||||||
|
@ -443,20 +513,24 @@ func correctNR(script []byte, additionalWitness bool) *payload.P2PNotaryRequest
|
||||||
signers[2] = transaction.Signer{Account: hash.Hash160(alphaVerificationScript)}
|
signers[2] = transaction.Signer{Account: hash.Hash160(alphaVerificationScript)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multisigInv := dummyAlphabetInvocationScript
|
||||||
|
if dummyMultisig {
|
||||||
|
multisigInv = dummyAlphabetInvocationScriptOld
|
||||||
|
}
|
||||||
scripts := []transaction.Witness{
|
scripts := []transaction.Witness{
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: multisigInv,
|
||||||
VerificationScript: alphaVerificationScript,
|
VerificationScript: alphaVerificationScript,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: multisigInv,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if additionalWitness { // insert on element with index 2
|
if additionalWitness { // insert on element with index 2
|
||||||
scripts = append(scripts[:2+1], scripts[2:]...)
|
scripts = append(scripts[:2+1], scripts[2:]...)
|
||||||
scripts[2] = transaction.Witness{
|
scripts[2] = transaction.Witness{
|
||||||
InvocationScript: dummyInvocationScript,
|
InvocationScript: multisigInv,
|
||||||
VerificationScript: alphaVerificationScript,
|
VerificationScript: alphaVerificationScript,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue