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, }, }, {}, }, }, } }