Merge pull request #3233 from stepanovdmitrii/feat/neotest_contract_signer_support

neotest: Add contract signer support
This commit is contained in:
Roman Khimov 2023-12-04 10:03:49 +03:00 committed by GitHub
commit 9a270ae30c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 104 additions and 7 deletions

View file

@ -225,7 +225,7 @@ func TestLedger_GetTransactionSignersInteropAPI(t *testing.T) {
}, },
}, },
}} }}
neotest.AddNetworkFee(e.Chain, tx, c.Committee) neotest.AddNetworkFee(t, e.Chain, tx, c.Committee)
neotest.AddSystemFee(e.Chain, tx, -1) neotest.AddSystemFee(e.Chain, tx, -1)
require.NoError(t, c.Committee.SignTx(e.Chain.GetConfig().Magic, tx)) require.NoError(t, c.Committee.SignTx(e.Chain.GetConfig().Magic, tx))
c.AddNewBlock(t, tx) c.AddNewBlock(t, tx)

View file

@ -106,7 +106,7 @@ func (e *Executor) SignTx(t testing.TB, tx *transaction.Transaction, sysFee int6
Scopes: transaction.Global, Scopes: transaction.Global,
}) })
} }
AddNetworkFee(e.Chain, tx, signers...) AddNetworkFee(t, e.Chain, tx, signers...)
AddSystemFee(e.Chain, tx, sysFee) AddSystemFee(e.Chain, tx, sysFee)
for _, acc := range signers { for _, acc := range signers {
@ -280,7 +280,7 @@ func NewDeployTxBy(t testing.TB, bc *core.Blockchain, signer Signer, c *Contract
Account: signer.ScriptHash(), Account: signer.ScriptHash(),
Scopes: transaction.Global, Scopes: transaction.Global,
}} }}
AddNetworkFee(bc, tx, signer) AddNetworkFee(t, bc, tx, signer)
require.NoError(t, signer.SignTx(netmode.UnitTestNet, tx)) require.NoError(t, signer.SignTx(netmode.UnitTestNet, tx))
return tx return tx
} }
@ -297,13 +297,31 @@ func AddSystemFee(bc *core.Blockchain, tx *transaction.Transaction, sysFee int64
} }
// AddNetworkFee adds network fee to the transaction. // AddNetworkFee adds network fee to the transaction.
func AddNetworkFee(bc *core.Blockchain, tx *transaction.Transaction, signers ...Signer) { func AddNetworkFee(t testing.TB, bc *core.Blockchain, tx *transaction.Transaction, signers ...Signer) {
baseFee := bc.GetBaseExecFee() baseFee := bc.GetBaseExecFee()
size := io.GetVarSize(tx) size := io.GetVarSize(tx)
for _, sgr := range signers { for _, sgr := range signers {
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script()) if csgr, ok := sgr.(ContractSigner); ok {
tx.NetworkFee += netFee sc, err := csgr.InvocationScript(tx)
size += sizeDelta require.NoError(t, err)
txCopy := *tx
ic, err := bc.GetTestVM(trigger.Verification, &txCopy, nil)
require.NoError(t, err)
ic.UseSigners(tx.Signers)
ic.VM.GasLimit = bc.GetMaxVerificationGAS()
require.NoError(t, bc.InitVerificationContext(ic, csgr.ScriptHash(), &transaction.Witness{InvocationScript: sc, VerificationScript: csgr.Script()}))
require.NoError(t, ic.VM.Run())
tx.NetworkFee += ic.VM.GasConsumed()
size += io.GetVarSize(sc) + io.GetVarSize(csgr.Script())
} else {
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script())
tx.NetworkFee += netFee
size += sizeDelta
}
} }
tx.NetworkFee += int64(size)*bc.FeePerByte() + bc.CalculateAttributesFee(tx) tx.NetworkFee += int64(size)*bc.FeePerByte() + bc.CalculateAttributesFee(tx)
} }

View file

@ -2,6 +2,7 @@ package neotest
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"sort" "sort"
"testing" "testing"
@ -10,8 +11,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "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/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "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/opcode"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -44,6 +47,13 @@ type MultiSigner interface {
Single(n int) SingleSigner Single(n int) SingleSigner
} }
// ContractSigner is an interface for contract signer.
type ContractSigner interface {
Signer
// InvocationScript returns an invocation script to be used as invocation script for contract-based witness.
InvocationScript(tx *transaction.Transaction) ([]byte, error)
}
// signer represents a simple-signature signer. // signer represents a simple-signature signer.
type signer wallet.Account type signer wallet.Account
@ -179,3 +189,72 @@ func checkMultiSigner(t testing.TB, s Signer) {
require.Equal(t, h, accs[i].Contract.ScriptHash(), "inconsistent multi-signer accounts") require.Equal(t, h, accs[i].Contract.ScriptHash(), "inconsistent multi-signer accounts")
} }
} }
type contractSigner struct {
params func(tx *transaction.Transaction) []any
scriptHash util.Uint160
}
// NewContractSigner returns a contract signer for the provided contract hash.
// getInvParams must return params to be used as invocation script for contract-based witness.
func NewContractSigner(h util.Uint160, getInvParams func(tx *transaction.Transaction) []any) ContractSigner {
return &contractSigner{
scriptHash: h,
params: getInvParams,
}
}
// InvocationScript implements ContractSigner.
func (s *contractSigner) InvocationScript(tx *transaction.Transaction) ([]byte, error) {
params := s.params(tx)
script := io.NewBufBinWriter()
for i := range params {
emit.Any(script.BinWriter, params[i])
}
if script.Err != nil {
return nil, script.Err
}
return script.Bytes(), nil
}
// Script implements ContractSigner.
func (s *contractSigner) Script() []byte {
return []byte{}
}
// ScriptHash implements ContractSigner.
func (s *contractSigner) ScriptHash() util.Uint160 {
return s.scriptHash
}
// SignHashable implements ContractSigner.
func (s *contractSigner) SignHashable(uint32, hash.Hashable) []byte {
panic("not supported")
}
// SignTx implements ContractSigner.
func (s *contractSigner) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
pos := -1
for idx := range tx.Signers {
if tx.Signers[idx].Account.Equals(s.ScriptHash()) {
pos = idx
break
}
}
if pos < 0 {
return fmt.Errorf("signer %s not found", s.ScriptHash().String())
}
if len(tx.Scripts) < pos {
return errors.New("transaction is not yet signed by the previous signer")
}
invoc, err := s.InvocationScript(tx)
if err != nil {
return err
}
if len(tx.Scripts) == pos {
tx.Scripts = append(tx.Scripts, transaction.Witness{})
}
tx.Scripts[pos].InvocationScript = invoc
tx.Scripts[pos].VerificationScript = s.Script()
return nil
}