forked from TrueCloudLab/frostfs-node
570 lines
13 KiB
Go
570 lines
13 KiB
Go
package event
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
"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/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/network/payload"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
alphaKeys keys.PublicKeys
|
|
wrongAlphaKeys keys.PublicKeys
|
|
|
|
dummyAlphabetInvocationScript = []byte{} // expected to be empty if generated by Notary Actor, as requester can't fill it in
|
|
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
|
|
)
|
|
|
|
func init() {
|
|
privat, _ := keys.NewPrivateKey()
|
|
pub := privat.PublicKey()
|
|
|
|
alphaKeys = keys.PublicKeys{pub}
|
|
|
|
wrongPrivat, _ := keys.NewPrivateKey()
|
|
wrongPub := wrongPrivat.PublicKey()
|
|
|
|
wrongAlphaKeys = keys.PublicKeys{wrongPub}
|
|
|
|
scriptHash, _ = util.Uint160DecodeStringLE("21fce15191428e9c2f0e8d0329ff6d3dd14882de")
|
|
}
|
|
|
|
type blockCounter struct {
|
|
epoch uint32
|
|
err error
|
|
}
|
|
|
|
func (b blockCounter) BlockCount() (res uint32, err error) {
|
|
return b.epoch, b.err
|
|
}
|
|
|
|
func TestPrepare_IncorrectScript(t *testing.T) {
|
|
preparator := notaryPreparator(
|
|
PreparatorPrm{
|
|
alphaKeysSource(),
|
|
blockCounter{100, nil},
|
|
},
|
|
)
|
|
|
|
for _, dummyMultisig := range []bool{true, false} { // try both empty and dummy multisig/Notary invocation witness script
|
|
t.Run(fmt.Sprintf("not contract call, compat: %t", dummyMultisig), func(t *testing.T) {
|
|
bw := io.NewBufBinWriter()
|
|
|
|
emit.Int(bw.BinWriter, 4)
|
|
emit.String(bw.BinWriter, "test")
|
|
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
|
emit.Syscall(bw.BinWriter, interopnames.SystemContractCallNative) // any != interopnames.SystemContractCall
|
|
|
|
nr := correctNR(bw.Bytes(), dummyMultisig, false)
|
|
|
|
_, err := preparator.Prepare(nr)
|
|
|
|
require.EqualError(t, err, errNotContractCall.Error())
|
|
})
|
|
|
|
t.Run(fmt.Sprintf("incorrect, compat: %t", dummyMultisig), func(t *testing.T) {
|
|
bw := io.NewBufBinWriter()
|
|
|
|
emit.Int(bw.BinWriter, -1)
|
|
emit.String(bw.BinWriter, "test")
|
|
emit.Bytes(bw.BinWriter, scriptHash.BytesBE())
|
|
emit.Syscall(bw.BinWriter, interopnames.SystemContractCall)
|
|
|
|
nr := correctNR(bw.Bytes(), dummyMultisig, false)
|
|
|
|
_, err := preparator.Prepare(nr)
|
|
|
|
require.EqualError(t, err, errIncorrectCallFlag.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrepare_IncorrectNR(t *testing.T) {
|
|
type (
|
|
mTX struct {
|
|
sigs []transaction.Signer
|
|
scripts []transaction.Witness
|
|
attrs []transaction.Attribute
|
|
}
|
|
fbTX struct {
|
|
attrs []transaction.Attribute
|
|
}
|
|
)
|
|
|
|
setIncorrectFields := func(nr payload.P2PNotaryRequest, m mTX, f fbTX) payload.P2PNotaryRequest {
|
|
if m.sigs != nil {
|
|
nr.MainTransaction.Signers = m.sigs
|
|
}
|
|
|
|
if m.scripts != nil {
|
|
nr.MainTransaction.Scripts = m.scripts
|
|
}
|
|
|
|
if m.attrs != nil {
|
|
nr.MainTransaction.Attributes = m.attrs
|
|
}
|
|
|
|
if f.attrs != nil {
|
|
nr.FallbackTransaction.Attributes = f.attrs
|
|
}
|
|
|
|
return nr
|
|
}
|
|
|
|
alphaVerificationScript, _ := smartcontract.CreateMultiSigRedeemScript(len(alphaKeys)*2/3+1, alphaKeys)
|
|
wrongAlphaVerificationScript, _ := smartcontract.CreateMultiSigRedeemScript(len(wrongAlphaKeys)*2/3+1, wrongAlphaKeys)
|
|
|
|
tests := []struct {
|
|
name string
|
|
addW bool // additional witness for non alphabet invocations
|
|
mTX mTX
|
|
fbTX fbTX
|
|
expErr error
|
|
}{
|
|
{
|
|
name: "incorrect witness amount",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{{}},
|
|
},
|
|
expErr: errUnexpectedWitnessAmount,
|
|
},
|
|
{
|
|
name: "not dummy invocation script",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
InvocationScript: wrongDummyInvocationScript,
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: ErrTXAlreadyHandled,
|
|
},
|
|
{
|
|
name: "incorrect main TX signers amount",
|
|
addW: false,
|
|
mTX: mTX{
|
|
sigs: []transaction.Signer{{}},
|
|
},
|
|
expErr: errUnexpectedCosignersAmount,
|
|
},
|
|
{
|
|
name: "incorrect main TX Alphabet signer",
|
|
addW: false,
|
|
mTX: mTX{
|
|
sigs: []transaction.Signer{
|
|
{},
|
|
{
|
|
Account: hash.Hash160(wrongAlphaVerificationScript),
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectAlphabetSigner,
|
|
},
|
|
{
|
|
name: "incorrect main TX attribute amount",
|
|
addW: false,
|
|
mTX: mTX{
|
|
attrs: []transaction.Attribute{{}, {}},
|
|
},
|
|
expErr: errIncorrectAttributesAmount,
|
|
},
|
|
{
|
|
name: "incorrect main TX attribute",
|
|
addW: false,
|
|
mTX: mTX{
|
|
attrs: []transaction.Attribute{
|
|
{
|
|
Value: &transaction.NotaryAssisted{
|
|
NKeys: uint8(len(alphaKeys) + 1),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErr: errIncorrectAttribute,
|
|
},
|
|
{
|
|
name: "incorrect main TX proxy witness",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{
|
|
InvocationScript: make([]byte, 1),
|
|
},
|
|
{
|
|
InvocationScript: dummyAlphabetInvocationScript,
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectProxyWitnesses,
|
|
},
|
|
{
|
|
name: "incorrect main TX proxy witness compat",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{
|
|
InvocationScript: make([]byte, 1),
|
|
},
|
|
{
|
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectProxyWitnesses,
|
|
},
|
|
{
|
|
name: "incorrect main TX Alphabet witness",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
VerificationScript: wrongAlphaVerificationScript,
|
|
InvocationScript: dummyAlphabetInvocationScript,
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectAlphabet,
|
|
},
|
|
{
|
|
name: "incorrect main TX Alphabet witness compat",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
VerificationScript: wrongAlphaVerificationScript,
|
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectAlphabet,
|
|
},
|
|
{
|
|
name: "incorrect main TX Notary witness",
|
|
addW: false,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
VerificationScript: alphaVerificationScript,
|
|
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,
|
|
},
|
|
},
|
|
},
|
|
expErr: errIncorrectNotaryPlaceholder,
|
|
},
|
|
{
|
|
name: "incorrect fb TX attributes amount",
|
|
addW: false,
|
|
fbTX: fbTX{
|
|
attrs: []transaction.Attribute{{}},
|
|
},
|
|
expErr: errIncorrectFBAttributesAmount,
|
|
},
|
|
{
|
|
name: "incorrect fb TX attributes",
|
|
addW: false,
|
|
fbTX: fbTX{
|
|
attrs: []transaction.Attribute{{}, {}, {}},
|
|
},
|
|
expErr: errIncorrectFBAttributes,
|
|
},
|
|
{
|
|
name: "expired fb TX",
|
|
addW: false,
|
|
fbTX: fbTX{
|
|
[]transaction.Attribute{
|
|
{},
|
|
{
|
|
Type: transaction.NotValidBeforeT,
|
|
Value: &transaction.NotValidBefore{
|
|
Height: 1,
|
|
},
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
expErr: &ExpiredTXError{},
|
|
},
|
|
{
|
|
name: "incorrect invoker TX Alphabet witness",
|
|
addW: true,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
VerificationScript: alphaVerificationScript,
|
|
InvocationScript: dummyAlphabetInvocationScript,
|
|
},
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectInvokerWitnesses,
|
|
},
|
|
{
|
|
name: "incorrect invoker TX Alphabet witness compat",
|
|
addW: true,
|
|
mTX: mTX{
|
|
scripts: []transaction.Witness{
|
|
{},
|
|
{
|
|
VerificationScript: alphaVerificationScript,
|
|
InvocationScript: dummyAlphabetInvocationScriptOld,
|
|
},
|
|
{},
|
|
{},
|
|
},
|
|
},
|
|
expErr: errIncorrectInvokerWitnesses,
|
|
},
|
|
{
|
|
name: "incorrect main TX attribute with invoker",
|
|
addW: true,
|
|
mTX: mTX{
|
|
attrs: []transaction.Attribute{
|
|
{
|
|
Value: &transaction.NotaryAssisted{
|
|
NKeys: uint8(len(alphaKeys) + 2),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErr: errIncorrectAttribute,
|
|
},
|
|
}
|
|
|
|
preparator := notaryPreparator(
|
|
PreparatorPrm{
|
|
alphaKeysSource(),
|
|
blockCounter{100, nil},
|
|
},
|
|
)
|
|
|
|
var (
|
|
incorrectNR payload.P2PNotaryRequest
|
|
err error
|
|
)
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
correctNR := correctNR(nil, false, test.addW)
|
|
incorrectNR = setIncorrectFields(*correctNR, test.mTX, test.fbTX)
|
|
|
|
_, err = preparator.Prepare(&incorrectNR)
|
|
|
|
require.EqualError(t, err, test.expErr.Error())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPrepare_CorrectNR(t *testing.T) {
|
|
tests := []struct {
|
|
hash util.Uint160
|
|
method string
|
|
args []any
|
|
}{
|
|
{
|
|
scriptHash,
|
|
"test1",
|
|
nil,
|
|
},
|
|
{
|
|
scriptHash,
|
|
"test2",
|
|
[]any{
|
|
int64(4),
|
|
"test",
|
|
[]any{
|
|
int64(4),
|
|
false,
|
|
true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
preparator := notaryPreparator(
|
|
PreparatorPrm{
|
|
alphaKeysSource(),
|
|
blockCounter{100, nil},
|
|
},
|
|
)
|
|
|
|
for _, test := range tests {
|
|
for i := 0; i < 1; i++ { // run tests against 3 and 4 witness NR
|
|
for _, dummyMultisig := range []bool{true, false} { // run tests against empty and dummy multisig/Notary witness
|
|
additionalWitness := i == 0
|
|
nr := correctNR(script(test.hash, test.method, test.args...), dummyMultisig, additionalWitness)
|
|
|
|
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)
|
|
}
|
|
|
|
_, _, 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func alphaKeysSource() client.AlphabetKeys {
|
|
return func() (keys.PublicKeys, error) {
|
|
return alphaKeys, nil
|
|
}
|
|
}
|
|
|
|
func script(hash util.Uint160, method string, args ...any) []byte {
|
|
bw := io.NewBufBinWriter()
|
|
|
|
if len(args) > 0 {
|
|
emit.AppCall(bw.BinWriter, hash, method, callflag.All, args)
|
|
} else {
|
|
emit.AppCallNoArgs(bw.BinWriter, hash, method, callflag.All)
|
|
}
|
|
|
|
return bw.Bytes()
|
|
}
|
|
|
|
func correctNR(script []byte, dummyMultisig, additionalWitness bool) *payload.P2PNotaryRequest {
|
|
alphaVerificationScript, _ := smartcontract.CreateMultiSigRedeemScript(len(alphaKeys)*2/3+1, alphaKeys)
|
|
|
|
signers := []transaction.Signer{
|
|
{},
|
|
{
|
|
Account: hash.Hash160(alphaVerificationScript),
|
|
},
|
|
{},
|
|
}
|
|
if additionalWitness { // insert on element with index 2
|
|
signers = append(signers[:2+1], signers[2:]...)
|
|
signers[2] = transaction.Signer{Account: hash.Hash160(alphaVerificationScript)}
|
|
}
|
|
|
|
multisigInv := dummyAlphabetInvocationScript
|
|
if dummyMultisig {
|
|
multisigInv = dummyAlphabetInvocationScriptOld
|
|
}
|
|
scripts := []transaction.Witness{
|
|
{},
|
|
{
|
|
InvocationScript: multisigInv,
|
|
VerificationScript: alphaVerificationScript,
|
|
},
|
|
{
|
|
InvocationScript: multisigInv,
|
|
},
|
|
}
|
|
if additionalWitness { // insert on element with index 2
|
|
scripts = append(scripts[:2+1], scripts[2:]...)
|
|
scripts[2] = transaction.Witness{
|
|
InvocationScript: multisigInv,
|
|
VerificationScript: alphaVerificationScript,
|
|
}
|
|
}
|
|
|
|
nKeys := uint8(len(alphaKeys))
|
|
if additionalWitness {
|
|
nKeys++
|
|
}
|
|
|
|
return &payload.P2PNotaryRequest{
|
|
MainTransaction: &transaction.Transaction{
|
|
Signers: signers,
|
|
Scripts: scripts,
|
|
Attributes: []transaction.Attribute{
|
|
{
|
|
Value: &transaction.NotaryAssisted{
|
|
NKeys: nKeys,
|
|
},
|
|
},
|
|
},
|
|
Script: script,
|
|
},
|
|
FallbackTransaction: &transaction.Transaction{
|
|
Attributes: []transaction.Attribute{
|
|
{},
|
|
{
|
|
Type: transaction.NotValidBeforeT,
|
|
Value: &transaction.NotValidBefore{
|
|
Height: 1000,
|
|
},
|
|
},
|
|
{},
|
|
},
|
|
},
|
|
}
|
|
}
|