diff --git a/cmd/frostfs-adm/internal/modules/morph/local_client.go b/cmd/frostfs-adm/internal/modules/morph/local_client.go index 2ef8774b..eca9f45b 100644 --- a/cmd/frostfs-adm/internal/modules/morph/local_client.go +++ b/cmd/frostfs-adm/internal/modules/morph/local_client.go @@ -13,12 +13,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/chaindump" - "github.com/nspcc-dev/neo-go/pkg/core/fee" "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/storage" "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/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" @@ -30,8 +28,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "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/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" @@ -150,12 +150,17 @@ func (l *localClient) CreateTxFromScript(script []byte, acc *wallet.Account, sys tx := transaction.New(script, sysFee) tx.Signers = signers tx.ValidUntilBlock = l.bc.BlockHeight() + 2 + tx.Scripts = make([]transaction.Witness, len(accounts)) + for i := range tx.Scripts { + tx.Scripts[i].VerificationScript = accounts[i].Contract.Script + } - err = l.AddNetworkFee(tx, netFee, accounts...) + fee, err := l.CalculateNetworkFee(tx) if err != nil { return nil, fmt.Errorf("failed to add network fee: %w", err) } + tx.NetworkFee = fee + netFee return tx, nil } @@ -233,67 +238,78 @@ func (l *localClient) InvokeContractVerify(util.Uint160, []smartcontract.Paramet // CalculateNetworkFee calculates network fee for the given transaction. // Copied from neo-go with minor corrections (no need to support non-notary mode): -// https://github.com/nspcc-dev/neo-go/blob/v0.99.2/pkg/services/rpcsrv/server.go#L744 +// https://github.com/nspcc-dev/neo-go/blob/v0.103.0/pkg/services/rpcsrv/server.go#L911 func (l *localClient) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) { + // Avoid setting hash for this tx: server code doesn't touch client transaction. + data := tx.Bytes() + tx, err := transaction.NewTransactionFromBytes(data) + if err != nil { + return 0, err + } + hashablePart, err := tx.EncodeHashableFields() if err != nil { - return 0, fmt.Errorf("failed to compute tx size: %w", err) + return 0, err } - size := len(hashablePart) + io.GetVarSize(len(tx.Signers)) - ef := l.bc.GetBaseExecFee() - - var netFee int64 + var ( + netFee int64 + // Verification GAS cost can't exceed this policy. + gasLimit = l.bc.GetMaxVerificationGAS() + ) for i, signer := range tx.Signers { - var verificationScript []byte - for _, w := range tx.Scripts { - if w.VerificationScript != nil && hash.Hash160(w.VerificationScript).Equals(signer.Account) { - verificationScript = w.VerificationScript - break + w := tx.Scripts[i] + if len(w.InvocationScript) == 0 { // No invocation provided, try to infer one. + var paramz []manifest.Parameter + if len(w.VerificationScript) == 0 { // Contract-based verification + cs := l.bc.GetContractState(signer.Account) + if cs == nil { + return 0, fmt.Errorf("signer %d has no verification script and no deployed contract", i) + } + md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1) + if md == nil || md.ReturnType != smartcontract.BoolType { + return 0, fmt.Errorf("signer %d has no verify method in deployed contract", i) + } + paramz = md.Parameters // Might as well have none params and it's OK. + } else { // Regular signature verification. + if vm.IsSignatureContract(w.VerificationScript) { + paramz = []manifest.Parameter{{Type: smartcontract.SignatureType}} + } else if nSigs, _, ok := vm.ParseMultiSigContract(w.VerificationScript); ok { + paramz = make([]manifest.Parameter, nSigs) + for j := 0; j < nSigs; j++ { + paramz[j] = manifest.Parameter{Type: smartcontract.SignatureType} + } + } } - } - if verificationScript == nil { - gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &tx.Scripts[i], l.maxGasInvoke) - if err != nil { - return 0, fmt.Errorf("invalid signature: %w", err) + inv := io.NewBufBinWriter() + for _, p := range paramz { + p.Type.EncodeDefaultValue(inv.BinWriter) } - netFee += gasConsumed - size += io.GetVarSize([]byte{}) + io.GetVarSize(tx.Scripts[i].InvocationScript) - continue + if inv.Err != nil { + return 0, fmt.Errorf("failed to create dummy invocation script (signer %d): %s", i, inv.Err.Error()) + } + w.InvocationScript = inv.Bytes() + } + gasConsumed, err := l.bc.VerifyWitness(signer.Account, tx, &w, gasLimit) + if err != nil && !errors.Is(err, core.ErrInvalidSignature) { + return 0, err + } + gasLimit -= gasConsumed + netFee += gasConsumed + size += io.GetVarSize(w.VerificationScript) + io.GetVarSize(w.InvocationScript) + } + if l.bc.P2PSigExtensionsEnabled() { + attrs := tx.GetAttributes(transaction.NotaryAssistedT) + if len(attrs) != 0 { + na := attrs[0].Value.(*transaction.NotaryAssisted) + netFee += (int64(na.NKeys) + 1) * l.bc.GetNotaryServiceFeePerKey() } - - fee, sizeDelta := fee.Calculate(ef, verificationScript) - netFee += fee - size += sizeDelta } - fee := l.bc.FeePerByte() netFee += int64(size) * fee - return netFee, nil } -// AddNetworkFee adds network fee for each witness script and optional extra -// network fee to transaction. `accs` is an array signer's accounts. -// Copied from neo-go with minor corrections (no need to support contract signers): -// https://github.com/nspcc-dev/neo-go/blob/6ff11baa1b9e4c71ef0d1de43b92a8c541ca732c/pkg/rpc/client/rpc.go#L960 -func (l *localClient) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs ...*wallet.Account) error { - if len(tx.Signers) != len(accs) { - return errors.New("number of signers must match number of scripts") - } - - size := io.GetVarSize(tx) - ef := l.bc.GetBaseExecFee() - for i := range tx.Signers { - netFee, sizeDelta := fee.Calculate(ef, accs[i].Contract.Script) - tx.NetworkFee += netFee - size += sizeDelta - } - - tx.NetworkFee += int64(size)*l.bc.FeePerByte() + extraFee - return nil -} - // getSigners returns an array of transaction signers and corresponding accounts from // given sender and cosigners. If cosigners list already contains sender, the sender // will be placed at the start of the list. diff --git a/cmd/frostfs-adm/internal/modules/morph/n3client.go b/cmd/frostfs-adm/internal/modules/morph/n3client.go index ac9abee8..e3042740 100644 --- a/cmd/frostfs-adm/internal/modules/morph/n3client.go +++ b/cmd/frostfs-adm/internal/modules/morph/n3client.go @@ -36,7 +36,6 @@ type Client interface { GetCommittee() (keys.PublicKeys, error) CalculateNotaryFee(uint8) (int64, error) CalculateNetworkFee(tx *transaction.Transaction) (int64, error) - AddNetworkFee(*transaction.Transaction, int64, ...*wallet.Account) error SignAndPushInvocationTx([]byte, *wallet.Account, int64, fixedn.Fixed8, []rpcclient.SignerAccount) (util.Uint256, error) SignAndPushP2PNotaryRequest(*transaction.Transaction, []byte, int64, int64, uint32, *wallet.Account) (*payload.P2PNotaryRequest, error) }