From bedfd75086a47c3cbd21019a533db06d4232c5cb Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 9 Mar 2022 13:34:39 +0300 Subject: [PATCH 01/15] neotest: port fix from 91350c3a481269ad5ebef33d47a5c7dea9ba956c --- pkg/neotest/compile.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/neotest/compile.go b/pkg/neotest/compile.go index 1fff8b961..db215a221 100644 --- a/pkg/neotest/compile.go +++ b/pkg/neotest/compile.go @@ -66,6 +66,8 @@ func CompileFile(t *testing.T, sender util.Uint160, srcPath string, configPath s o.Permissions[i] = manifest.Permission(conf.Permissions[i]) } o.SafeMethods = conf.SafeMethods + o.Overloads = conf.Overloads + o.SourceURL = conf.SourceURL m, err := compiler.CreateManifest(di, o) require.NoError(t, err) From 150041d25e98c43a9844c34584a147895db848a1 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 11 Mar 2022 11:34:37 +0300 Subject: [PATCH 02/15] core: unexport native Management's GetMinimumDeploymentFee This method is used only from Management contract. We also have neotest-based tests for `getMinimumDeploymentFee`, it should be enough. --- pkg/core/native/management.go | 8 ++++---- pkg/core/native_management_test.go | 9 --------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go index 6e0d3c836..f61c640d3 100644 --- a/pkg/core/native/management.go +++ b/pkg/core/native/management.go @@ -200,7 +200,7 @@ func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stac gas := ic.Chain.GetStoragePrice() * int64(len(nefBytes)+len(manifestBytes)) if isDeploy { - fee := m.GetMinimumDeploymentFee(ic.DAO) + fee := m.minimumDeploymentFee(ic.DAO) if fee > gas { gas = fee } @@ -400,11 +400,11 @@ func (m *Management) Destroy(d *dao.Simple, hash util.Uint160) error { } func (m *Management) getMinimumDeploymentFee(ic *interop.Context, args []stackitem.Item) stackitem.Item { - return stackitem.NewBigInteger(big.NewInt(m.GetMinimumDeploymentFee(ic.DAO))) + return stackitem.NewBigInteger(big.NewInt(m.minimumDeploymentFee(ic.DAO))) } -// GetMinimumDeploymentFee returns the minimum required fee for contract deploy. -func (m *Management) GetMinimumDeploymentFee(dao *dao.Simple) int64 { +// minimumDeploymentFee returns the minimum required fee for contract deploy. +func (m *Management) minimumDeploymentFee(dao *dao.Simple) int64 { return getIntWithKey(m.ID, dao, keyMinimumDeploymentFee) } diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index d03009af0..194d79567 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -14,15 +14,6 @@ type memoryStore struct { func (memoryStore) Close() error { return nil } -func TestMinimumDeploymentFee(t *testing.T) { - chain := newTestChain(t) - - t.Run("get, internal method", func(t *testing.T) { - n := chain.contracts.Management.GetMinimumDeploymentFee(chain.dao) - require.Equal(t, 10_00000000, int(n)) - }) -} - func TestManagement_GetNEP17Contracts(t *testing.T) { t.Run("empty chain", func(t *testing.T) { chain := newTestChain(t) From 3b4532531983a0a344375809ae6e1387ec3e829e Mon Sep 17 00:00:00 2001 From: AnnaShaleva Date: Tue, 22 Mar 2022 19:54:27 +0300 Subject: [PATCH 03/15] neotest: extend multichain constructor --- pkg/neotest/chain/chain.go | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index 5cbec396b..8845bbcb6 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -170,6 +170,28 @@ func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) { // NewMultiWithCustomConfig is similar to NewMulti except it allows to override the // default configuration. func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { + return NewMultiWithCustomConfigAndStore(t, f, nil, true) +} + +// NewMultiWithCustomConfigAndStore creates new blockchain instance with custom +// protocol configuration, custom storage, 4 validators and 6 committee members. +// Second return value is for validator signer, third -- for committee. +func NewMultiWithCustomConfigAndStore(t *testing.T, f func(*config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer, neotest.Signer) { + bc, validator, committee, err := NewMultiWithCustomConfigAndStoreNoCheck(t, f, st) + require.NoError(t, err) + if run { + go bc.Run() + t.Cleanup(bc.Close) + } + return bc, validator, committee +} + +// NewMultiWithCustomConfigAndStoreNoCheck creates new blockchain instance with +// custom protocol configuration, custom store, 4 validators and 6 committee +// members. Second return value is for validator signer, third -- for committee. +// The resulting blockchain instance is not running and constructor error is +// returned. +func NewMultiWithCustomConfigAndStoreNoCheck(t *testing.T, f func(*config.ProtocolConfiguration), st storage.Store) (*core.Blockchain, neotest.Signer, neotest.Signer, error) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, MaxTraceableBlocks: MaxTraceableBlocks, @@ -182,12 +204,11 @@ func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration if f != nil { f(&protoCfg) } + if st == nil { + st = storage.NewMemoryStore() + } - st := storage.NewMemoryStore() log := zaptest.NewLogger(t) bc, err := core.NewBlockchain(st, protoCfg, log) - require.NoError(t, err) - go bc.Run() - t.Cleanup(bc.Close) - return bc, neotest.NewMultiSigner(multiValidatorAcc...), neotest.NewMultiSigner(multiCommitteeAcc...) + return bc, neotest.NewMultiSigner(multiValidatorAcc...), neotest.NewMultiSigner(multiCommitteeAcc...), err } From 4a74c117eec3456f7986ebecc094d2f3f941e2b3 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 10 Mar 2022 17:12:04 +0300 Subject: [PATCH 04/15] *: refactor TestCreateBasicChain and its dependencies Close #2355 --- pkg/core/basic_chain_test.go | 275 +++++++++++ ...kchain_test.go => blockchain_core_test.go} | 304 +----------- pkg/core/blockchain_neotest_test.go | 338 ++++++++++++++ pkg/core/helper_test.go | 437 +----------------- pkg/core/native_management_test.go | 35 +- pkg/core/native_policy_test.go | 5 - pkg/core/stateroot_test.go | 6 + pkg/core/statesync_test.go | 116 ++--- pkg/neotest/basic.go | 38 +- pkg/neotest/client.go | 9 + .../testdata/verify/verification_contract.yml | 2 + .../verification_with_args_contract.yml | 2 + 12 files changed, 752 insertions(+), 815 deletions(-) create mode 100644 pkg/core/basic_chain_test.go rename pkg/core/{blockchain_test.go => blockchain_core_test.go} (86%) create mode 100644 pkg/core/blockchain_neotest_test.go create mode 100644 pkg/rpc/server/testdata/verify/verification_contract.yml create mode 100644 pkg/rpc/server/testdata/verify_args/verification_with_args_contract.yml diff --git a/pkg/core/basic_chain_test.go b/pkg/core/basic_chain_test.go new file mode 100644 index 000000000..d831c0b57 --- /dev/null +++ b/pkg/core/basic_chain_test.go @@ -0,0 +1,275 @@ +package core_test + +import ( + "encoding/base64" + "encoding/hex" + "math/big" + "os" + "path" + "path/filepath" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/chaindump" + "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" + "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/rpc/client/nns" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/require" +) + +const ( + // examplesPrefix is a prefix of the example smart-contracts. + examplesPrefix = "../../examples/" + // basicChainPrefix is a prefix used to store Basic chain .acc file for tests. + // It is also used to retrieve smart contracts that should be deployed to + // Basic chain. + basicChainPrefix = "../rpc/server/testdata/" +) + +var notaryModulePath = filepath.Join("..", "services", "notary") + +// TestCreateBasicChain generates "../rpc/testdata/testblocks.acc" file which +// contains data for RPC unit tests. It also is a nice integration test. +// To generate new "../rpc/testdata/testblocks.acc", follow the steps: +// 1. Set saveChain down below to true +// 2. Run tests with `$ make test` +func TestCreateBasicChain(t *testing.T) { + const saveChain = false + + bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(cfg *config.ProtocolConfiguration) { + cfg.P2PSigExtensions = true + }) + e := neotest.NewExecutor(t, bc, validators, committee) + + initBasicChain(t, e) + + if saveChain { + outStream, err := os.Create(basicChainPrefix + "testblocks.acc") + require.NoError(t, err) + t.Cleanup(func() { + outStream.Close() + }) + + writer := io.NewBinWriterFromIO(outStream) + writer.WriteU32LE(bc.BlockHeight()) + err = chaindump.Dump(bc, writer, 1, bc.BlockHeight()) + require.NoError(t, err) + } + + require.False(t, saveChain) +} + +func initBasicChain(t *testing.T, e *neotest.Executor) { + if !e.Chain.GetConfig().P2PSigExtensions { + t.Fatal("P2PSitExtensions should be enabled to init basic chain") + } + + const neoAmount = 99999000 + + gasHash := e.NativeHash(t, nativenames.Gas) + neoHash := e.NativeHash(t, nativenames.Neo) + policyHash := e.NativeHash(t, nativenames.Policy) + notaryHash := e.NativeHash(t, nativenames.Notary) + designationHash := e.NativeHash(t, nativenames.Designation) + t.Logf("native GAS hash: %v", gasHash) + t.Logf("native NEO hash: %v", neoHash) + t.Logf("native Policy hash: %v", policyHash) + t.Logf("native Notary hash: %v", notaryHash) + t.Logf("Block0 hash: %s", e.Chain.GetHeaderHash(0).StringLE()) + + acc0 := e.Validator.(neotest.MultiSigner).Single(2) // priv0 index->order and order->index conversion + priv0ScriptHash := acc0.ScriptHash() + acc1 := e.Validator.(neotest.MultiSigner).Single(0) // priv1 index->order and order->index conversion + priv1ScriptHash := acc1.ScriptHash() + neoValidatorInvoker := e.ValidatorInvoker(neoHash) + gasValidatorInvoker := e.ValidatorInvoker(gasHash) + neoPriv0Invoker := e.NewInvoker(neoHash, acc0) + gasPriv0Invoker := e.NewInvoker(gasHash, acc0) + designateSuperInvoker := e.NewInvoker(designationHash, e.Validator, e.Committee) + + deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) { + txDeployHash, cH := newDeployTx(t, e, acc0, path, configPath, true) + b := e.TopBlock(t) + return b.Hash(), txDeployHash, cH + } + + e.CheckGASBalance(t, priv0ScriptHash, big.NewInt(5000_0000)) // gas bounty + + // Block #1: move 1000 GAS and neoAmount NEO to priv0. + txMoveNeo := neoValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, neoAmount, nil) + txMoveGas := gasValidatorInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) + b := e.AddNewBlock(t, txMoveNeo, txMoveGas) + e.CheckHalt(t, txMoveNeo.Hash(), stackitem.Make(true)) + e.CheckHalt(t, txMoveGas.Hash(), stackitem.Make(true)) + t.Logf("Block1 hash: %s", b.Hash().StringLE()) + bw := io.NewBufBinWriter() + b.EncodeBinary(bw.BinWriter) + require.NoError(t, bw.Err) + jsonB, err := b.MarshalJSON() + require.NoError(t, err) + t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) + t.Logf("Block1 JSON: %s", string(jsonB)) + bw.Reset() + b.Header.EncodeBinary(bw.BinWriter) + require.NoError(t, bw.Err) + jsonH, err := b.Header.MarshalJSON() + require.NoError(t, err) + t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) + t.Logf("Header1 JSON: %s", string(jsonH)) + jsonTxMoveNeo, err := txMoveNeo.MarshalJSON() + require.NoError(t, err) + t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE()) + t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo)) + t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes())) + t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE()) + + e.EnsureGASBalance(t, priv0ScriptHash, func(balance *big.Int) bool { return balance.Cmp(big.NewInt(1000*native.GASFactor)) >= 0 }) + // info for getblockheader rpc tests + t.Logf("header hash: %s", b.Hash().StringLE()) + buf := io.NewBufBinWriter() + b.Header.EncodeBinary(buf.BinWriter) + t.Logf("header: %s", hex.EncodeToString(buf.Bytes())) + + // Block #2: deploy test_contract (Rubles contract). + cfgPath := basicChainPrefix + "test_contract.yml" + block2H, txDeployH, cHash := deployContractFromPriv0(t, basicChainPrefix+"test_contract.go", "Rubl", cfgPath, 1) + t.Logf("txDeploy: %s", txDeployH.StringLE()) + t.Logf("Block2 hash: %s", block2H.StringLE()) + + // Block #3: invoke `putValue` method on the test_contract. + rublPriv0Invoker := e.NewInvoker(cHash, acc0) + txInvH := rublPriv0Invoker.Invoke(t, true, "putValue", "testkey", "testvalue") + t.Logf("txInv: %s", txInvH.StringLE()) + + // Block #4: transfer 1000 NEO from priv0 to priv1. + neoPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 1000, nil) + + // Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0. + initTx := rublPriv0Invoker.PrepareInvoke(t, "init") + transferTx := e.NewUnsignedTx(t, rublPriv0Invoker.Hash, "transfer", cHash, priv0ScriptHash, 1000, nil) + e.SignTx(t, transferTx, 1500_0000, acc0) // Set system fee manually to avoid verification failure. + e.AddNewBlock(t, initTx, transferTx) + e.CheckHalt(t, initTx.Hash(), stackitem.NewBool(true)) + e.CheckHalt(t, transferTx.Hash(), stackitem.Make(true)) + t.Logf("receiveRublesTx: %v", transferTx.Hash().StringLE()) + + // Block #6: transfer 123 rubles from priv0 to priv1 + transferTxH := rublPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 123, nil) + t.Logf("sendRublesTx: %v", transferTxH.StringLE()) + + // Block #7: push verification contract into the chain. + verifyPath := filepath.Join(basicChainPrefix, "verify", "verification_contract.go") + verifyCfg := filepath.Join(basicChainPrefix, "verify", "verification_contract.yml") + _, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", verifyCfg, 2) + + // Block #8: deposit some GAS to notary contract for priv0. + transferTxH = gasPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, notaryHash, 10_0000_0000, []interface{}{priv0ScriptHash, int64(e.Chain.BlockHeight() + 1000)}) + t.Logf("notaryDepositTxPriv0: %v", transferTxH.StringLE()) + + // Block #9: designate new Notary node. + ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json")) + require.NoError(t, err) + require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt)) + designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.P2PNotary), []interface{}{ntr.Accounts[0].PrivateKey().PublicKey().Bytes()}) + t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes())) + + // Block #10: push verification contract with arguments into the chain. + verifyPath = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.go") + verifyCfg = filepath.Join(basicChainPrefix, "verify_args", "verification_with_args_contract.yml") + _, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", verifyCfg, 3) // block #10 + + // Block #11: push NameService contract into the chain. + nsPath := examplesPrefix + "nft-nd-nns/" + nsConfigPath := nsPath + "nns.yml" + _, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, nsConfigPath, 4) // block #11 + nsCommitteeInvoker := e.CommitteeInvoker(nsHash) + nsPriv0Invoker := e.NewInvoker(nsHash, acc0) + + // Block #12: transfer funds to committee for further NS record registration. + gasValidatorInvoker.Invoke(t, true, "transfer", + e.Validator.ScriptHash(), e.Committee.ScriptHash(), 1000_00000000, nil) // block #12 + + // Block #13: add `.com` root to NNS. + nsCommitteeInvoker.Invoke(t, stackitem.Null{}, "addRoot", "com") // block #13 + + // Block #14: register `neo.com` via NNS. + registerTxH := nsPriv0Invoker.Invoke(t, true, "register", + "neo.com", priv0ScriptHash) // block #14 + res := e.GetTxExecResult(t, registerTxH) + require.Equal(t, 1, len(res.Events)) // transfer + tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes() + require.NoError(t, err) + t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID)) + + // Block #15: set A record type with priv0 owner via NNS. + nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15 + + // Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call + txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") + // Invoke `test_contract.go`: put values to check `findstates` RPC call. + txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") + txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") + txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") + e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16 + e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true)) + e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true)) + e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true)) + e.CheckHalt(t, txPut3.Hash(), stackitem.NewBool(true)) + + // Block #17: deploy NeoFS Object contract (NEP11-Divisible). + nfsPath := examplesPrefix + "nft-d/" + nfsConfigPath := nfsPath + "nft.yml" + _, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, nfsConfigPath, 5) // block #17 + nfsPriv0Invoker := e.NewInvoker(nfsHash, acc0) + nfsPriv1Invoker := e.NewInvoker(nfsHash, acc1) + + // Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract. + containerID := util.Uint256{1, 2, 3} + objectID := util.Uint256{4, 5, 6} + txGas0toNFSH := gasPriv0Invoker.Invoke(t, true, "transfer", + priv0ScriptHash, nfsHash, 10_0000_0000, []interface{}{containerID.BytesBE(), objectID.BytesBE()}) // block #18 + res = e.GetTxExecResult(t, txGas0toNFSH) + require.Equal(t, 2, len(res.Events)) // GAS transfer + NFSO transfer + tokenID, err = res.Events[1].Item.Value().([]stackitem.Item)[3].TryBytes() + require.NoError(t, err) + t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID)) + + // Block #19: transfer 0.25 NFSO from priv0 to priv1. + nfsPriv0Invoker.Invoke(t, true, "transfer", priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) // block #19 + + // Block #20: transfer 1000 GAS to priv1. + gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), + priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) // block #20 + + // Block #21: transfer 0.05 NFSO from priv1 back to priv0. + nfsPriv1Invoker.Invoke(t, true, "transfer", priv1ScriptHash, priv0ScriptHash, 5, tokenID, nil) // block #21 + + // Compile contract to test `invokescript` RPC call + invokePath := filepath.Join(basicChainPrefix, "invoke", "invokescript_contract.go") + invokeCfg := filepath.Join(basicChainPrefix, "invoke", "invoke.yml") + _, _ = newDeployTx(t, e, acc0, invokePath, invokeCfg, false) + + // Prepare some transaction for future submission. + txSendRaw := neoPriv0Invoker.PrepareInvoke(t, "transfer", priv0ScriptHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), nil) + bw.Reset() + txSendRaw.EncodeBinary(bw.BinWriter) + t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE()) +} + +func newDeployTx(t *testing.T, e *neotest.Executor, sender neotest.Signer, sourcePath, configPath string, deploy bool) (util.Uint256, util.Uint160) { + c := neotest.CompileFile(t, sender.ScriptHash(), sourcePath, configPath) + t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", sourcePath, c.Hash.StringLE(), base64.StdEncoding.EncodeToString(c.NEF.Script)) + if deploy { + return e.DeployContractBy(t, sender, c, nil), c.Hash + } + return util.Uint256{}, c.Hash +} diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_core_test.go similarity index 86% rename from pkg/core/blockchain_test.go rename to pkg/core/blockchain_core_test.go index 7e827461e..808c997c4 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_core_test.go @@ -16,13 +16,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "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/dao" "github.com/nspcc-dev/neo-go/pkg/core/fee" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -1523,77 +1520,6 @@ func TestSubscriptions(t *testing.T) { require.NoError(t, err) } -func testDumpAndRestore(t *testing.T, dumpF, restoreF func(c *config.Config)) { - if restoreF == nil { - restoreF = dumpF - } - - bc := newTestChainWithCustomCfg(t, dumpF) - - initBasicChain(t, bc) - require.True(t, bc.BlockHeight() > 5) // ensure that test is valid - - w := io.NewBufBinWriter() - require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1)) - require.NoError(t, w.Err) - - buf := w.Bytes() - t.Run("invalid start", func(t *testing.T) { - bc2 := newTestChainWithCustomCfg(t, restoreF) - - r := io.NewBinReaderFromBuf(buf) - require.Error(t, chaindump.Restore(bc2, r, 2, 1, nil)) - }) - t.Run("good", func(t *testing.T) { - bc2 := newTestChainWithCustomCfg(t, restoreF) - - r := io.NewBinReaderFromBuf(buf) - require.NoError(t, chaindump.Restore(bc2, r, 0, 2, nil)) - require.Equal(t, uint32(1), bc2.BlockHeight()) - - r = io.NewBinReaderFromBuf(buf) // new reader because start is relative to dump - require.NoError(t, chaindump.Restore(bc2, r, 2, 1, nil)) - t.Run("check handler", func(t *testing.T) { - lastIndex := uint32(0) - errStopped := errors.New("stopped") - f := func(b *block.Block) error { - lastIndex = b.Index - if b.Index >= bc.BlockHeight()-1 { - return errStopped - } - return nil - } - require.NoError(t, chaindump.Restore(bc2, r, 0, 1, f)) - require.Equal(t, bc2.BlockHeight(), lastIndex) - - r = io.NewBinReaderFromBuf(buf) - err := chaindump.Restore(bc2, r, 4, bc.BlockHeight()-bc2.BlockHeight(), f) - require.True(t, errors.Is(err, errStopped)) - require.Equal(t, bc.BlockHeight()-1, lastIndex) - }) - }) -} - -func TestDumpAndRestore(t *testing.T) { - t.Run("no state root", func(t *testing.T) { - testDumpAndRestore(t, func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = false - }, nil) - }) - t.Run("with state root", func(t *testing.T) { - testDumpAndRestore(t, func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true - }, nil) - }) - t.Run("remove untraceable", func(t *testing.T) { - // Dump can only be created if all blocks and transactions are present. - testDumpAndRestore(t, nil, func(c *config.Config) { - c.ProtocolConfiguration.MaxTraceableBlocks = 2 - c.ProtocolConfiguration.RemoveUntraceableBlocks = true - }) - }) -} - func TestRemoveOldTransfers(t *testing.T) { // Creating proper number of transfers/blocks takes unneccessary time, so emulate // some DB with stale entries. @@ -1841,7 +1767,11 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) { c.ProtocolConfiguration.KeepOnlyLatestState = true } bcSpout := newTestChainWithCustomCfg(t, spountCfg) - initBasicChain(t, bcSpout) + + // Generate some content. + for i := 0; i < len(bcSpout.GetConfig().StandbyCommittee); i++ { + require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock())) + } // reach next to the latest state sync point and pretend that we've just restored stateSyncPoint := (int(bcSpout.BlockHeight())/stateSyncInterval + 1) * stateSyncInterval @@ -2007,227 +1937,3 @@ func setSigner(tx *transaction.Transaction, h util.Uint160) { Scopes: transaction.Global, }} } - -func TestBlockchain_StartFromExistingDB(t *testing.T) { - ps, path := newLevelDBForTestingWithPath(t, "") - customConfig := func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true // Need for P2PStateExchangeExtensions check. - } - bc := initTestChain(t, ps, customConfig) - go bc.Run() - initBasicChain(t, bc) - require.True(t, bc.BlockHeight() > 5, "ensure that basic chain is correctly initialised") - - // Information for further tests. - h := bc.BlockHeight() - cryptoLibHash, err := bc.GetNativeContractScriptHash(nativenames.CryptoLib) - require.NoError(t, err) - cryptoLibState := bc.GetContractState(cryptoLibHash) - require.NotNil(t, cryptoLibState) - var ( - managementID = -1 - managementContractPrefix = 8 - ) - - bc.Close() // Ensure persist is done and persistent store is properly closed. - - newPS := func(t *testing.T) storage.Store { - ps, _ = newLevelDBForTestingWithPath(t, path) - t.Cleanup(func() { require.NoError(t, ps.Close()) }) - return ps - } - t.Run("mismatch storage version", func(t *testing.T) { - ps = newPS(t) - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - d := dao.NewSimple(cache, bc.config.StateRootInHeader, bc.config.P2PStateExchangeExtensions) - d.PutVersion(dao.Version{ - Value: "0.0.0", - }) - _, err := d.Persist() // Persist to `cache` wrapper. - require.NoError(t, err) - _, err = initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "storage version mismatch")) - }) - t.Run("mismatch StateRootInHeader", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, func(c *config.Config) { - customConfig(c) - c.ProtocolConfiguration.StateRootInHeader = false - }) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "StateRootInHeader setting mismatch")) - }) - t.Run("mismatch P2PSigExtensions", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, func(c *config.Config) { - customConfig(c) - c.ProtocolConfiguration.P2PSigExtensions = false - }) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "P2PSigExtensions setting mismatch")) - }) - t.Run("mismatch P2PStateExchangeExtensions", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, func(c *config.Config) { - customConfig(c) - c.ProtocolConfiguration.StateRootInHeader = true - c.ProtocolConfiguration.P2PStateExchangeExtensions = true - }) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "P2PStateExchangeExtensions setting mismatch")) - }) - t.Run("mismatch KeepOnlyLatestState", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, func(c *config.Config) { - customConfig(c) - c.ProtocolConfiguration.KeepOnlyLatestState = true - }) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "KeepOnlyLatestState setting mismatch")) - }) - t.Run("corrupted headers", func(t *testing.T) { - ps = newPS(t) - - // Corrupt headers hashes batch. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - key := make([]byte, 5) - key[0] = byte(storage.IXHeaderHashList) - binary.BigEndian.PutUint32(key[1:], 1) - cache.Put(key, []byte{1, 2, 3}) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "failed to read batch of 2000")) - }) - t.Run("corrupted current header height", func(t *testing.T) { - ps = newPS(t) - - // Remove current header. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - cache.Delete([]byte{byte(storage.SYSCurrentHeader)}) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "failed to retrieve current header")) - }) - t.Run("missing last batch of 2000 headers and missing last header", func(t *testing.T) { - ps = newPS(t) - - // Remove latest headers hashes batch and current header. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - cache.Delete([]byte{byte(storage.IXHeaderHashList)}) - currHeaderInfo, err := cache.Get([]byte{byte(storage.SYSCurrentHeader)}) - require.NoError(t, err) - currHeaderHash, err := util.Uint256DecodeBytesLE(currHeaderInfo[:32]) - require.NoError(t, err) - cache.Delete(append([]byte{byte(storage.DataExecutable)}, currHeaderHash.BytesBE()...)) - - _, err = initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "could not get header")) - }) - t.Run("missing last block", func(t *testing.T) { - ps = newPS(t) - - // Remove current block from storage. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - cache.Delete([]byte{byte(storage.SYSCurrentBlock)}) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "failed to retrieve current block height")) - }) - t.Run("missing last stateroot", func(t *testing.T) { - ps = newPS(t) - - // Remove latest stateroot from storage. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - key := make([]byte, 5) - key[0] = byte(storage.DataMPTAux) - binary.BigEndian.PutUint32(key, h) - cache.Delete(key) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "can't init MPT at height")) - }) - t.Run("failed native Management initialisation", func(t *testing.T) { - ps = newPS(t) - - // Corrupt serialised CryptoLib state. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - key := make([]byte, 1+4+1+20) - key[0] = byte(storage.STStorage) - binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) - key[5] = byte(managementContractPrefix) - copy(key[6:], cryptoLibHash.BytesBE()) - cache.Put(key, []byte{1, 2, 3}) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "can't init cache for Management native contract")) - }) - t.Run("invalid native contract deactivation", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, func(c *config.Config) { - customConfig(c) - c.ProtocolConfiguration.NativeUpdateHistories = map[string][]uint32{ - nativenames.Policy: {0}, - nativenames.Neo: {0}, - nativenames.Gas: {0}, - nativenames.Designation: {0}, - nativenames.StdLib: {0}, - nativenames.Management: {0}, - nativenames.Oracle: {0}, - nativenames.Ledger: {0}, - nativenames.Notary: {0}, - nativenames.CryptoLib: {h + 10}, - } - }) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is already stored, but marked as inactive for height %d in config", nativenames.CryptoLib, h))) - }) - t.Run("invalid native contract activation", func(t *testing.T) { - ps = newPS(t) - - // Remove CryptoLib from storage. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - key := make([]byte, 1+4+1+20) - key[0] = byte(storage.STStorage) - binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) - key[5] = byte(managementContractPrefix) - copy(key[6:], cryptoLibHash.BytesBE()) - cache.Delete(key) - - _, err := initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is not stored, but should be active at height %d according to config", nativenames.CryptoLib, h))) - }) - t.Run("stored and autogenerated native contract's states mismatch", func(t *testing.T) { - ps = newPS(t) - - // Change stored CryptoLib state. - cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. - key := make([]byte, 1+4+1+20) - key[0] = byte(storage.STStorage) - binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) - key[5] = byte(managementContractPrefix) - copy(key[6:], cryptoLibHash.BytesBE()) - cs := *cryptoLibState - cs.ID = -123 - csBytes, err := stackitem.SerializeConvertible(&cs) - require.NoError(t, err) - cache.Put(key, csBytes) - - _, err = initTestChainNoCheck(t, cache, customConfig) - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch (stored contract state differs from autogenerated one)", nativenames.CryptoLib))) - }) - - t.Run("good", func(t *testing.T) { - ps = newPS(t) - _, err := initTestChainNoCheck(t, ps, customConfig) - require.NoError(t, err) - }) -} diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go new file mode 100644 index 000000000..7caa409ef --- /dev/null +++ b/pkg/core/blockchain_neotest_test.go @@ -0,0 +1,338 @@ +package core_test + +import ( + "encoding/binary" + "errors" + "fmt" + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/config" + "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/dao" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestBlockchain_DumpAndRestore(t *testing.T) { + t.Run("no state root", func(t *testing.T) { + testDumpAndRestore(t, func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = false + c.P2PSigExtensions = true + }, nil) + }) + t.Run("with state root", func(t *testing.T) { + testDumpAndRestore(t, func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + c.P2PSigExtensions = true + }, nil) + }) + t.Run("remove untraceable", func(t *testing.T) { + // Dump can only be created if all blocks and transactions are present. + testDumpAndRestore(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + }, func(c *config.ProtocolConfiguration) { + c.MaxTraceableBlocks = 2 + c.RemoveUntraceableBlocks = true + c.P2PSigExtensions = true + }) + }) +} + +func testDumpAndRestore(t *testing.T, dumpF, restoreF func(c *config.ProtocolConfiguration)) { + if restoreF == nil { + restoreF = dumpF + } + + bc, validators, committee := chain.NewMultiWithCustomConfig(t, dumpF) + e := neotest.NewExecutor(t, bc, validators, committee) + + initBasicChain(t, e) + require.True(t, bc.BlockHeight() > 5) // ensure that test is valid + + w := io.NewBufBinWriter() + require.NoError(t, chaindump.Dump(bc, w.BinWriter, 0, bc.BlockHeight()+1)) + require.NoError(t, w.Err) + + buf := w.Bytes() + t.Run("invalid start", func(t *testing.T) { + bc2, _, _ := chain.NewMultiWithCustomConfig(t, restoreF) + + r := io.NewBinReaderFromBuf(buf) + require.Error(t, chaindump.Restore(bc2, r, 2, 1, nil)) + }) + t.Run("good", func(t *testing.T) { + bc2, _, _ := chain.NewMultiWithCustomConfig(t, dumpF) + + r := io.NewBinReaderFromBuf(buf) + require.NoError(t, chaindump.Restore(bc2, r, 0, 2, nil)) + require.Equal(t, uint32(1), bc2.BlockHeight()) + + r = io.NewBinReaderFromBuf(buf) // new reader because start is relative to dump + require.NoError(t, chaindump.Restore(bc2, r, 2, 1, nil)) + t.Run("check handler", func(t *testing.T) { + lastIndex := uint32(0) + errStopped := errors.New("stopped") + f := func(b *block.Block) error { + lastIndex = b.Index + if b.Index >= bc.BlockHeight()-1 { + return errStopped + } + return nil + } + require.NoError(t, chaindump.Restore(bc2, r, 0, 1, f)) + require.Equal(t, bc2.BlockHeight(), lastIndex) + + r = io.NewBinReaderFromBuf(buf) + err := chaindump.Restore(bc2, r, 4, bc.BlockHeight()-bc2.BlockHeight(), f) + require.True(t, errors.Is(err, errStopped)) + require.Equal(t, bc.BlockHeight()-1, lastIndex) + }) + }) +} + +func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) { + if dbPath == "" { + dbPath = t.TempDir() + } + dbOptions := storage.LevelDBOptions{ + DataDirectoryPath: dbPath, + } + newLevelStore, err := storage.NewLevelDBStore(dbOptions) + require.Nil(t, err, "NewLevelDBStore error") + return newLevelStore, dbPath +} + +func TestBlockchain_StartFromExistingDB(t *testing.T) { + ps, path := newLevelDBForTestingWithPath(t, "") + customConfig := func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true // Need for P2PStateExchangeExtensions check. + c.P2PSigExtensions = true // Need for basic chain initializer. + } + bc, validators, committee, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, ps) + require.NoError(t, err) + go bc.Run() + e := neotest.NewExecutor(t, bc, validators, committee) + initBasicChain(t, e) + require.True(t, bc.BlockHeight() > 5, "ensure that basic chain is correctly initialised") + + // Information for further tests. + h := bc.BlockHeight() + cryptoLibHash, err := bc.GetNativeContractScriptHash(nativenames.CryptoLib) + require.NoError(t, err) + cryptoLibState := bc.GetContractState(cryptoLibHash) + require.NotNil(t, cryptoLibState) + var ( + managementID = -1 + managementContractPrefix = 8 + ) + + bc.Close() // Ensure persist is done and persistent store is properly closed. + + newPS := func(t *testing.T) storage.Store { + ps, _ = newLevelDBForTestingWithPath(t, path) + t.Cleanup(func() { require.NoError(t, ps.Close()) }) + return ps + } + t.Run("mismatch storage version", func(t *testing.T) { + ps = newPS(t) + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + d := dao.NewSimple(cache, bc.GetConfig().StateRootInHeader, bc.GetConfig().P2PStateExchangeExtensions) + d.PutVersion(dao.Version{ + Value: "0.0.0", + }) + _, err := d.Persist() // Persist to `cache` wrapper. + require.NoError(t, err) + _, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "storage version mismatch"), err) + }) + t.Run("mismatch StateRootInHeader", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.ProtocolConfiguration) { + customConfig(c) + c.StateRootInHeader = false + }, ps) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "StateRootInHeader setting mismatch"), err) + }) + t.Run("mismatch P2PSigExtensions", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.ProtocolConfiguration) { + customConfig(c) + c.P2PSigExtensions = false + }, ps) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "P2PSigExtensions setting mismatch"), err) + }) + t.Run("mismatch P2PStateExchangeExtensions", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.ProtocolConfiguration) { + customConfig(c) + c.StateRootInHeader = true + c.P2PStateExchangeExtensions = true + }, ps) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "P2PStateExchangeExtensions setting mismatch"), err) + }) + t.Run("mismatch KeepOnlyLatestState", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.ProtocolConfiguration) { + customConfig(c) + c.KeepOnlyLatestState = true + }, ps) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "KeepOnlyLatestState setting mismatch"), err) + }) + t.Run("corrupted headers", func(t *testing.T) { + ps = newPS(t) + + // Corrupt headers hashes batch. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + key := make([]byte, 5) + key[0] = byte(storage.IXHeaderHashList) + binary.BigEndian.PutUint32(key[1:], 1) + cache.Put(key, []byte{1, 2, 3}) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "failed to read batch of 2000"), err) + }) + t.Run("corrupted current header height", func(t *testing.T) { + ps = newPS(t) + + // Remove current header. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + cache.Delete([]byte{byte(storage.SYSCurrentHeader)}) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "failed to retrieve current header"), err) + }) + t.Run("missing last batch of 2000 headers and missing last header", func(t *testing.T) { + ps = newPS(t) + + // Remove latest headers hashes batch and current header. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + cache.Delete([]byte{byte(storage.IXHeaderHashList)}) + currHeaderInfo, err := cache.Get([]byte{byte(storage.SYSCurrentHeader)}) + require.NoError(t, err) + currHeaderHash, err := util.Uint256DecodeBytesLE(currHeaderInfo[:32]) + require.NoError(t, err) + cache.Delete(append([]byte{byte(storage.DataExecutable)}, currHeaderHash.BytesBE()...)) + + _, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "could not get header"), err) + }) + t.Run("missing last block", func(t *testing.T) { + ps = newPS(t) + + // Remove current block from storage. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + cache.Delete([]byte{byte(storage.SYSCurrentBlock)}) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "failed to retrieve current block height"), err) + }) + t.Run("missing last stateroot", func(t *testing.T) { + ps = newPS(t) + + // Remove latest stateroot from storage. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + key := make([]byte, 5) + key[0] = byte(storage.DataMPTAux) + binary.BigEndian.PutUint32(key, h) + cache.Delete(key) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "can't init MPT at height"), err) + }) + t.Run("failed native Management initialisation", func(t *testing.T) { + ps = newPS(t) + + // Corrupt serialised CryptoLib state. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + key := make([]byte, 1+4+1+20) + key[0] = byte(storage.STStorage) + binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) + key[5] = byte(managementContractPrefix) + copy(key[6:], cryptoLibHash.BytesBE()) + cache.Put(key, []byte{1, 2, 3}) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "can't init cache for Management native contract"), err) + }) + t.Run("invalid native contract deactivation", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, func(c *config.ProtocolConfiguration) { + customConfig(c) + c.NativeUpdateHistories = map[string][]uint32{ + nativenames.Policy: {0}, + nativenames.Neo: {0}, + nativenames.Gas: {0}, + nativenames.Designation: {0}, + nativenames.StdLib: {0}, + nativenames.Management: {0}, + nativenames.Oracle: {0}, + nativenames.Ledger: {0}, + nativenames.Notary: {0}, + nativenames.CryptoLib: {h + 10}, + } + }, ps) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is already stored, but marked as inactive for height %d in config", nativenames.CryptoLib, h)), err) + }) + t.Run("invalid native contract activation", func(t *testing.T) { + ps = newPS(t) + + // Remove CryptoLib from storage. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + key := make([]byte, 1+4+1+20) + key[0] = byte(storage.STStorage) + binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) + key[5] = byte(managementContractPrefix) + copy(key[6:], cryptoLibHash.BytesBE()) + cache.Delete(key) + + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s is not stored, but should be active at height %d according to config", nativenames.CryptoLib, h)), err) + }) + t.Run("stored and autogenerated native contract's states mismatch", func(t *testing.T) { + ps = newPS(t) + + // Change stored CryptoLib state. + cache := storage.NewMemCachedStore(ps) // Extra wrapper to avoid good DB corruption. + key := make([]byte, 1+4+1+20) + key[0] = byte(storage.STStorage) + binary.LittleEndian.PutUint32(key[1:], uint32(managementID)) + key[5] = byte(managementContractPrefix) + copy(key[6:], cryptoLibHash.BytesBE()) + cs := *cryptoLibState + cs.ID = -123 + csBytes, err := stackitem.SerializeConvertible(&cs) + require.NoError(t, err) + cache.Put(key, csBytes) + + _, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch (stored contract state differs from autogenerated one)", nativenames.CryptoLib)), err) + }) + + t.Run("good", func(t *testing.T) { + ps = newPS(t) + _, _, _, err := chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, ps) + require.NoError(t, err) + }) +} diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 927932bf3..e7f1cf38f 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -1,13 +1,9 @@ package core import ( - "encoding/base64" - "encoding/hex" "encoding/json" "fmt" "math/big" - "os" - "path" "path/filepath" "strings" "testing" @@ -18,18 +14,12 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" - "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" - "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/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/client/nns" "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/trigger" @@ -47,9 +37,6 @@ import ( // multisig address which possess all NEO. var neoOwner = testchain.MultisigScriptHash() -// examplesPrefix is a prefix of the example smart-contracts. -const examplesPrefix = "../../examples/" - // newTestChain should be called before newBlock invocation to properly setup // global state. func newTestChain(t testing.TB) *Blockchain { @@ -68,35 +55,21 @@ func newTestChainWithCustomCfgAndStore(t testing.TB, st storage.Store, f func(*c } func newLevelDBForTesting(t testing.TB) storage.Store { - newLevelStore, _ := newLevelDBForTestingWithPath(t, "") - return newLevelStore -} - -func newLevelDBForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) { - if dbPath == "" { - dbPath = t.TempDir() - } + dbPath := t.TempDir() dbOptions := storage.LevelDBOptions{ DataDirectoryPath: dbPath, } newLevelStore, err := storage.NewLevelDBStore(dbOptions) require.Nil(t, err, "NewLevelDBStore error") - return newLevelStore, dbPath + return newLevelStore } func newBoltStoreForTesting(t testing.TB) storage.Store { - boltDBStore, _ := newBoltStoreForTestingWithPath(t, "") - return boltDBStore -} - -func newBoltStoreForTestingWithPath(t testing.TB, dbPath string) (storage.Store, string) { - if dbPath == "" { - d := t.TempDir() - dbPath = filepath.Join(d, "test_bolt_db") - } + d := t.TempDir() + dbPath := filepath.Join(d, "test_bolt_db") boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath}) require.NoError(t, err) - return boltDBStore, dbPath + return boltDBStore } func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain { @@ -218,382 +191,6 @@ func TestBug1728(t *testing.T) { require.Equal(t, aer.VMState, vm.HaltState) } -// This function generates "../rpc/testdata/testblocks.acc" file which contains data -// for RPC unit tests. It also is a nice integration test. -// To generate new "../rpc/testdata/testblocks.acc", follow the steps: -// 1. Set saveChain down below to true -// 2. Run tests with `$ make test` -func TestCreateBasicChain(t *testing.T) { - const saveChain = false - const prefix = "../rpc/server/testdata/" - - bc := newTestChain(t) - initBasicChain(t, bc) - - if saveChain { - outStream, err := os.Create(prefix + "testblocks.acc") - require.NoError(t, err) - t.Cleanup(func() { - outStream.Close() - }) - - writer := io.NewBinWriterFromIO(outStream) - writer.WriteU32LE(bc.BlockHeight()) - err = chaindump.Dump(bc, writer, 1, bc.BlockHeight()) - require.NoError(t, err) - } - - priv0 := testchain.PrivateKeyByID(0) - priv1 := testchain.PrivateKeyByID(1) - priv0ScriptHash := priv0.GetScriptHash() - acc0 := wallet.NewAccountFromPrivateKey(priv0) - - // Prepare some transaction for future submission. - txSendRaw := newNEP17Transfer(bc.contracts.NEO.Hash, priv0ScriptHash, priv1.GetScriptHash(), int64(fixedn.Fixed8FromInt64(1000))) - txSendRaw.ValidUntilBlock = bc.config.MaxValidUntilBlockIncrement - txSendRaw.Nonce = 0x1234 - txSendRaw.Signers = []transaction.Signer{{ - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - AllowedContracts: nil, - AllowedGroups: nil, - }} - require.NoError(t, addNetworkFee(bc, txSendRaw, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txSendRaw)) - bw := io.NewBufBinWriter() - txSendRaw.EncodeBinary(bw.BinWriter) - t.Logf("sendrawtransaction: \n\tbase64: %s\n\tHash LE: %s", base64.StdEncoding.EncodeToString(bw.Bytes()), txSendRaw.Hash().StringLE()) - require.False(t, saveChain) -} - -func initBasicChain(t *testing.T, bc *Blockchain) { - const prefix = "../rpc/server/testdata/" - // Increase in case if you need more blocks - const validUntilBlock = 1200 - - // To be incremented after each created transaction to keep chain constant. - var testNonce uint32 = 1 - - // Use as nonce when new transaction is created to avoid random data in tests. - getNextNonce := func() uint32 { - testNonce++ - return testNonce - } - - const neoAmount = 99999000 - - gasHash := bc.contracts.GAS.Hash - neoHash := bc.contracts.NEO.Hash - policyHash := bc.contracts.Policy.Hash - notaryHash := bc.contracts.Notary.Hash - t.Logf("native GAS hash: %v", gasHash) - t.Logf("native NEO hash: %v", neoHash) - t.Logf("native Policy hash: %v", policyHash) - t.Logf("native Notary hash: %v", notaryHash) - t.Logf("Block0 hash: %s", bc.GetHeaderHash(0).StringLE()) - - priv0 := testchain.PrivateKeyByID(0) - priv0ScriptHash := priv0.GetScriptHash() - priv1 := testchain.PrivateKeyByID(1) - priv1ScriptHash := priv1.GetScriptHash() - acc0 := wallet.NewAccountFromPrivateKey(priv0) - acc1 := wallet.NewAccountFromPrivateKey(priv1) - - deployContractFromPriv0 := func(t *testing.T, path, contractName string, configPath *string, expectedID int32) (util.Uint256, util.Uint256, util.Uint160) { - txDeploy, _ := newDeployTx(t, bc, priv0ScriptHash, path, contractName, configPath) - txDeploy.Nonce = getNextNonce() - txDeploy.ValidUntilBlock = validUntilBlock - require.NoError(t, addNetworkFee(bc, txDeploy, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txDeploy)) - b := bc.newBlock(txDeploy) - require.NoError(t, bc.AddBlock(b)) // block #11 - checkTxHalt(t, bc, txDeploy.Hash()) - sh, err := bc.GetContractScriptHash(expectedID) - require.NoError(t, err) - return b.Hash(), txDeploy.Hash(), sh - } - - require.Equal(t, big.NewInt(5000_0000), bc.GetUtilityTokenBalance(priv0ScriptHash)) // gas bounty - - // Block #1: move 1000 GAS and neoAmount NEO to priv0. - txMoveNeo, err := testchain.NewTransferFromOwner(bc, neoHash, priv0ScriptHash, neoAmount, getNextNonce(), validUntilBlock) - require.NoError(t, err) - // Move some GAS to one simple account. - txMoveGas, err := testchain.NewTransferFromOwner(bc, gasHash, priv0ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), - getNextNonce(), validUntilBlock) - require.NoError(t, err) - b := bc.newBlock(txMoveNeo, txMoveGas) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, txMoveGas.Hash()) - checkTxHalt(t, bc, txMoveNeo.Hash()) - t.Logf("Block1 hash: %s", b.Hash().StringLE()) - bw := io.NewBufBinWriter() - b.EncodeBinary(bw.BinWriter) - require.NoError(t, bw.Err) - jsonB, err := b.MarshalJSON() - require.NoError(t, err) - t.Logf("Block1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) - t.Logf("Block1 JSON: %s", string(jsonB)) - bw.Reset() - b.Header.EncodeBinary(bw.BinWriter) - require.NoError(t, bw.Err) - jsonH, err := b.Header.MarshalJSON() - require.NoError(t, err) - t.Logf("Header1 base64: %s", base64.StdEncoding.EncodeToString(bw.Bytes())) - t.Logf("Header1 JSON: %s", string(jsonH)) - jsonTxMoveNeo, err := txMoveNeo.MarshalJSON() - require.NoError(t, err) - t.Logf("txMoveNeo hash: %s", txMoveNeo.Hash().StringLE()) - t.Logf("txMoveNeo JSON: %s", string(jsonTxMoveNeo)) - t.Logf("txMoveNeo base64: %s", base64.StdEncoding.EncodeToString(txMoveNeo.Bytes())) - t.Logf("txMoveGas hash: %s", txMoveGas.Hash().StringLE()) - - require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash).Cmp(big.NewInt(1000*native.GASFactor)) >= 0) - // info for getblockheader rpc tests - t.Logf("header hash: %s", b.Hash().StringLE()) - buf := io.NewBufBinWriter() - b.Header.EncodeBinary(buf.BinWriter) - t.Logf("header: %s", hex.EncodeToString(buf.Bytes())) - - // Block #2: deploy test_contract. - cfgPath := prefix + "test_contract.yml" - block2H, txDeployH, cHash := deployContractFromPriv0(t, prefix+"test_contract.go", "Rubl", &cfgPath, 1) - t.Logf("txDeploy: %s", txDeployH.StringLE()) - t.Logf("Block2 hash: %s", block2H.StringLE()) - - // Block #3: invoke `putValue` method on the test_contract. - script := io.NewBufBinWriter() - emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "testvalue") - txInv := transaction.New(script.Bytes(), 1*native.GASFactor) - txInv.Nonce = getNextNonce() - txInv.ValidUntilBlock = validUntilBlock - txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}} - require.NoError(t, addNetworkFee(bc, txInv, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txInv)) - b = bc.newBlock(txInv) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, txInv.Hash()) - t.Logf("txInv: %s", txInv.Hash().StringLE()) - - // Block #4: transfer 0.0000_1 NEO from priv0 to priv1. - txNeo0to1 := newNEP17Transfer(neoHash, priv0ScriptHash, priv1ScriptHash, 1000) - txNeo0to1.Nonce = getNextNonce() - txNeo0to1.ValidUntilBlock = validUntilBlock - txNeo0to1.Signers = []transaction.Signer{ - { - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - AllowedContracts: nil, - AllowedGroups: nil, - }, - } - require.NoError(t, addNetworkFee(bc, txNeo0to1, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txNeo0to1)) - b = bc.newBlock(txNeo0to1) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, txNeo0to1.Hash()) - - // Block #5: initialize rubles contract and transfer 1000 rubles from the contract to priv0. - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, cHash, "init", callflag.All) - initTx := transaction.New(w.Bytes(), 1*native.GASFactor) - initTx.Nonce = getNextNonce() - initTx.ValidUntilBlock = validUntilBlock - initTx.Signers = []transaction.Signer{{Account: priv0ScriptHash}} - require.NoError(t, addNetworkFee(bc, initTx, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), initTx)) - transferTx := newNEP17Transfer(cHash, cHash, priv0ScriptHash, 1000) - transferTx.Nonce = getNextNonce() - transferTx.ValidUntilBlock = validUntilBlock - transferTx.Signers = []transaction.Signer{ - { - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - AllowedContracts: nil, - AllowedGroups: nil, - }, - } - require.NoError(t, addNetworkFee(bc, transferTx, acc0)) - transferTx.SystemFee += 1000000 - require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) - b = bc.newBlock(initTx, transferTx) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, initTx.Hash()) - checkTxHalt(t, bc, transferTx.Hash()) - t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE()) - - // Block #6: transfer 123 rubles from priv0 to priv1 - transferTx = newNEP17Transfer(cHash, priv0.GetScriptHash(), priv1ScriptHash, 123) - transferTx.Nonce = getNextNonce() - transferTx.ValidUntilBlock = validUntilBlock - transferTx.Signers = []transaction.Signer{ - { - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - AllowedContracts: nil, - AllowedGroups: nil, - }, - } - require.NoError(t, addNetworkFee(bc, transferTx, acc0)) - transferTx.SystemFee += 1000000 - require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) - b = bc.newBlock(transferTx) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, transferTx.Hash()) - t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) - - // Block #7: push verification contract into the chain. - verifyPath := filepath.Join(prefix, "verify", "verification_contract.go") - _, _, _ = deployContractFromPriv0(t, verifyPath, "Verify", nil, 2) - - // Block #8: deposit some GAS to notary contract for priv0. - transferTx = newNEP17Transfer(gasHash, priv0.GetScriptHash(), notaryHash, 10_0000_0000, priv0.GetScriptHash(), int64(bc.BlockHeight()+1000)) - transferTx.Nonce = getNextNonce() - transferTx.ValidUntilBlock = validUntilBlock - transferTx.Signers = []transaction.Signer{ - { - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - }, - } - require.NoError(t, addNetworkFee(bc, transferTx, acc0)) - transferTx.SystemFee += 10_0000 - require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) - b = bc.newBlock(transferTx) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, transferTx.Hash()) - t.Logf("notaryDepositTxPriv0: %v", transferTx.Hash().StringLE()) - - // Block #9: designate new Notary node. - ntr, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, "./testdata/notary1.json")) - require.NoError(t, err) - require.NoError(t, ntr.Accounts[0].Decrypt("one", ntr.Scrypt)) - bc.setNodesByRole(t, true, noderoles.P2PNotary, keys.PublicKeys{ntr.Accounts[0].PrivateKey().PublicKey()}) - t.Logf("Designated Notary node: %s", hex.EncodeToString(ntr.Accounts[0].PrivateKey().PublicKey().Bytes())) - - // Block #10: push verification contract with arguments into the chain. - verifyPath = filepath.Join(prefix, "verify_args", "verification_with_args_contract.go") - _, _, _ = deployContractFromPriv0(t, verifyPath, "VerifyWithArgs", nil, 3) // block #10 - - // Block #11: push NameService contract into the chain. - nsPath := examplesPrefix + "nft-nd-nns/" - nsConfigPath := nsPath + "nns.yml" - _, _, nsHash := deployContractFromPriv0(t, nsPath, nsPath, &nsConfigPath, 4) // block #11 - - // Block #12: transfer funds to committee for futher NS record registration. - transferFundsToCommittee(t, bc) // block #12 - - // Block #13: add `.com` root to NNS. - res, err := invokeContractMethodGeneric(bc, -1, - nsHash, "addRoot", true, "com") // block #13 - require.NoError(t, err) - checkResult(t, res, stackitem.Null{}) - - // Block #14: register `neo.com` via NNS. - res, err = invokeContractMethodGeneric(bc, -1, - nsHash, "register", acc0, "neo.com", priv0ScriptHash) // block #14 - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - require.Equal(t, 1, len(res.Events)) // transfer - tokenID, err := res.Events[0].Item.Value().([]stackitem.Item)[3].TryBytes() - require.NoError(t, err) - t.Logf("NNS token #1 ID (hex): %s", hex.EncodeToString(tokenID)) - - // Block #15: set A record type with priv0 owner via NNS. - res, err = invokeContractMethodGeneric(bc, -1, nsHash, - "setRecord", acc0, "neo.com", int64(nns.A), "1.2.3.4") // block #15 - require.NoError(t, err) - checkResult(t, res, stackitem.Null{}) - - // Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call - script.Reset() - emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "testkey", "newtestvalue") - // Invoke `test_contract.go`: put values to check `findstates` RPC call - emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa", "v1") - emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa10", "v2") - emit.AppCall(script.BinWriter, cHash, "putValue", callflag.All, "aa50", "v3") - txInv = transaction.New(script.Bytes(), 1*native.GASFactor) - txInv.Nonce = getNextNonce() - txInv.ValidUntilBlock = validUntilBlock - txInv.Signers = []transaction.Signer{{Account: priv0ScriptHash}} - require.NoError(t, addNetworkFee(bc, txInv, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txInv)) - b = bc.newBlock(txInv) - require.NoError(t, bc.AddBlock(b)) // block #16 - checkTxHalt(t, bc, txInv.Hash()) - - // Block #17: deploy NeoFS Object contract (NEP11-Divisible). - nfsPath := examplesPrefix + "nft-d/" - nfsConfigPath := nfsPath + "nft.yml" - _, _, nfsHash := deployContractFromPriv0(t, nfsPath, nfsPath, &nfsConfigPath, 5) // block #17 - - // Block #18: mint 1.00 NFSO token by transferring 10 GAS to NFSO contract. - containerID := util.Uint256{1, 2, 3} - objectID := util.Uint256{4, 5, 6} - txGas0toNFS := newNEP17Transfer(gasHash, priv0ScriptHash, nfsHash, 10_0000_0000, containerID.BytesBE(), objectID.BytesBE()) - txGas0toNFS.SystemFee += 4000_0000 - txGas0toNFS.Nonce = getNextNonce() - txGas0toNFS.ValidUntilBlock = validUntilBlock - txGas0toNFS.Signers = []transaction.Signer{ - { - Account: priv0ScriptHash, - Scopes: transaction.CalledByEntry, - }, - } - require.NoError(t, addNetworkFee(bc, txGas0toNFS, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txGas0toNFS)) - b = bc.newBlock(txGas0toNFS) - require.NoError(t, bc.AddBlock(b)) // block #18 - checkTxHalt(t, bc, txGas0toNFS.Hash()) - aer, _ := bc.GetAppExecResults(txGas0toNFS.Hash(), trigger.Application) - require.Equal(t, 2, len(aer[0].Events)) // GAS transfer + NFSO transfer - tokenID, err = aer[0].Events[1].Item.Value().([]stackitem.Item)[3].TryBytes() - require.NoError(t, err) - t.Logf("NFSO token #1 ID (hex): %s", hex.EncodeToString(tokenID)) - - // Block #19: transfer 0.25 NFSO from priv0 to priv1. - script.Reset() - emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv0ScriptHash, priv1ScriptHash, 25, tokenID, nil) - emit.Opcodes(script.BinWriter, opcode.ASSERT) - require.NoError(t, script.Err) - txNFS0to1 := transaction.New(script.Bytes(), 1*native.GASFactor) - txNFS0to1.Nonce = getNextNonce() - txNFS0to1.ValidUntilBlock = validUntilBlock - txNFS0to1.Signers = []transaction.Signer{{Account: priv0ScriptHash, Scopes: transaction.CalledByEntry}} - require.NoError(t, addNetworkFee(bc, txNFS0to1, acc0)) - require.NoError(t, acc0.SignTx(testchain.Network(), txNFS0to1)) - b = bc.newBlock(txNFS0to1) - require.NoError(t, bc.AddBlock(b)) // block #19 - checkTxHalt(t, bc, txNFS0to1.Hash()) - - // Block #20: transfer 1000 GAS to priv1. - txMoveGas, err = testchain.NewTransferFromOwner(bc, gasHash, priv1ScriptHash, int64(fixedn.Fixed8FromInt64(1000)), - getNextNonce(), validUntilBlock) - require.NoError(t, err) - require.NoError(t, bc.AddBlock(bc.newBlock(txMoveGas))) - checkTxHalt(t, bc, txMoveGas.Hash()) // block #20 - - // Block #21: transfer 0.05 NFSO from priv1 back to priv0. - script.Reset() - emit.AppCall(script.BinWriter, nfsHash, "transfer", callflag.All, priv1ScriptHash, priv0.GetScriptHash(), 5, tokenID, nil) - emit.Opcodes(script.BinWriter, opcode.ASSERT) - require.NoError(t, script.Err) - txNFS1to0 := transaction.New(script.Bytes(), 1*native.GASFactor) - txNFS1to0.Nonce = getNextNonce() - txNFS1to0.ValidUntilBlock = validUntilBlock - txNFS1to0.Signers = []transaction.Signer{{Account: priv1ScriptHash, Scopes: transaction.CalledByEntry}} - require.NoError(t, addNetworkFee(bc, txNFS1to0, acc0)) - require.NoError(t, acc1.SignTx(testchain.Network(), txNFS1to0)) - b = bc.newBlock(txNFS1to0) - require.NoError(t, bc.AddBlock(b)) // block #21 - checkTxHalt(t, bc, txNFS1to0.Hash()) - - // Compile contract to test `invokescript` RPC call - invokePath := filepath.Join(prefix, "invoke", "invokescript_contract.go") - invokeCfg := filepath.Join(prefix, "invoke", "invoke.yml") - _, _ = newDeployTx(t, bc, priv0ScriptHash, invokePath, "ContractForInvokescriptTest", &invokeCfg) -} - func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction { return newNEP17TransferWithAssert(sc, from, to, amount, true, additionalArgs...) } @@ -612,13 +209,6 @@ func newNEP17TransferWithAssert(sc, from, to util.Uint160, amount int64, needAss return transaction.New(script, 11000000) } -func newDeployTx(t *testing.T, bc *Blockchain, sender util.Uint160, name, ctrName string, cfgName *string) (*transaction.Transaction, util.Uint160) { - tx, h, avm, err := testchain.NewDeployTx(bc, name, sender, nil, cfgName) - require.NoError(t, err) - t.Logf("contract (%s): \n\tHash: %s\n\tAVM: %s", name, h.StringLE(), base64.StdEncoding.EncodeToString(avm)) - return tx, h -} - func addSigners(sender util.Uint160, txs ...*transaction.Transaction) { for _, tx := range txs { tx.Signers = []transaction.Signer{{ @@ -630,23 +220,6 @@ func addSigners(sender util.Uint160, txs ...*transaction.Transaction) { } } -func addNetworkFee(bc *Blockchain, tx *transaction.Transaction, sender *wallet.Account) error { - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), sender.Contract.Script) - tx.NetworkFee += netFee - size += sizeDelta - for _, cosigner := range tx.Signers { - contract := bc.GetContractState(cosigner.Account) - if contract != nil { - netFee, sizeDelta = fee.Calculate(bc.GetBaseExecFee(), contract.NEF.Script) - tx.NetworkFee += netFee - size += sizeDelta - } - } - tx.NetworkFee += int64(size) * bc.FeePerByte() - return nil -} - // Signer can be either bool or *wallet.Account. // In the first case `true` means sign by committee, `false` means sign by validators. func prepareContractMethodInvokeGeneric(chain *Blockchain, sysfee int64, diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go index 194d79567..e38a8cf96 100644 --- a/pkg/core/native_management_test.go +++ b/pkg/core/native_management_test.go @@ -1,30 +1,33 @@ -package core +package core_test import ( "testing" - "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" ) -type memoryStore struct { - *storage.MemoryStore -} - -func (memoryStore) Close() error { return nil } - func TestManagement_GetNEP17Contracts(t *testing.T) { t.Run("empty chain", func(t *testing.T) { - chain := newTestChain(t) - require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash}, chain.contracts.Management.GetNEP17Contracts()) + bc, validators, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validators, committee) + + require.ElementsMatch(t, []util.Uint160{e.NativeHash(t, nativenames.Neo), + e.NativeHash(t, nativenames.Gas)}, bc.GetNEP17Contracts()) }) - t.Run("test chain", func(t *testing.T) { - chain := newTestChain(t) - initBasicChain(t, chain) - rublesHash, err := chain.GetContractScriptHash(1) - require.NoError(t, err) - require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash, rublesHash}, chain.contracts.Management.GetNEP17Contracts()) + t.Run("basic chain", func(t *testing.T) { + bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true // `initBasicChain` requires Notary enabled + }) + e := neotest.NewExecutor(t, bc, validators, committee) + initBasicChain(t, e) + + require.ElementsMatch(t, []util.Uint160{e.NativeHash(t, nativenames.Neo), + e.NativeHash(t, nativenames.Gas), e.ContractHash(t, 1)}, bc.GetNEP17Contracts()) }) } diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 8e06d6571..a980e91fd 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -10,11 +10,6 @@ import ( "github.com/stretchr/testify/require" ) -func transferFundsToCommittee(t *testing.T, chain *Blockchain) { - transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(), - chain.contracts.GAS.Hash, 1000_00000000) -} - func TestFeePerByte(t *testing.T) { chain := newTestChain(t) diff --git a/pkg/core/stateroot_test.go b/pkg/core/stateroot_test.go index 29777f457..0b27a92c3 100644 --- a/pkg/core/stateroot_test.go +++ b/pkg/core/stateroot_test.go @@ -133,6 +133,12 @@ func TestStateRoot(t *testing.T) { require.Equal(t, h, r.Witness[0].ScriptHash()) } +type memoryStore struct { + *storage.MemoryStore +} + +func (memoryStore) Close() error { return nil } + func TestStateRootInitNonZeroHeight(t *testing.T) { st := memoryStore{storage.NewMemoryStore()} h, pubs, accs := newMajorityMultisigWithGAS(t, 2) diff --git a/pkg/core/statesync_test.go b/pkg/core/statesync_test.go index 2be3b01a9..4162c921b 100644 --- a/pkg/core/statesync_test.go +++ b/pkg/core/statesync_test.go @@ -1,13 +1,14 @@ -package core +package core_test import ( "testing" - "time" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/mpt" "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util/slice" "github.com/stretchr/testify/require" @@ -18,40 +19,41 @@ func TestStateSyncModule_Init(t *testing.T) { stateSyncInterval = 2 maxTraceable uint32 = 3 ) - spoutCfg := func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true - c.ProtocolConfiguration.P2PStateExchangeExtensions = true - c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval - c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable + spoutCfg := func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + c.P2PStateExchangeExtensions = true + c.StateSyncInterval = stateSyncInterval + c.MaxTraceableBlocks = maxTraceable } - bcSpout := newTestChainWithCustomCfg(t, spoutCfg) + bcSpout, validators, committee := chain.NewMultiWithCustomConfig(t, spoutCfg) + e := neotest.NewExecutor(t, bcSpout, validators, committee) for i := 0; i <= 2*stateSyncInterval+int(maxTraceable)+1; i++ { - require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock())) + e.AddNewBlock(t) } - boltCfg := func(c *config.Config) { + boltCfg := func(c *config.ProtocolConfiguration) { spoutCfg(c) - c.ProtocolConfiguration.KeepOnlyLatestState = true - c.ProtocolConfiguration.RemoveUntraceableBlocks = true + c.KeepOnlyLatestState = true + c.RemoveUntraceableBlocks = true } t.Run("error: module disabled by config", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, func(c *config.Config) { + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { boltCfg(c) - c.ProtocolConfiguration.RemoveUntraceableBlocks = false + c.RemoveUntraceableBlocks = false }) module := bcBolt.GetStateSyncModule() require.Error(t, module.Init(bcSpout.BlockHeight())) // module inactive (non-archival node) }) t.Run("inactive: spout chain is too low to start state sync process", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) module := bcBolt.GetStateSyncModule() require.NoError(t, module.Init(uint32(2*stateSyncInterval-1))) require.False(t, module.IsActive()) }) t.Run("inactive: bolt chain height is close enough to spout chain height", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) for i := 1; i < int(bcSpout.BlockHeight())-stateSyncInterval; i++ { b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) require.NoError(t, err) @@ -63,15 +65,16 @@ func TestStateSyncModule_Init(t *testing.T) { }) t.Run("error: bolt chain is too low to start state sync process", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) - require.NoError(t, bcBolt.AddBlock(bcBolt.newBlock())) + bcBolt, validatorsBolt, committeeBolt := chain.NewMultiWithCustomConfig(t, boltCfg) + eBolt := neotest.NewExecutor(t, bcBolt, validatorsBolt, committeeBolt) + eBolt.AddNewBlock(t) module := bcBolt.GetStateSyncModule() require.Error(t, module.Init(uint32(3*stateSyncInterval))) }) t.Run("initialized: no previous state sync point", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) module := bcBolt.GetStateSyncModule() require.NoError(t, module.Init(bcSpout.BlockHeight())) @@ -82,7 +85,7 @@ func TestStateSyncModule_Init(t *testing.T) { }) t.Run("error: outdated state sync point in the storage", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) module := bcBolt.GetStateSyncModule() require.NoError(t, module.Init(bcSpout.BlockHeight())) @@ -91,7 +94,7 @@ func TestStateSyncModule_Init(t *testing.T) { }) t.Run("initialized: valid previous state sync point in the storage", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, _, _ := chain.NewMultiWithCustomConfig(t, boltCfg) module := bcBolt.GetStateSyncModule() require.NoError(t, module.Init(bcSpout.BlockHeight())) @@ -104,7 +107,8 @@ func TestStateSyncModule_Init(t *testing.T) { }) t.Run("initialization from headers/blocks/mpt synced stages", func(t *testing.T) { - bcBolt := newTestChainWithCustomCfg(t, boltCfg) + bcBolt, validatorsBolt, committeeBolt := chain.NewMultiWithCustomConfig(t, boltCfg) + eBolt := neotest.NewExecutor(t, bcBolt, validatorsBolt, committeeBolt) module := bcBolt.GetStateSyncModule() require.NoError(t, module.Init(bcSpout.BlockHeight())) @@ -263,7 +267,7 @@ func TestStateSyncModule_Init(t *testing.T) { // add one more block to the restored chain and start new module: the module should recognise state sync is completed // and regular blocks processing was started - require.NoError(t, bcBolt.AddBlock(bcBolt.newBlock())) + eBolt.AddNewBlock(t) module = bcBolt.GetStateSyncModule() require.NoError(t, module.Init(bcSpout.BlockHeight())) require.False(t, module.IsActive()) @@ -282,27 +286,31 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { maxTraceable uint32 = 6 stateSyncPoint = 20 ) - spoutCfg := func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true - c.ProtocolConfiguration.P2PStateExchangeExtensions = true - c.ProtocolConfiguration.StateSyncInterval = stateSyncInterval - c.ProtocolConfiguration.MaxTraceableBlocks = maxTraceable + spoutCfg := func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + c.P2PStateExchangeExtensions = true + c.StateSyncInterval = stateSyncInterval + c.MaxTraceableBlocks = maxTraceable + c.P2PSigExtensions = true // `initBasicChain` assumes Notary is enabled. } - bcSpout := newTestChainWithCustomCfg(t, spoutCfg) - initBasicChain(t, bcSpout) + bcSpoutStore := storage.NewMemoryStore() + bcSpout, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, spoutCfg, bcSpoutStore, false) + go bcSpout.Run() // Will close it manually at the end. + e := neotest.NewExecutor(t, bcSpout, validators, committee) + initBasicChain(t, e) // make spout chain higher that latest state sync point (add several blocks up to stateSyncPoint+2) - require.NoError(t, bcSpout.AddBlock(bcSpout.newBlock())) + e.AddNewBlock(t) require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight())) - boltCfg := func(c *config.Config) { + boltCfg := func(c *config.ProtocolConfiguration) { spoutCfg(c) - c.ProtocolConfiguration.KeepOnlyLatestState = true - c.ProtocolConfiguration.RemoveUntraceableBlocks = true + c.KeepOnlyLatestState = true + c.RemoveUntraceableBlocks = true } - bcBoltStore := memoryStore{storage.NewMemoryStore()} - bcBolt := initTestChain(t, bcBoltStore, boltCfg) - go bcBolt.Run() + bcBoltStore := storage.NewMemoryStore() + bcBolt, _, _ := chain.NewMultiWithCustomConfigAndStore(t, boltCfg, bcBoltStore, false) + go bcBolt.Run() // Will close it manually at the end. module := bcBolt.GetStateSyncModule() t.Run("error: add headers before initialisation", func(t *testing.T) { @@ -421,9 +429,9 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { require.Equal(t, bcSpout.BlockHeight(), bcBolt.BlockHeight()) // compare storage states - fetchStorage := func(bc *Blockchain) []storage.KeyValue { + fetchStorage := func(ps storage.Store, storagePrefix byte) []storage.KeyValue { var kv []storage.KeyValue - bc.dao.Store.Seek(storage.SeekRange{Prefix: []byte{byte(bc.dao.Version.StoragePrefix)}}, func(k, v []byte) bool { + ps.Seek(storage.SeekRange{Prefix: []byte{storagePrefix}}, func(k, v []byte) bool { key := slice.Copy(k) value := slice.Copy(v) if key[0] == byte(storage.STTempStorage) { @@ -437,25 +445,19 @@ func TestStateSyncModule_RestoreBasicChain(t *testing.T) { }) return kv } - expected := fetchStorage(bcSpout) - actual := fetchStorage(bcBolt) + // Both blockchains are running, so we need to wait until recent changes will be persisted + // to the underlying backend store. Close blockchains to ensure persist was completed. + bcSpout.Close() + bcBolt.Close() + expected := fetchStorage(bcSpoutStore, byte(storage.STStorage)) + actual := fetchStorage(bcBoltStore, byte(storage.STTempStorage)) require.ElementsMatch(t, expected, actual) // no temp items should be left - require.Eventually(t, func() bool { - var haveItems bool - bcBolt.dao.Store.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool { - haveItems = true - return false - }) - return !haveItems - }, time.Second*5, time.Millisecond*100) - bcBolt.Close() - - // Check restoring with new prefix. - bcBolt = initTestChain(t, bcBoltStore, boltCfg) - go bcBolt.Run() - defer bcBolt.Close() - require.Equal(t, storage.STTempStorage, bcBolt.dao.Version.StoragePrefix) - require.Equal(t, storage.STTempStorage, bcBolt.persistent.Version.StoragePrefix) + var haveItems bool + bcBoltStore.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool { + haveItems = true + return false + }) + require.False(t, haveItems) } diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index 750d72288..c5e820eac 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -63,6 +63,13 @@ func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 { return h } +// ContractHash returns contract hash by ID. +func (e *Executor) ContractHash(t *testing.T, id int32) util.Uint160 { + h, err := e.Chain.GetContractScriptHash(id) + require.NoError(t, err) + return h +} + // NativeID returns native contract ID by name. func (e *Executor) NativeID(t *testing.T, name string) int32 { h := e.NativeHash(t, name) @@ -132,7 +139,15 @@ func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer // data is an optional argument to `_deploy`. // Returns hash of the deploy transaction. func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) util.Uint256 { - tx := e.NewDeployTx(t, e.Chain, c, data) + return e.DeployContractBy(t, e.Validator, c, data) +} + +// DeployContractBy compiles and deploys contract to bc using provided signer. +// It also checks that precalculated contract hash matches the actual one. +// data is an optional argument to `_deploy`. +// Returns hash of the deploy transaction. +func (e *Executor) DeployContractBy(t *testing.T, signer Signer, c *Contract, data interface{}) util.Uint256 { + tx := NewDeployTxBy(t, e.Chain, signer, c, data) e.AddNewBlock(t, tx) e.CheckHalt(t, tx.Hash()) @@ -148,8 +163,8 @@ func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) u return tx.Hash() } -// DeployContractCheckFAULT compiles and deploys contract to bc. It checks that deploy -// transaction FAULTed with the specified error. +// DeployContractCheckFAULT compiles and deploys contract to bc using validator +// account. It checks that deploy transaction FAULTed with the specified error. func (e *Executor) DeployContractCheckFAULT(t *testing.T, c *Contract, data interface{}, errMessage string) { tx := e.NewDeployTx(t, e.Chain, c, data) e.AddNewBlock(t, tx) @@ -221,8 +236,19 @@ func (e *Executor) CheckGASBalance(t *testing.T, acc util.Uint160, expected *big require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String())) } +// EnsureGASBalance ensures that provided account owns amount of GAS that satisfies provided condition. +func (e *Executor) EnsureGASBalance(t *testing.T, acc util.Uint160, isOk func(balance *big.Int) bool) { + actual := e.Chain.GetUtilityTokenBalance(acc) + require.True(t, isOk(actual), fmt.Errorf("invalid GAS balance: got %s, condition is not satisfied", actual.String())) +} + // NewDeployTx returns new deployment tx for contract signed by committee. func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction { + return NewDeployTxBy(t, bc, e.Validator, c, data) +} + +// NewDeployTxBy returns new deployment tx for contract signed by the specified signer. +func NewDeployTxBy(t *testing.T, bc blockchainer.Blockchainer, signer Signer, c *Contract, data interface{}) *transaction.Transaction { rawManifest, err := json.Marshal(c.Manifest) require.NoError(t, err) @@ -237,11 +263,11 @@ func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Co tx.Nonce = Nonce() tx.ValidUntilBlock = bc.BlockHeight() + 1 tx.Signers = []transaction.Signer{{ - Account: e.Validator.ScriptHash(), + Account: signer.ScriptHash(), Scopes: transaction.Global, }} - addNetworkFee(bc, tx, e.Validator) - require.NoError(t, e.Validator.SignTx(netmode.UnitTestNet, tx)) + addNetworkFee(bc, tx, signer) + require.NoError(t, signer.SignTx(netmode.UnitTestNet, tx)) return tx } diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go index 7767b02c8..2255b4249 100644 --- a/pkg/neotest/client.go +++ b/pkg/neotest/client.go @@ -19,6 +19,15 @@ type ContractInvoker struct { Signers []Signer } +// NewInvoker creates new ContractInvoker for contract with hash h and specified signers. +func (e *Executor) NewInvoker(h util.Uint160, signers ...Signer) *ContractInvoker { + return &ContractInvoker{ + Executor: e, + Hash: h, + Signers: signers, + } +} + // CommitteeInvoker creates new ContractInvoker for contract with hash h and committee multisignature signer. func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker { return &ContractInvoker{ diff --git a/pkg/rpc/server/testdata/verify/verification_contract.yml b/pkg/rpc/server/testdata/verify/verification_contract.yml new file mode 100644 index 000000000..5d18c74a7 --- /dev/null +++ b/pkg/rpc/server/testdata/verify/verification_contract.yml @@ -0,0 +1,2 @@ +name: "Verify" +sourceurl: https://github.com/nspcc-dev/neo-go/ diff --git a/pkg/rpc/server/testdata/verify_args/verification_with_args_contract.yml b/pkg/rpc/server/testdata/verify_args/verification_with_args_contract.yml new file mode 100644 index 000000000..2c70e5a7c --- /dev/null +++ b/pkg/rpc/server/testdata/verify_args/verification_with_args_contract.yml @@ -0,0 +1,2 @@ +name: "Verify with args" +sourceurl: https://github.com/nspcc-dev/neo-go/ From 889a7ec3780d17cd05a283f3bd218a23c30b061c Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 11 Mar 2022 13:22:36 +0300 Subject: [PATCH 05/15] rpc: regenerate basic chain and adjust RPC server tests --- pkg/core/basic_chain_test.go | 10 ++-- pkg/rpc/server/server_test.go | 71 +++++++++++++++++-------- pkg/rpc/server/testdata/testblocks.acc | Bin 33175 -> 33482 bytes 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/pkg/core/basic_chain_test.go b/pkg/core/basic_chain_test.go index d831c0b57..d9e8cbfc3 100644 --- a/pkg/core/basic_chain_test.go +++ b/pkg/core/basic_chain_test.go @@ -214,12 +214,12 @@ func initBasicChain(t *testing.T, e *neotest.Executor) { nsPriv0Invoker.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4") // block #15 // Block #16: invoke `test_contract.go`: put new value with the same key to check `getstate` RPC call - txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") + txPutNewValue := rublPriv0Invoker.PrepareInvoke(t, "putValue", "testkey", "newtestvalue") // tx1 // Invoke `test_contract.go`: put values to check `findstates` RPC call. - txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") - txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") - txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") - e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16 + txPut1 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa", "v1") // tx2 + txPut2 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa10", "v2") // tx3 + txPut3 := rublPriv0Invoker.PrepareInvoke(t, "putValue", "aa50", "v3") // tx4 + e.AddNewBlock(t, txPutNewValue, txPut1, txPut2, txPut3) // block #16 e.CheckHalt(t, txPutNewValue.Hash(), stackitem.NewBool(true)) e.CheckHalt(t, txPut1.Hash(), stackitem.NewBool(true)) e.CheckHalt(t, txPut2.Hash(), stackitem.NewBool(true)) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 71eab16d8..96d79ebf9 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -61,17 +61,17 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const genesisBlockHash = "a4ae00f6ac7496cac14e709fbf8b8ecb4c9831d8a6ee396056af9350fcf22671" -const testContractHash = "1ab08f5508edafa6f28e3db3227442a9e70aac52" -const deploymentTxHash = "017c9edb217477aeb3e0c35462361209fdb7bf104dc8e285e2385af8713926b4" +const genesisBlockHash = "f42e2ae74bbea6aa1789fdc4efa35ad55b04335442637c091eafb5b0e779dae7" +const testContractHash = "2db7d679c538ace5f00495c9e9d8ea95f1e0f5a5" +const deploymentTxHash = "496bccb5cb0a008ef9b7a32c459e508ef24fbb0830f82bac9162afa4ca804839" const ( - verifyContractHash = "7deef31e5c616e157cdf02a5446f36d0a4eead52" + verifyContractHash = "06ed5314c2e4cb103029a60b86d46afa2fb8f67c" verifyContractAVM = "VwIAQS1RCDBwDBTunqIsJ+NL0BSPxBCOCPdOj1BIskrZMCQE2zBxaBPOStkoJATbKGlK2SgkBNsol0A=" - verifyWithArgsContractHash = "6df009754ce475a6a5730c9e488f80e8e47bc1f1" - nnsContractHash = "1a7530a4c6cfdd40ffed40775aa5453febab24c0" + verifyWithArgsContractHash = "0dce75f52adb1a4c5c6eaa6a34eb26db2e5b3781" + nnsContractHash = "ee92563903e4efd53565784080b2dbdc5c37e21f" nnsToken1ID = "6e656f2e636f6d" - nfsoContractHash = "aaf8913c501e25c42877e79f04cb7c2c1ab47e57" + nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f" nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486" invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA" ) @@ -279,7 +279,7 @@ var rpcTestCases = map[string][]rpcTestCase{ result: func(e *executor) interface{} { return &map[string]interface{}{ "name": "neo.com", - "expiration": "HrL+G4YB", + "expiration": "lhbLRl0B", } }, }, @@ -882,7 +882,7 @@ var rpcTestCases = map[string][]rpcTestCase{ name: "positive, with notifications", params: `["` + nnsContractHash + `", "transfer", [{"type":"Hash160", "value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"},{"type":"String", "value":"neo.com"},{"type":"Any", "value":null}],["0xb248508f4ef7088e10c48f14d04be3272ca29eee"]]`, result: func(e *executor) interface{} { - script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0x0b, 0x13, 0xc0, 0x1f, 0x0c, 0x08, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x0c, 0x14, 0xc0, 0x24, 0xab, 0xeb, 0x3f, 0x45, 0xa5, 0x5a, 0x77, 0x40, 0xed, 0xff, 0x40, 0xdd, 0xcf, 0xc6, 0xa4, 0x30, 0x75, 0x1a, 0x41, 0x62, 0x7d, 0x5b, 0x52} + script := []byte{0x0b, 0x0c, 0x07, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x0c, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb, 0x13, 0xc0, 0x1f, 0xc, 0x8, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52} return &result.Invoke{ State: "HALT", GasConsumed: 32167260, @@ -915,7 +915,7 @@ var rpcTestCases = map[string][]rpcTestCase{ chg := []storage.Operation{{ State: "Changed", Key: []byte{0xfa, 0xff, 0xff, 0xff, 0xb}, - Value: []byte{0xe8, 0x80, 0x64, 0xcb, 0x53, 0x79, 0x12}, + Value: []byte{0x1e, 0xb, 0xca, 0xeb, 0x53, 0x79, 0x12}, }, { State: "Added", Key: []byte{0xfb, 0xff, 0xff, 0xff, 0x14, 0xd6, 0x24, 0x87, 0x12, 0xff, 0x97, 0x22, 0x80, 0xa0, 0xae, 0xf5, 0x24, 0x1c, 0x96, 0x4d, 0x63, 0x78, 0x29, 0xcd, 0xb}, @@ -927,7 +927,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { State: "Changed", Key: []byte{0xfa, 0xff, 0xff, 0xff, 0x14, 0xee, 0x9e, 0xa2, 0x2c, 0x27, 0xe3, 0x4b, 0xd0, 0x14, 0x8f, 0xc4, 0x10, 0x8e, 0x8, 0xf7, 0x4e, 0x8f, 0x50, 0x48, 0xb2}, - Value: []byte{0x41, 0x01, 0x21, 0x05, 0x9e, 0x0b, 0x0b, 0x18, 0x0b}, + Value: []byte{0x41, 0x01, 0x21, 0x05, 0xf6, 0x99, 0x28, 0x2d, 0xb}, }} // Can be returned in any order. assert.ElementsMatch(t, chg, res.Diagnostics.Changes) @@ -937,7 +937,7 @@ var rpcTestCases = map[string][]rpcTestCase{ name: "positive, verbose", params: `["` + nnsContractHash + `", "resolve", [{"type":"String", "value":"neo.com"},{"type":"Integer","value":1}], [], true]`, result: func(e *executor) interface{} { - script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0xc0, 0x24, 0xab, 0xeb, 0x3f, 0x45, 0xa5, 0x5a, 0x77, 0x40, 0xed, 0xff, 0x40, 0xdd, 0xcf, 0xc6, 0xa4, 0x30, 0x75, 0x1a, 0x41, 0x62, 0x7d, 0x5b, 0x52} + script := []byte{0x11, 0xc, 0x7, 0x6e, 0x65, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x12, 0xc0, 0x1f, 0xc, 0x7, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0xc, 0x14, 0x1f, 0xe2, 0x37, 0x5c, 0xdc, 0xdb, 0xb2, 0x80, 0x40, 0x78, 0x65, 0x35, 0xd5, 0xef, 0xe4, 0x3, 0x39, 0x56, 0x92, 0xee, 0x41, 0x62, 0x7d, 0x5b, 0x52} stdHash, _ := e.chain.GetNativeContractScriptHash(nativenames.StdLib) cryptoHash, _ := e.chain.GetNativeContractScriptHash(nativenames.CryptoLib) return &result.Invoke{ @@ -1205,12 +1205,12 @@ var rpcTestCases = map[string][]rpcTestCase{ "sendrawtransaction": { { name: "positive", - params: `["ADQSAADA2KcAAAAAABDiEgAAAAAAgBYAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsgEAYBDAAwDodkgXAAAADBQRJlu0FyUAQb4E6PokDjj1fB5WmwwU7p6iLCfjS9AUj8QQjgj3To9QSLIUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQFCDEBRp0p08GFA2rYC/Xrol8DIhXEMfVMbUJEYer1RqZSatmTjUJE9fnZtDGkQEX/zQ7yOhbnIPAZIrllUTuUBskhUKAwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CQVbnsyc="]`, + params: `["ABsAAACWP5gAAAAAAEDaEgAAAAAAFgAAAAHunqIsJ+NL0BSPxBCOCPdOj1BIsoAAXgsDAOh2SBcAAAAMFBEmW7QXJQBBvgTo+iQOOPV8HlabDBTunqIsJ+NL0BSPxBCOCPdOj1BIshTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1IBQgxAOv87rSn7OV7Y/wuVE58QaSz0o0wv37hWY08RZFP2kYYgSPvemZiT69wf6QeAUTABJ1JosxgIUory9vXv0kkpXSgMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwkFW57Mn"]`, result: func(e *executor) interface{} { return &result.RelayResult{} }, check: func(t *testing.T, e *executor, inv interface{}) { res, ok := inv.(*result.RelayResult) require.True(t, ok) - expectedHash := "8ea251d812fbbdecaebfc164fb6afbd78b7db94f7dacb69421cd5d4e364522d2" + expectedHash := "acc3e13102c211068d06ff64034d6f7e2d4db00c1703d0dec8afa73560664fe1" assert.Equal(t, expectedHash, res.Hash.StringLE()) }, }, @@ -1945,12 +1945,12 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoError(t, json.Unmarshal(res, actual)) checkNep17TransfersAux(t, e, actual, sent, rcvd) } - t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{14, 15, 16, 17}, []int{3, 4}) }) + t.Run("time frame only", func(t *testing.T) { testNEP17T(t, 4, 5, 0, 0, []int{17, 18, 19, 20}, []int{3, 4}) }) t.Run("no res", func(t *testing.T) { testNEP17T(t, 100, 100, 0, 0, []int{}, []int{}) }) - t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{11, 12}, []int{2}) }) - t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{14}, []int{3}) }) - t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{13, 14}, []int{3}) }) - t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{15, 16}, []int{4}) }) + t.Run("limit", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 0, []int{14, 15}, []int{2}) }) + t.Run("limit 2", func(t *testing.T) { testNEP17T(t, 4, 5, 2, 0, []int{17}, []int{3}) }) + t.Run("limit with page", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 1, []int{16, 17}, []int{3}) }) + t.Run("limit with page 2", func(t *testing.T) { testNEP17T(t, 1, 7, 3, 2, []int{18, 19}, []int{4}) }) }) } @@ -2086,7 +2086,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "46748035310", + Amount: "47102293830", LastUpdated: 19, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), @@ -2198,7 +2198,7 @@ func checkNep11TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc } func checkNep17Transfers(t *testing.T, e *executor, acc interface{}) { - checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}) + checkNep17TransfersAux(t, e, acc, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}) } func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rcvd []int) { @@ -2224,8 +2224,11 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc blockPutNewTestValue, err := e.chain.GetBlock(e.chain.GetHeaderHash(16)) // invoke `put` method of `test_contract.go` with `testkey`, `newtestvalue` args require.NoError(t, err) - require.Equal(t, 1, len(blockPutNewTestValue.Transactions)) + require.Equal(t, 4, len(blockPutNewTestValue.Transactions)) txPutNewTestValue := blockPutNewTestValue.Transactions[0] + txPutValue1 := blockPutNewTestValue.Transactions[1] // invoke `put` method of `test_contract.go` with `aa`, `v1` args + txPutValue2 := blockPutNewTestValue.Transactions[2] // invoke `put` method of `test_contract.go` with `aa10`, `v2` args + txPutValue3 := blockPutNewTestValue.Transactions[3] // invoke `put` method of `test_contract.go` with `aa50`, `v3` args blockSetRecord, err := e.chain.GetBlock(e.chain.GetHeaderHash(15)) // add type A record to `neo.com` domain via NNS require.NoError(t, err) @@ -2336,6 +2339,30 @@ func checkNep17TransfersAux(t *testing.T, e *executor, acc interface{}, sent, rc Index: 17, TxHash: blockDeploy5.Hash(), }, + { + Timestamp: blockPutNewTestValue.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txPutValue3.SystemFee + txPutValue3.NetworkFee).String(), + Index: 16, + TxHash: blockPutNewTestValue.Hash(), + }, + { + Timestamp: blockPutNewTestValue.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txPutValue2.SystemFee + txPutValue2.NetworkFee).String(), + Index: 16, + TxHash: blockPutNewTestValue.Hash(), + }, + { + Timestamp: blockPutNewTestValue.Timestamp, + Asset: e.chain.UtilityTokenHash(), + Address: "", // burn + Amount: big.NewInt(txPutValue1.SystemFee + txPutValue1.NetworkFee).String(), + Index: 16, + TxHash: blockPutNewTestValue.Hash(), + }, { Timestamp: blockPutNewTestValue.Timestamp, Asset: e.chain.UtilityTokenHash(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index f004f9cf5d6862b9662098837dc7f761b9a6a9c1..243844ae29b1d1866aa7cd89a61c83660f1ffa80 100644 GIT binary patch delta 11509 zcmc(FbyQSe+crbT(A^CqATe~$&?4Qbbax{SigX=HLQqm#5Jf^#2|++oK|;C&>E;_o zfA4zUXT9(9{qwE$oj>Nx*=x@1``*``*WOuCr~^+>G00I+Q1VhSIN2W_Y*ateLV;Ut za+|vrEEjWJ?^mXs`aJ4;qqW4}rjHl9NxiF|stpON6RBv${NXAb+of4fDfE>`u}CSy zb-JO$k7rl?>EuF}&AAZTC1kib5mtevR9pIH^aCFD7hB5*FIXcZPq=pt!LaqDA{VhV zXO4;{e?GK66gLc~xiy<>@e1@&p$1115(fguFY$^KxmBG%jwk1bAQtHzg~Fjdiuwz` zW1Sfl;lC2VFc$(HwzSPBI2?*e7IrsEAEHEzGW^M8QC$PqEB9S`-c>ElM+F-tG0#r8 z+xBi27~F^Kt`lf56LlQeicIN>NJ zc&JoAjxc}}RtkU-RVEDd2@H$Xo{J{?sH|b4G`UfkUzT`%!=y1c7#hh{@BdWeQj%0z zNu3C4l-2+Av@DRd{?qE?_UF4T>)*!0@wV;zu?(fbu>Px8=-Z|-F@~`=HZnmbT`^wY zs~2PAFwG|d_ZXW4iqGZ5m_K#rt=6;ajSK$1v>CJXSpN7P@5t#MYFKWnD&;pY>?q}v zJ@!&5Xp11mXiiL9J-Tm%PU5Qe!7H!`8hrvs2#Lz|5jIU;6}d zIvL70Rkb8-^p0>^qz^{X0_w}!(ilxD@^s#+;#T|h!cinHLn-P#t?~*ah3ddC!D6=T zSYHZ$o}VAdlg?>LH{)O5CvK=-X&;alzdxz*U7fw7#@J?xTpy3?>?}cfkZ!3!6oXz) z%j+J~*xPZXVm~m#jrzePRI$=_@ z+jA95SyB4o{u&qWp}0!;Kyj^AY8Ce>KX!}eev;O6MV_K}XGtiypBo3A@5SAE!mZ`} zy-)!V30>GvLbRdM!mTUk`bXV1EvI4%V%Uo&Oysz+d29GV{ggu63d{{joHmgRBe9=N z56FmLL{(qO_gN1z#6{>vl9=g%VKiy#FWUwN$Sn?bwTF5eNNBvt zEUq+TWc$(s3yQ{Yl_nBQ^G8X34R~3z!?f!Dv-pY(JPio*JXZIir@7M^g|+;iSNU|r zi$c0}_0N0_pO&sU`JJ&V^X4NbYi|`527df4(!dch zB+Qb<)fv5wHOOXD=nMV#%8-S%MP3}s&m1BBqn}^FbEXK!$5P5wRGLya=X(LKjwy-- zcO?@|hwbe5>GAI>e9V>_VHF(aVvy{n}9CH;~_rE`&Hw9tQ zlKN6KUGysT<@!Ahe!hC)vgr8BeKL_xpHh)~uLBJGjYC+=^S(Si#>4rQcYSI5#+bzw z)#8C1Mj^#cfGqUk!>vP_OABWfq4L$)5}eq=@{Ca^hLzkmND`EJq_Eu{i7q-~UF^1H zkn7{2PlCLoA<+ee10|&F<|Ri9zcV(cH~KTs_g1RJJ_b&{y9>7pG1URdfnmo*B@SbG z1!Yz?pWz3L*?Ty8yr3@%A)Al1VulxA);y0FO&xgLCRVfH$A zoybQ6faneqo{qop?BVl*abS;qPKN6id{yCdzb5tK8ThHM%KMVmDb<7Tn8XsiFuZ#E zUj0*v^NF>2bA^WpW*G^Fc1Pjc(!=lUQ0C3X!qGR~<0eTcQG0K*2m;4Euw zLcEtVKL@z&jOK@=1+QFR`QJ;fQ0sm9y5oe_DiNrBRJ}sFps!ZKGGjVU{1SVg{S~Fx z(!3U_BIafx81~AKOVj+>Fs0LW*t?;f*zRC8Ki0EX_zZih-S;Z3v)Lnr8P78)(Co-H z$E@s=#P)ULl7-B?XxPf)dDwbCm$4z>$szeCB9CY(vQ8h`HR&tJM zv(<4?@o7BJQ1=!D;|Qdiqo5S?>Rv2c0)-yc90!=mgZV8Cq)@s9g__9U4CmL*GVd<0 zV%qh(*oxy+YBV?J7T%ZfIo+*}`W<@&1Z>|qth-74w`hogjE3Na3BCXvbw%$+sAS_y zT1z|k+I|mQ{i1573D5hh98*36%TF=+J2{MUriH2a`iurYE>HJMxp2?B$C3@#Dv*L< zu0HFi;!m&nWE(Ha3RLzgo#AZ3wCV-QZWPxXWo4fI`rAj_D=hVUM)_@YJD$1lmhXID znOtjGZj>ba;uOiUg}|^CDSZdT$U6oS7N>9& z)-Facx}%$;518yfI!+B9Yda1))Pzh|5&Odli^+Sm72r5BqoiMI3tY7JYO7tdv#si< z;;?B5BP>E>-`>-9hXdD;5om&c-FZCuT1zjHx_H|lAa?2deNVSzW<0DZfL2RJIy3siw&GW-!_es_vv;4Hy> zAq5NvJa!!)b|)Vu>Rb`Z!I;IVrqas>(x^(Zf<>c!ag6;+R6_Q%f)n&2u(d$e?3F`_ z$S{nJz%XI56p7^ehd;iK`H!y~;sLpI(*HRU|46V{VFh2XrL=`k@L#|1DDIKN6Zy(V zmRMlun`Bt?Exnv23HPqq>K0oo{l*(S59PNsYePMuVnyC`LbQCMDeOq~Z_kxDIcPX*@1?Ag#>Yp#q`0SVm95*_g@X@W{ zZT>KLl#fJVl)B@gV$I>thD66cyr;V+Yt-++u&&`|LT2?&WvhKnr^jb1=sl#yYmzIH z2fsYV+kGJlP2ZSIS$bSg4r}SAOgKa8Wf!ELB$+1awcIHf_T^MGF=z$D^gnz1?@KijVt)ZIzHk`^Jt;gKNTPxxv77ztUE=O71}HyxABR)?R<$}X zd9z-PLT+=~Kx@z@rBsf_<1K5|o$Mjj@!3Dm_+k5s($0pSEgYxGn)KW5T9hXy_JCEgtOi>o8E>@<=DmG%Bk0tSGv^`rW*=PE~*3` z({{WWz0p5PHv0Gd=vYy7wo+D-bFx*V?jPx$97KTgGWd` zx0#B-3+=tIJD+)b3jLVo=}MP$I7)D%14v`?6_pJ?gJA{+X~(d&q|cFl$(BCWR}^Aa z3f}u~%bI2Qn}1jN2=UlheR0M$>T-Mn%kaMAyHP=m`|XRrmq1xGr}tj-#^@FjJ6r@> zUHs)p%=r09k#}q)c8K1_2S{Y~K_=1`SK)xMvjbm4n^Pe$N(Dt)=SDnHQ`saRA)ea1A;^6De; zQ)}-M z>yVw0WXqZPe67`FIzlipP@L6f4GcT;_tIpnDE&n|LazuYMC4Kt`h zXm|?9rm9LfDS{wRBHWA_yWA&jx7YEj6Y~a&bxY5PWH)5qsnDREhGJp8r!~gDgLw-9 z^=-3dx5S8C`fTnJnFe42U!^ zv7;$}gK7npT$6S*2;hBTVJF0)NbYnAhK;`n()9d3SXXrMdJuHbbDfunKLevyUBJ<3 zOZ7GEw(c8lFnk^x%*glHo8#)6eYj`Nle?a#AD|S5HnGqT+JlG{NDsTh1|f>V|8MGq zA@%uxq7E1qMbJvXD)v>TNwV->v~e4)x=rO#xpo(ichKXLj`0EJeC*_~`L>;`IJtNU z?+EAzSdlwE>)qvn@_RqvGcGgSrvUuUcXhMd<^ypxT0gFlDl!7aHayUkSC8hg1+hQQ z(V#{d;%=0N=6^?*)vzSxa-^%WW-ae}egS*(c7L?+?7=Ws-FJPo(pPj^X-I!@;DUt3 zb@rxN%F_tL#f;@WxO~e)j^@tn>a`PGE@DjFzN%e6VAc;49+sFFeBVPL^N~R>sH2AW zYyT8;mw}84BYnLO7#0e_#;G7A$gsaV+)5j_6p#aNO4c0X?wBPFQ(b2H_y}$Q;5||4 zsLpGc{WZVfHQ2cVu7S!u>yuure1f9*V7Czr8$&cADg#fUm&71KL9L#}-&v@Qc;(yT zk42b%WSqO_6DKSdb6EOyDVehd>gQC}j z8NBliu3R*=m10#E567ie^QWVYxv8Ho+eUUb&5k(WtQNi*i4cpZr<>|rB*2kejI-eY zkdR*RLc)Uen*(~J(vF_t>6|0VT zvfz8MR-u7Xa!>G@1)&6AK*V6!SFA4bX*86_m)+3Lqi0#%?mavZN}0hQ zVYyT9?&M&ufa`p`6juxKE^H)ATV!OT!cMOCbWx-NQo*oqt5)ZMTXu#KIGhbXvI;JT z-*pR3bhBKLpMuV2gGNiLIq|uIe{SjT<%}zF^DOtqv6AyK-Z$xd6U@Nm5D>qEgfxU7 zj0?ezMJ^B?5>iB!ewELF2h{TS0Fh!I2oi-1cq(9pBvDd@;lRFFCa@t83CxS@0jm<} zz+DkP;6iX05Ejh>#00&7A`t+XhZ+N$Lg|1r)Ef|#Xh0Ax#6XK64EQQ+3Y=S!0jyFM zK%Ffv@B^0ucrFe{kT7q7N0RZk985#9R37NRmnP^9b}}tQ7;J^QH-D_6{8{fW*ZGc@ zu4s}pc$PToFC*+%;Ke5mVvlEkQC*UMBI0-xKu=v`CA|FF;VQ@B!OPjO;(7|G)}ECo zXL4!C>8({5H~y_FU4N=Lk{i@L!;y~>9Kwb_aKv8KjSNc>5HFzG-U?^aFMi*iZoyNU zqpFe2wKD=oYu3DL=1+NzHRQ5j3NtUxNWZm;f1%pI$mVjkRgQokjjpEaTT1}uSjvI% z;G64^RL(B`)r0KgWt+m71MYA4W|S$N%<&Hw&06OOxKCN{qjRQq)%W5GHQqKq&aZMH<{f*bx9<}@^{roaWQExofRihJ#hRP_dRQE~_0ckpHM!ZLS9B2#G$ zZ}?DHoPrf&<2DifX^rge@k@h-WTS3&E;F5(AL*g;%o0pZiR&b&U~}4MF2Vw}7_`%x zV0y;IU5$#~bN9{|jgF!&8j(HgHWxCa(^C*2Y~!Df{~rz(jO3ru5gaTY(4=jSBw^WJ z;M88AFVC5(?r_S%B|v;Io>x>6PhpysAXck9F1rtyfvrY#g5rF_R^}L@H56{z0-Hp9 zLUg!@;Fk9hlfSILa_d%Qw6{CgK}BQLH$BN!|(W^5HB zj#@;`L3jcDsBjJiH%`9kQ_J74zchZXEV-<*($0XX8>$594Y#J=CPj7pzbT+9VPv}b zjVWz~jM?2FGsxMOL@_}!GOcVC4mC5rzkBwnY* z3;HP*9cOlR4&Q-Z1>)~P{6Mm*l5Vne;`>W5jI9gUvPfr{jbGGhc)8QuBOa1gH}F)C ztijN;Zk?B>ic4qtBTbp>m?P&fIbqb5;3s;J_#oqB|Bv=ArIU(Q)4hnEaKpAj`cj+0L0D1Orb%HIufJl1 zXg6%o-Np-aLWDH{iu$r)j6X%qW@qZdVF9+n6T8c3;s$BI{`BU^nxfX=Tn9C=rTnby z$36IVZz`jFw%__G{^I4)E+wJGCirT+z z(1YB*2Zb(t+c#5rX9Y>5A1>1q^^X%EZh_c2uOUl=Y%{x%CCQalxoLhuwe zCBHf%qfA)t@+M_BS;Z23z!deL$PL6pH@ckEt{0!8-pB~85`6x?@{oNlrT->E?aWM} zXeyNKhtcU5sb}*@gCe>$CV4>wsJ0f?;5oiU|IBpk2S+7t(eEdtu2+_w<3p~!zGnUG-b2Rye~9;# z(dj56jv>3{LU1bn=4Kt`69&0E+J&{>)OE(QnkG`XccQH0=LB}p_mVR%>Z?Zr%uQla z&YpK2qLVe7$A87h`nR}Gh>ZIMB|$_Ou3>Aly`amj2Nw#lqR_rD7DBs?=TaL^Us1B* zwK9U(k5z3YE3O@R3q`EEgyZU0tO(;zSX#uTt)Y*=u(V;zXOPo~QIKVZTy!N<=tHb? zE+WWXz3b%y%r8#vu5b&zrjxp4wQARsh8J$$@_yxw5k(IdpGWrLS_~+X-6()zLz~2} zHH=KM&{5sh7&tw=yMKL3 z^1b;>!_@u36C^OVBqD%gb_OH4k>DRiPKd7!%9sXfR`J!o2;?uQhNJc37_BeqU zaS&$smSQ&2OSsJul{an<=%DK1u{gK6zwbG5V4-0{>Z-p;V67YL8_lna$vJ#ZZz~}l zcSfv_(A4MyFKEWg=$3*b&oKpy*vc_iXAm@YNJP|Yl~FcXaUDZQ!grx_#|3S1^i_OL zXNSxWs|TK8&zrN8q`gRsxbOPAC&+9Oy{g=nP+-MG=TIvJ6uUh25kwDj!dH8U=`#U+ zFE+}i#DiZm%fItoMDs^RTKVZNJ{OBU{d5oC<3P-(9&KmesZ5H(Vh7YZ!?anxSLuo- zZJ&RelBm#8Zs|9U3O?itK`j4}aYX;n1+u`82OR&Ii!&F}Dk+qBOm(;)s>OlftK39S zWS540ip6ZJ>>Np9lty-EOvdI+yDGcZs7U<;d)`(e7}Ok_UB<71KVB7EReTE(CCV*L zv6{z&lp=-WU!@2ON8tyf`U!whEiFlIX-4xTkyJu>*u^!h&C1gQcA)E-j*=T$mcc;y$wTgcu+y+mT~r^4yExO>we zJgODdzew-I7*4Lt!$QE&pZeJQY3rlh{&?ip9wo}{frWpt z`X8hS{l7`U7pd*|minW6@otWM=C{j2G?Z~!?^wB&hwE|0(gWs2;fAWtz0&HlPIo;k zZsM!RZA)!A7rrY}tDzN9<85+#BHgL;b}bN>y_hm8%HizryNT)jCrQ#xrk_pA-z$q3 z+aAAD0B6cK%j#u*ir(I+A>-ws6_V|nw%}6+C&qWbGd1W`?Z6T4dWLi-Cksf}-Fb87 zbl&?EBWL%ysOFoxr_XVBWVjOTKH-iKeRRL7|0IRgTDvsh_m)chiKR80tj`MB`u*&# zeOd=StntiZ#12+nrM+P$#)4Z`nQnj?hDoWs2-jWwLgtKz4in0`Y>;jXpR3c+j)xw4 zZ=be-s4UV*rW@)>JVbhNz$&}miqBo#!7##~Tl--oOxLY_SH>;gr!PF414&Mvk#tfO zZslmIYHo;lU`4cQJowUc(Acw}`P^0K^|jN(3CHn8*@d%sU-m!S#}u~<^OrYmqA4`8rLxb1w>U+uCU2P+`QB;mGGlk|bziP%!``ObwJt3IbeWc*yB*#bagjclINT$Nt}Xb#xFGz~^EHFt`u{=`PWr`v9%$OCZga3|Mlt0ht0S zZcq>e2y!zAy#~IysepI^7I!Xy)q|MU5q`?KolwDnNi@R*a}3lH7P`K<@o{j`68;H< zxHB-$As!VS0v&aQXAqT>LqMRbp76k*u>f7+*Fdv7FX9BuhP%2*&DG6~r-P#wJk5Mv zK=m6x8~O~tmy;+6MfDpT@g45%O#DR@6$Om#ZslZa?Q>7TnF#R=xOgyvP=N#wD2M}S z_XuVo2NOBE`B}O++Awiiy1M(i`EW|WbC2!6csK-U30vOW+yKlTWWXa&B3zEkKQjuC z1L>X|_xli!-p)i^cn(1b(K-=!CnqNDgCHSu$E)e@!$%Dlzz;Gf^z z_+z=bW4Zi!%}qGu3^9W3=890k441g|wbtmji9A$u zMOBVVSusUr@-O;pjt@W@$ZAX->z^&&p2Lw=myBf0%1ATsspM0}XVd$?moE#6)X`j7 zzgBn|OOHFTm*}T^C^ma0v9U!k6;;!Hlat}#o+hrpD`jz+gN#znzL2oS7yP2+pC7n# zxD-@1p`XclyGhn^ktMA9{Z{k|5=O1xA~#-1?Z6aBHkvr!V{K6-+4MrOHFK*XqipWmhyjZ~Uouwo`Qb+lft7 z6xVFM}6;a%Dp0Gv;9O{}ReG?J!sBm5OuSR=!)t~?~+OVQe zVBTgr1w$`=09!gJ({|`^)90O1^{+>36v?O$6ZZ*j`_N2^+x?uF?M7tt0Ga-?LqPIR z$}a%u{ng0|7UszhFG9%h|k?&zTX`k<$_6ss*FSPi4lPbf` ztGDn>_G+EZT?A6cp!P5YUr)(B7YUSq03=K)^3vg6OMUmtWU&@Io&u4Jn7W7A{H34l zbCzx-nFA`+{yB2&xp6Y|gx8XPK}FlfU2ZeQgHhB-;JEx&~8nR(=j zTT^qf8jTI$yKC93sm@DI>B=*nHIPxFQLv5~V)+Pf-PdXu!;$7Z&2c2L# z7aR=R+6ojSz}m5EMsIXKsnI?@D(ViO}mOMb)LzD)YmL6{*Av%PHl zn`)XBzHxn9!u_8;_mB|%!)W>O4NX{ZeJ6hxmqnY2!6dRiA=v7s7M3sW9`CNyL|jCH zkC!o0by+e*i|6qCq$K@sDySL$^!{f{I=^WN!Ok9;O~?3eF~t3&Xd zWm!}NFBBN?IF*hs3)6N@wkl~(1kHsJ408T z-)yfPw`VM6u;k`_x*qXuvdvt7q{#gAAiMwx(Jg8H?<^J>lEp%vBt#4ObCS?r49$>n z`mrh8IyUW4n7oco9&F`3k zIo=Uz5I^1&zI1@i&Y`W+ol$OLXUeQ)jp##Q85X3NN|AB%wqsBq|Ld>L89Re46(?;| zrgqMgOrZA>YqwIRK~Nig+Sn^W5g zO*jcs=^%k#c+jT{*j%>P)T za3&_|Q!8KVI{dy%=(=J|M0hl}Y}vt7^WZCumX86Wj4H2EYh_U}@^ zyfp@X8iX-C9{O`cWsm5OzuwmDAYu;FH&CUeEKM^Wj|A;ltk7TC>?}{nmZ|dJn~*WIsefCj)^%MQP|9Y^PJuwRIB#-eelf zNPYQgOt0MYbo5@E0*G}Yn)k60Cs&kK|ARC=&TzOj5mC*wTl9ogxRjpoyiTOYM~?u< zZA#nIU=1P8`ee;H)dqi(xA&BIPpX3~`E06Si3_o*#wh)$81gI~Q>zf@9v&p{Ewstx z6L-Kl&zYUlH@29=F{{^|xC#eF^@0ldBnwYc*=e5A>sL;;~7r0U-iQW_qf%glK^C|u zl#3_mKqD8DRSr)oNOGqA}S(3iMa^oW+p7o{>=S5syv!ekIAY@()D*kZ#bos$3QGaO09AvI!& zZc?kpdormS0zJ<3Gi9b+mQ#k8j_tFx(Ay7LA8rLE`xGy`YSip7h>6dubwi-pEgn!* z)5Bw1=#OR6?S_|am*eExqz?*7--q%SEAL4tTgEmmEO$B_>X>~TzrSvMW%|la=*jSq zn3iEwd=CHRjr0^CRD`(y2wxljxA3T7kTtM_iU9aV7_%$25p^W zvXW$(sI6GeCC+d7UI%p*OD1&5`X*%d(p+O!-@ZJU(f_1)y-H)8^<>8lR zG}@|LF6ab5#6qA48=OvipHYn-M)gFzU8pk*lI+nb-8o4@Jz*?nV-l;Q0r>%|b3CR4 zvtew{{IkLN7BG49AI;@2Vd1Ii+Xr`UMArI+i$oaY&3^b(7iciHk@`_FE0m)-oRZXC z?4{j1l;u}@3aWqlf?dk{++6ZY%$Ijsgx#&z*u>&@4f|G_>jV9m=8Nuwxo>mt19#X!BBiq#VyOq zI{fDC-@u_em|`&Luv$`ol@^iIS(|Qn)-EaT)AVu-;SF8RcLRH4@kNNU$s$L)IhwDy zhBqn!S`@#zf)J_zHlsiXfvz*KfeOrsW?5W1FtCq}0~`sF08Lnmz$JDbaEPS~SV$28 zonmwbtY=LS_UQgGx26*!=#4Hqsd zWYxLD<6V9bdpFZ~z@MhUAl8BRgAvV>ryI-SKx}a*dLQrc-7=j_Q1lhf9mCJ-vfnT> zQlcBMLmF=>Fn)wU3CbU9n%>LWzNJW zC~NHk&&;voUS0^(+T;kwc`VjVmjNRyz5{`xu5hP50217zd2pc>weplNc+Hk_ZFJuk zDeLu%E8@Eoz3+Vu4trKNULhEsaRjBNqD6&2pn=)oIvcks5N%!|5k}w$01WKo7uq|EI^(|}< zFZp21j3SV_aZ|m3x*}C0f-Ia~XqkTzaOCo!+DkBh|J~84PgX!WEBdh7V%S!@Q_Fl( zm+S^AtzIO!+{dU^Ka_74x9 z?|hxn`$``~zlk1P7Yn*tgnCq9SQ`cNB9*X!lr#Lx!tI|=sv@>CQ%1iq$_!jSxa#R? zV`sOS)im1)Z}v!bsU(5Hv-b%pU}>D35G|rWHt^z{kgGUk>6zcowd)K7%GM-bq>V5C;O~!@iUcb!=rp|2DNrt00np&TGYHd95}BPe41>l=@_)NFP2uC~w_t6>h1^ zEjd25& zjx3YLUL+DxHy!}-Cn>BK?G07$FY03$KwD3i{R=Q{T_d4hxG1fu$XjJKKof7qC?vw$&S*;!* z(Lz??iN=&&X@PW^8kg#h6ZvFCx*J!4hz&4rj^>ZLygMSg8T~PcE+7!g8wLoPvxl=E zg!F&Z7*JBW z51n$AUV+F|QT)JtSZvT<_eDy}s&Oh|~{8h{5B6VrI<*NgY( z6wl=xgpv%ZCtxSx(hA@-)4P`V90s5nt@Ae`t+5U%7EGzH5^h}(N77@RhC<&5pIY~6 zzqqQ#u{I2+jrMuP>CE@t6RUq2#Ip*Oj4e2N1$DH#WGnqq>R=yZDe^c-ri&j*sJ z@Hxl}L7HC!FXS8z2iSsz@ly}v0<7G0?#|(XR|GcuMbs>CA03Rv^seAfN52{^>M5^y zMXY#MzFVol?qkM9%K?GDK|GiKleDjl`}1Dvy_eR!p*)$@&U7s4b83{~;4TakPv?;GRp2Cp>er+Thc zv>B|8;5h!~ zZ}r9igG}FmLIH*-as-rYR{xPyNR)Kb&RU9f&ehVggS}Wb+Mxk5yD#z&;AhV-DtVG8 zAGGCBpQt~SNj?rn$w9g;vsA{}ENLD#e64aoJCPFNLS7c5NHhp3&WNq4s@wCG+N} zNwN(pfCTCp9cMVSxT;BQx+n>y-QrfSAe`86<<3QLV?vT+f9c_ZqVk|R3lwWb&&Xxi z+UyovB;Qj|?m}PBqOv)c>-?NShAlq?s`|B7#bB7kx)lC_N0pYSVstIH>rBy(3DtZ$ zP_?M6tpZlF_HeSLI8DLP!j8~sj;(`@kMd+T<+-yerx)z*k`M%HBK~~CayN-f<$g!> zPfhxo{8uLRy)_s^cx(8kcH4nAw|HntW$N!v&IX8|j)(H@I@2u1g$TYfDE`jIgRhF^ zjsyzl2B<&5&^t}ao6#TLS3v|rypWBHr@@Yy|0mB!&lQ9{z4_03M$RAkPT-D@Kw4 z;u&SpyFM&u{0@;mz7~tW>;SEi9Zse6*GA>VAK&^e#u`BB29Kn=vp;w2S4#ycW8D&` zZKT?^E8s^V`Pgy&$h9Kb_Jr#p3!s&X#C!3N-#x30d{*K(z?nB}^PQlwtj8vIxd}rd_bsNeByYwRcHKzZ zosFMw4?x*UnGh(6rM|(dul=PJ7lxX&Sk+BRCyKnIA+77>%30d^E-Y0K#**VQHYp3? z1(x=3xT(!fQqnd_o=bfgnBD;wmMBsVfriFefauKnaCrzAluC?q@B^F)W~zC${ndI( zRK}z_-}^+nJ@{U`o(s`g^f6-h-3fyUR(Lfg-_bwe6@91?n{5 zD-qAQr44M3D;&XTPGu|g_wDdUM=&6cxF*JLsD)`6LgDj2SM|&DUnllzscI?+A)$z8 z`28Kg)9WXi6TLM=ngKbQ=N{m{(3%Jy4KOtcE(SwPiqxw0^8^ z<^-&~TVL67a|$4&P0)PB^rai!)60voc&{e^S+e1_U_ajsbU_guAGQ2o*W4@Y~?9&li zcc#@Pk4$Tp1j2TmRyaC;qSvU)-7tK&;dYa2b98V|$c!?7PpP}m2z_vHEXh1ecn^lb+T zdddih(_dA~EZP@evdxhKUL3B7MBUx&@gHyH6RlAsvgFPD%$R%HOR{i#Vms8shp>%U zN3Myf;lt~bMQ1_Z{Qj(hw&etLhR-3wnsUFSG3BQDt1gG<6;!pBsAK;%$-_Y=dFBi* zhw2jdI`A^Wci(LMDnh#&FQzr^@;z@E;1Ly6erfyO#+RIGDK1mvH36fSLPFwwFdx+g zEaQb=Rr&H)qfiJ`ww1LA_SN*h-G%kBOMpXRzInBi+Bdr=nS=LgUSO_~@Gh2*`A@Li z8kg?hw6jwz(p+}gCBKB@x^{w_2Vo_jRv^&h88q^D)&(|Z6H%Qq5Ye55s#~LjO4bUb zug&>>dRjDe@XAGhkc6*cmYI2bJUGH3ofGu>deyL?0o@)F791}{S~AFu!u;`EPLotf z_4#ed3=p1+4Fxam|I%|=+xj^A{;T^6boO&XPXBjD#so0nU;~=G-Tyc=g@18oiG1wn ztYRHcemk>A^*?AAM8!&SO?^pg?scpSn=P z`5an077F!1fk6B9pHgmfAMQtf9~DlYQng+Z1YLx!wgxp=SB`VR!A7U60*39@#|wi> zU6BTd48oxlqbEW4U25Gvt0d&WP8GEw(0h9Rdc%`}t3nto_{j#xxSJ1u{5+c~dS&)R%f zHy~CjV`9hsM!ui;oiDpj*M>fAqY0t=&)iYMW~(LO{^YVp;(QlpEcyEAWVe2_%!J*Z z=y{!woG<4CF2Yb?I$q*=WV*KRtjYov03*lYqlf1#t_$T9+-EAnTJTNi?7)otD zxw&ySf<{a&uOL{*5&bHFb=72G)@Hl3OWvv0@!;JysUR$!Ll_tNF9>v)p5yaAit^Z} z95{Nj=AKs~fs<4;>qJ%4r&S)}Gy-)#qJc4MQR+~_Q4GkILnDxc|*)pr=A3X6U7;7VF)yX6}7t2uGa+3 zG$VcB8Xc!i$#?z@jq^|@+VyjSub6Q>L_2}Ufr|@ug9%p7KU|h$2iRes`*Q0v7%Zku z?Xd>GL7-Odau4$5#dLC}3Ewt&a+CeAQnbbCe(&t9+zT4KT`~bLAU7B?Cn@M_Y*`5tgQBS6G*65V0Y21$R|B_>^u1!-8nPC+%N>T#bJbr^IJSzUXZ%O8qX5 z81oGu`X0u_kq#X;epeyL$pYs8y$AyJ4N+I<{yIb37tV|ol}NFD1@Bg!ODCi;*W};` zb|<4KW(^Xk2#uTO!4^N@lj66BH}9}p2spVpwvpqIu}a^+ApzRa7a0IpGE@lfVXfI! zfR&pELchbIi(`XN1^xe2{E)E-mHSgF?u^WYfJpAb{jTC=0SR2iCQPU_f=a51?d^8& zV+C;^T2*wE@?7~Q(+JL4o2K^&3@1JHc=J7^HPhb=0>z3@3Dlm9cEWm%9nO|qL{~x@ zb3TIh4E>dxX=+SL_*_IbRteQz3mch{p7#_HLy=~&=5y>nY$XdEe*KaW(jk&oh~q zv%~$JpMTv(4QGQuY{P4bq3 zOj6R(h`_v;tCaf28s(V21^j8>6jkS%BJ96n5r5ccSyk`ss@ROMa57<=y!g7eJm30v zFbS7}Va)S>!6S51QQ4;bfqn^B%D8mDg7H3eFg!9bxvr_-W_6tR+1Js1g5p{z zL-{&Dy>G|gsIHP>%`v4n-Q5NVCf+QJsyvdro{8Rm4s%(<3BkLKq>~L1c`mSL%7thh zsUbUNLS~ef|Cw5d5@y#KSzfG43F{35;^GVAJ5rCDqBb-nepFRd$hRW1gZL?P<_uvq1+!mQ)BgX;?my12B%Nj-fr z6{_5-rVmUXvB~{P_tS&vhJjbDjF}QAsv=o6oS$u`Sxup{sk1cUYp@Q5X)z$-pf%?V z*XS)I<6S0vKj9y&%UdOGs8Yn|`*5BRHM{Q0K!=%Hx#ef?PAl$fN5NM0rjF`}m#<0R(WCG)Itj04?;-oPYfwz>Vywf!_WfbP+bWf^FGAj#R66C4q zjEa}#FlNoQCqGP%Y36xX7$wkZf(*!H{`lLU1l=?TO@&G;J8>VyuFa46?$->PeYa*w z<)i3Rk3QvpzaG}^+G(_sZGYtxSEy89{nExR^R40b#$Hbuq_xeN@dk{)nlOaOfb7RE ztUonFJ9L-p_|*jC&KU8!9CL1cM|M)iGz=rC7 zZufA6)Ka3ZMh;4KQ&D-nlQLn~!>V*-`cCpFnTko;t)BJP6;sphbPm+yX*`W4>#92Q zy=dvT3#yIUJAV8}Z1#G{KUTLfzuXvtGBY_XMT`$i$0;GJ~RkyQR=gYWpVH5 zE(9%hKJ19>eXK?%cK=RYtgCiJ%uK~kmd(nZ@UJIGG7#Lr|0h|a1e^awhMVXdAO_O! zB_ms_>5eb}PZ}RZN)RB#p#-$-_<%48RzMb)1Vu^|=yjn29z)E4Mnq$F%Z?hbanZVI zz@iEXO9F~c!axZw4xsH!4N$of0^wLpK&(9xs-&?10XwNi+&ln7$1cGJK` z6%~|1F7p_|1dKtb5bxCh9y^eti2h!^%RwIq6Cy(q`u&ua-7Q2WKngGlQ3C?bu>c{2 z2u(;5@gwr}xQxhNv5GwuAaEoHivfDLRDgn`5+XSu0gD5OSQY4&5drXBB@rovCL%p3 z2a~?T(uS#uc-VXLIePN_)j0-Z0UzZ_fzR$lfQmdT@Wfpb9nIO#(-t_8rv%{cJ}9o3 zKs7!Skl_(Q1_AqN%n7oh%?o-tY0R>t!yurbB!I%xAFK{M@)QP>13jLW;9`K@>lRoR zaP;C9qJZG}d$@Xd26{01dAizr1WSNGNz%@W=N#XXtJyIK=NX~S!Mb8}zu<5`CpTTO zMWD-zo?#9#ggFH}8;E^H)K{H?g53?p27b>48HjZNmtOn`8_eacC0=z6hkH9Y>nf&O zZVCO_n~#eG3P@v45wTB2Uss}0#F7v!&$}-6_x#jVTnQ0lpw^oK3<5rQ3xk;exc7Y? zdI+(zM}V!HvmGOct-GhchaZPDV+ggb1S(>;iutFGqh3y-x?-kqI2^F`CZ+y+|3Qc! zFn<2thS(r5?8AP$12Ob024VnWJ_s>?pUg;X z86gJ|;xJ6WV8zoHu3x;If^@~&5h5d&BLQ@L#d(#k{w#rlcnQ=51tnNa5i#WzgitnQig?w-_#;G}it_NaPos$Ffi`K^ocU3Q|afGpE74ZR$B?O$_t zS;lAGTFH~9X+W2-<}?X-Ar$H7u>SFLsK+svX*1)*Kjg0_o?DRW^Nbt{)yx-Jw^Dlr zFY6^H_Xwm_`ie!>XS+zK;X<66IioMu=vagbT}A>~^v0{0|C^s9M*2Ck+g~YMujW6U zXE~DhZhjkf9})p=hME-%3^6Fve$hVk8S`+jbHY~F;~jLjl??q=Th{!->!OSQaMj@9 z5370x2y`F;x6~;(cH!&cjNXCXXh>7~>@76i5naNUO`NZSP4w5;$(^uv)SMD>#+vm$ z|5BDZkE3p&5wXyal-WpyC2FQXpv*LUY#7NvDduj7*Zk?bu_4yRaz8;gp($c&lp445 zA7?vMwMiHQv~#@d3K*`A?mt1&Gt{3Mdq0kUGskCX8Q+30m;)6T1bR2KQ)#rE5cVIoU9zxwKJ z?Clw0O=zm#0X8*y?i(4;Vw70?;^%jwx4%JjjGB__F}GK^m*YPXD&Fp?8CKb+ZL9-V zb|Q=v;-iDcwY%*Y z6f*{k%Y!2NWwRyt=NoD+du;5_1+MM1$c+2gL5{>i!*rjJr9){9&Gf`XynU>I_yBweaMev`j_nt z#z31FURS~UnODgU@G1cu0(sm{3DZZjXcz3xpIMP8#op%(|At9r-yueCn1T}WG05yV z`c|zmX;rlZDv8rG?);veab5k|H=BA%zcAp0_$?l~)OAcImEljG{CfJ{|Giy($RPx3 zL3Mia)%+W~SjpIg!!@_la}|`M?ree#h36gmEF0zzC{WGTveoSVNG>BF=@Sy-UpnHUkMCqnm{LiN$|K@8w$p?SF)@!m9ac!X2 ze4olYz$Gx`E)CifkGZW|$ZOh|oQ@Z`X-z52k`cE}S`drdI)`>=K(PhFP zZ(hE4^?L__>Z*{eFAqHS7J3pq~A!_S$K|RP;Y;GQ{N{iT! z$}-HiI+5BrQejn#(;8g4Z_w$+RBu6`5~J}-DM8vwPvmg z7uN;Xp{9{5>27KHecu!(?nR05xqYR53me_c8^Xu;VcdY_H#w-Cs^K?0{#7#^A<6%Dp#a8=76uM>t?OQ!1 zx?ws3v|}=__luW8hEFTMr!Wj&cRaJs`QShrs3x##aE}4zaY6ur+U4dot2fC?sMwG` z3WV&$xi5mtmeBQZQ3;n}-II%T$G4wORGRb^a-htX6-cI=MMY}U#M-WIq)R%-tk9tb z+^~TPsil8(c9!%Xodtow08 Date: Tue, 15 Mar 2022 19:16:39 +0300 Subject: [PATCH 06/15] core: refactor helper test contracts generation * Move generator to a separate package. * Move loader to a separate package and get rid of the code duplications. --- internal/contracts/contracts.go | 103 ++++ internal/contracts/contracts_test.go | 495 ++++++++++++++++++ .../contracts/management_helper/README.md | 9 + .../management_helper1.manifest.json | 0 .../management_helper/management_helper1.nef | Bin .../management_helper2.manifest.json | 0 .../management_helper/management_helper2.nef | Bin internal/contracts/oracle_contract/README.md | 9 + .../oracle_contract/oracle.manifest.json | 0 .../contracts}/oracle_contract/oracle.nef | Bin pkg/core/blockchain_core_test.go | 7 +- pkg/core/interop_system_test.go | 439 +--------------- .../native/native_test/management_test.go | 80 +-- pkg/core/native/native_test/neo_test.go | 3 +- pkg/core/native/native_test/oracle_test.go | 42 +- pkg/core/native_contract_test.go | 3 +- pkg/core/oracle_test.go | 142 +---- .../test_data/management_helper/README.md | 9 - pkg/core/test_data/oracle_contract/README.md | 9 - 19 files changed, 648 insertions(+), 702 deletions(-) create mode 100644 internal/contracts/contracts.go create mode 100644 internal/contracts/contracts_test.go create mode 100644 internal/contracts/management_helper/README.md rename {pkg/core/test_data => internal/contracts}/management_helper/management_helper1.manifest.json (100%) mode change 100755 => 100644 rename {pkg/core/test_data => internal/contracts}/management_helper/management_helper1.nef (100%) mode change 100755 => 100644 rename {pkg/core/test_data => internal/contracts}/management_helper/management_helper2.manifest.json (100%) mode change 100755 => 100644 rename {pkg/core/test_data => internal/contracts}/management_helper/management_helper2.nef (100%) mode change 100755 => 100644 create mode 100644 internal/contracts/oracle_contract/README.md rename {pkg/core/test_data => internal/contracts}/oracle_contract/oracle.manifest.json (100%) mode change 100755 => 100644 rename {pkg/core/test_data => internal/contracts}/oracle_contract/oracle.nef (100%) mode change 100755 => 100644 delete mode 100644 pkg/core/test_data/management_helper/README.md delete mode 100644 pkg/core/test_data/oracle_contract/README.md diff --git a/internal/contracts/contracts.go b/internal/contracts/contracts.go new file mode 100644 index 000000000..d922985ff --- /dev/null +++ b/internal/contracts/contracts.go @@ -0,0 +1,103 @@ +package contracts + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +var ( + helper1ContractNEFPath = filepath.Join("management_helper", "management_helper1.nef") + helper1ContractManifestPath = filepath.Join("management_helper", "management_helper1.manifest.json") + helper2ContractNEFPath = filepath.Join("management_helper", "management_helper2.nef") + helper2ContractManifestPath = filepath.Join("management_helper", "management_helper2.manifest.json") + + oracleContractNEFPath = filepath.Join("oracle_contract", "oracle.nef") + oracleContractManifestPath = filepath.Join("oracle_contract", "oracle.manifest.json") +) + +// GetTestContractState reads 2 pre-compiled contracts generated by +// TestGenerateHelperContracts second of which is allowed to call the first. +func GetTestContractState(t *testing.T, pathToInternalContracts string, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) { + errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate") + neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractNEFPath)) + require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound)) + ne, err := nef.FileFromBytes(neBytes) + require.NoError(t, err) + + mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractManifestPath)) + require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound)) + m := &manifest.Manifest{} + err = json.Unmarshal(mBytes, m) + require.NoError(t, err) + + cs1 := &state.Contract{ + ContractBase: state.ContractBase{ + NEF: ne, + Manifest: *m, + ID: id1, + }, + } + + neBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractNEFPath)) + require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound)) + ne, err = nef.FileFromBytes(neBytes) + require.NoError(t, err) + + mBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractManifestPath)) + require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound)) + m = &manifest.Manifest{} + err = json.Unmarshal(mBytes, m) + require.NoError(t, err) + + // Retrieve hash of the first contract from the permissions of the second contract. + require.Equal(t, 1, len(m.Permissions)) + require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type) + cs1.Hash = m.Permissions[0].Contract.Hash() + + cs2 := &state.Contract{ + ContractBase: state.ContractBase{ + NEF: ne, + Manifest: *m, + ID: id2, + Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name), + }, + } + + return cs1, cs2 +} + +// GetOracleContractState reads pre-compiled oracle contract generated by +// TestGenerateHelperContracts and returns its state. +func GetOracleContractState(t *testing.T, pathToInternalContracts string, sender util.Uint160, id int32) *state.Contract { + errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate") + + neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractNEFPath)) + require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound)) + ne, err := nef.FileFromBytes(neBytes) + require.NoError(t, err) + + mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractManifestPath)) + require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound)) + m := &manifest.Manifest{} + err = json.Unmarshal(mBytes, m) + require.NoError(t, err) + + return &state.Contract{ + ContractBase: state.ContractBase{ + NEF: ne, + Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), + Manifest: *m, + ID: id, + }, + } +} diff --git a/internal/contracts/contracts_test.go b/internal/contracts/contracts_test.go new file mode 100644 index 000000000..77cd71e18 --- /dev/null +++ b/internal/contracts/contracts_test.go @@ -0,0 +1,495 @@ +package contracts + +import ( + "encoding/json" + "os" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "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/nef" + "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/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +// TestGenerateHelperContracts generates contract states that are used in tests. +// See generateOracleContract and generateManagementHelperContracts comments for +// details. +func TestGenerateHelperContracts(t *testing.T) { + const saveState = false + + generateOracleContract(t, saveState) + generateManagementHelperContracts(t, saveState) + + require.False(t, saveState) +} + +// generateOracleContract generates helper contract that is able to call +// native Oracle contract and has callback method. It uses test chain to define +// Oracle and StdLib native hashes and saves generated NEF and manifest to `oracle_contract` folder. +// Set `saveState` flag to true and run the test to rewrite NEF and manifest files. +func generateOracleContract(t *testing.T, saveState bool) { + bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + }) + e := neotest.NewExecutor(t, bc, validator, committee) + + oracleHash := e.NativeHash(t, nativenames.Oracle) + stdHash := e.NativeHash(t, nativenames.StdLib) + + w := io.NewBufBinWriter() + emit.Int(w.BinWriter, 5) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.Int(w.BinWriter, int64(callflag.All)) + emit.String(w.BinWriter, "request") + emit.Bytes(w.BinWriter, oracleHash.BytesBE()) + emit.Syscall(w.BinWriter, interopnames.SystemContractCall) + emit.Opcodes(w.BinWriter, opcode.DROP) + emit.Opcodes(w.BinWriter, opcode.RET) + + // `handle` method aborts if len(userData) == 2 and does NOT perform witness checks + // for the sake of contract code simplicity (the contract is used in multiple testchains). + offset := w.Len() + + emit.Opcodes(w.BinWriter, opcode.OVER) + emit.Opcodes(w.BinWriter, opcode.SIZE) + emit.Int(w.BinWriter, 2) + emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3}) + emit.Opcodes(w.BinWriter, opcode.ABORT) + emit.Int(w.BinWriter, 4) // url, userData, code, result + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes + emit.String(w.BinWriter, "lastOracleResponse") + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) + emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) + emit.Opcodes(w.BinWriter, opcode.RET) + + m := manifest.NewManifest("TestOracle") + m.ABI.Methods = []manifest.Method{ + { + Name: "requestURL", + Offset: 0, + Parameters: []manifest.Parameter{ + manifest.NewParameter("url", smartcontract.StringType), + manifest.NewParameter("filter", smartcontract.StringType), + manifest.NewParameter("callback", smartcontract.StringType), + manifest.NewParameter("userData", smartcontract.AnyType), + manifest.NewParameter("gasForResponse", smartcontract.IntegerType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "handle", + Offset: offset, + Parameters: []manifest.Parameter{ + manifest.NewParameter("url", smartcontract.StringType), + manifest.NewParameter("userData", smartcontract.AnyType), + manifest.NewParameter("code", smartcontract.IntegerType), + manifest.NewParameter("result", smartcontract.ByteArrayType), + }, + ReturnType: smartcontract.VoidType, + }, + } + + perm := manifest.NewPermission(manifest.PermissionHash, oracleHash) + perm.Methods.Add("request") + m.Permissions = append(m.Permissions, *perm) + + // Generate NEF file. + script := w.Bytes() + ne, err := nef.NewFile(script) + require.NoError(t, err) + + // Write NEF file. + bytes, err := ne.Bytes() + require.NoError(t, err) + if saveState { + err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm) + require.NoError(t, err) + } + + // Write manifest file. + mData, err := json.Marshal(m) + require.NoError(t, err) + if saveState { + err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm) + require.NoError(t, err) + } +} + +// generateManagementHelperContracts generates 2 helper contracts second of which is +// allowed to call the first. It uses test chain to define Management and StdLib +// native hashes and saves generated NEF and manifest to `management_contract` folder. +// Set `saveState` flag to true and run the test to rewrite NEF and manifest files. +func generateManagementHelperContracts(t *testing.T, saveState bool) { + bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + }) + e := neotest.NewExecutor(t, bc, validator, committee) + + mgmtHash := e.NativeHash(t, nativenames.Management) + stdHash := e.NativeHash(t, nativenames.StdLib) + neoHash := e.NativeHash(t, nativenames.Neo) + singleChainValidatorAcc := e.Validator.(neotest.MultiSigner).Single(2).Account() // priv0 + require.NoError(t, singleChainValidatorAcc.ConvertMultisig(1, keys.PublicKeys{singleChainValidatorAcc.PrivateKey().PublicKey()})) + singleChainValidatorHash := singleChainValidatorAcc.Contract.ScriptHash() + + w := io.NewBufBinWriter() + emit.Opcodes(w.BinWriter, opcode.ABORT) + addOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET) + addMultiOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET) + ret7Off := w.Len() + emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET) + dropOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET) + initOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET) + add3Off := w.Len() + emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET) + invalidRetOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET) + justRetOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.RET) + verifyOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, + opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) + deployOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3) + emit.String(w.BinWriter, "create") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes + emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET) + emit.String(w.BinWriter, "update") // 8 bytes + emit.Int(w.BinWriter, 2) // 1 byte + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte + emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) + emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) + emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes + emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) + putValOff := w.Len() + emit.String(w.BinWriter, "initial") + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) + emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) + emit.Opcodes(w.BinWriter, opcode.RET) + getValOff := w.Len() + emit.String(w.BinWriter, "initial") + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) + emit.Syscall(w.BinWriter, interopnames.SystemStorageGet) + emit.Opcodes(w.BinWriter, opcode.RET) + delValOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) + emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete) + emit.Opcodes(w.BinWriter, opcode.RET) + onNEP17PaymentOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) + emit.Int(w.BinWriter, 4) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "LastPayment") + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) + emit.Opcodes(w.BinWriter, opcode.RET) + onNEP11PaymentOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) + emit.Int(w.BinWriter, 5) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "LostPayment") + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) + emit.Opcodes(w.BinWriter, opcode.RET) + update3Off := w.Len() + emit.Int(w.BinWriter, 3) + emit.Opcodes(w.BinWriter, opcode.JMP, 2+1) + updateOff := w.Len() + emit.Int(w.BinWriter, 2) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All) + emit.Opcodes(w.BinWriter, opcode.DROP) + emit.Opcodes(w.BinWriter, opcode.RET) + destroyOff := w.Len() + emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All) + emit.Opcodes(w.BinWriter, opcode.DROP) + emit.Opcodes(w.BinWriter, opcode.RET) + invalidStackOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item + emit.Opcodes(w.BinWriter, opcode.RET) + callT0Off := w.Len() + emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET) + callT1Off := w.Len() + emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET) + callT2Off := w.Len() + emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET) + burnGasOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas) + emit.Opcodes(w.BinWriter, opcode.RET) + invocCounterOff := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter) + emit.Opcodes(w.BinWriter, opcode.RET) + + script := w.Bytes() + m := manifest.NewManifest("TestMain") + m.ABI.Methods = []manifest.Method{ + { + Name: "add", + Offset: addOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("addend1", smartcontract.IntegerType), + manifest.NewParameter("addend2", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "add", + Offset: addMultiOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("addend1", smartcontract.IntegerType), + manifest.NewParameter("addend2", smartcontract.IntegerType), + manifest.NewParameter("addend3", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "ret7", + Offset: ret7Off, + Parameters: []manifest.Parameter{}, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "drop", + Offset: dropOff, + ReturnType: smartcontract.VoidType, + }, + { + Name: manifest.MethodInit, + Offset: initOff, + ReturnType: smartcontract.VoidType, + }, + { + Name: "add3", + Offset: add3Off, + Parameters: []manifest.Parameter{ + manifest.NewParameter("addend", smartcontract.IntegerType), + }, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "invalidReturn", + Offset: invalidRetOff, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "justReturn", + Offset: justRetOff, + ReturnType: smartcontract.VoidType, + }, + { + Name: manifest.MethodVerify, + Offset: verifyOff, + ReturnType: smartcontract.BoolType, + }, + { + Name: manifest.MethodDeploy, + Offset: deployOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("data", smartcontract.AnyType), + manifest.NewParameter("isUpdate", smartcontract.BoolType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "getValue", + Offset: getValOff, + ReturnType: smartcontract.StringType, + }, + { + Name: "putValue", + Offset: putValOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("value", smartcontract.StringType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "delValue", + Offset: delValOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("key", smartcontract.StringType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: manifest.MethodOnNEP11Payment, + Offset: onNEP11PaymentOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("from", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("tokenid", smartcontract.ByteArrayType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: manifest.MethodOnNEP17Payment, + Offset: onNEP17PaymentOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("from", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "update", + Offset: updateOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("nef", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "update", + Offset: update3Off, + Parameters: []manifest.Parameter{ + manifest.NewParameter("nef", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "destroy", + Offset: destroyOff, + ReturnType: smartcontract.VoidType, + }, + { + Name: "invalidStack", + Offset: invalidStackOff, + ReturnType: smartcontract.VoidType, + }, + { + Name: "callT0", + Offset: callT0Off, + Parameters: []manifest.Parameter{ + manifest.NewParameter("address", smartcontract.Hash160Type), + }, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "callT1", + Offset: callT1Off, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "callT2", + Offset: callT2Off, + ReturnType: smartcontract.IntegerType, + }, + { + Name: "burnGas", + Offset: burnGasOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("amount", smartcontract.IntegerType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "invocCounter", + Offset: invocCounterOff, + ReturnType: smartcontract.IntegerType, + }, + } + m.Permissions = make([]manifest.Permission, 2) + m.Permissions[0].Contract.Type = manifest.PermissionHash + m.Permissions[0].Contract.Value = neoHash + m.Permissions[0].Methods.Add("balanceOf") + + m.Permissions[1].Contract.Type = manifest.PermissionHash + m.Permissions[1].Contract.Value = util.Uint160{} + m.Permissions[1].Methods.Add("method") + + // Generate NEF file. + ne, err := nef.NewFile(script) + require.NoError(t, err) + ne.Tokens = []nef.MethodToken{ + { + Hash: neoHash, + Method: "balanceOf", + ParamCount: 1, + HasReturn: true, + CallFlag: callflag.ReadStates, + }, + { + Hash: util.Uint160{}, + Method: "method", + HasReturn: true, + CallFlag: callflag.ReadStates, + }, + } + ne.Checksum = ne.CalculateChecksum() + + // Write first NEF file. + bytes, err := ne.Bytes() + require.NoError(t, err) + if saveState { + err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm) + require.NoError(t, err) + } + + // Write first manifest file. + mData, err := json.Marshal(m) + require.NoError(t, err) + if saveState { + err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm) + require.NoError(t, err) + } + + // Create hash of the first contract assuming that sender is single-chain validator. + h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name) + + currScript := []byte{byte(opcode.RET)} + m = manifest.NewManifest("TestAux") + perm := manifest.NewPermission(manifest.PermissionHash, h) + perm.Methods.Add("add") + perm.Methods.Add("drop") + perm.Methods.Add("add3") + perm.Methods.Add("invalidReturn") + perm.Methods.Add("justReturn") + perm.Methods.Add("getValue") + m.Permissions = append(m.Permissions, *perm) + ne, err = nef.NewFile(currScript) + require.NoError(t, err) + + // Write second NEF file. + bytes, err = ne.Bytes() + require.NoError(t, err) + if saveState { + err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm) + require.NoError(t, err) + } + + // Write second manifest file. + mData, err = json.Marshal(m) + require.NoError(t, err) + if saveState { + err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm) + require.NoError(t, err) + } +} diff --git a/internal/contracts/management_helper/README.md b/internal/contracts/management_helper/README.md new file mode 100644 index 000000000..888580153 --- /dev/null +++ b/internal/contracts/management_helper/README.md @@ -0,0 +1,9 @@ +## Management helper contracts + +Management helper contracts NEF and manifest files are generated automatically by +`TestGenerateHelperContracts` and are used in tests. Do not modify these files manually. +To regenerate these files: + +1. Open `TestGenerateHelperContracts` and set `saveState` flag to `true`. +2. Run `TestGenerateHelperContracts`. +3. Set `saveState` back to `false`. \ No newline at end of file diff --git a/pkg/core/test_data/management_helper/management_helper1.manifest.json b/internal/contracts/management_helper/management_helper1.manifest.json old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/management_helper/management_helper1.manifest.json rename to internal/contracts/management_helper/management_helper1.manifest.json diff --git a/pkg/core/test_data/management_helper/management_helper1.nef b/internal/contracts/management_helper/management_helper1.nef old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/management_helper/management_helper1.nef rename to internal/contracts/management_helper/management_helper1.nef diff --git a/pkg/core/test_data/management_helper/management_helper2.manifest.json b/internal/contracts/management_helper/management_helper2.manifest.json old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/management_helper/management_helper2.manifest.json rename to internal/contracts/management_helper/management_helper2.manifest.json diff --git a/pkg/core/test_data/management_helper/management_helper2.nef b/internal/contracts/management_helper/management_helper2.nef old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/management_helper/management_helper2.nef rename to internal/contracts/management_helper/management_helper2.nef diff --git a/internal/contracts/oracle_contract/README.md b/internal/contracts/oracle_contract/README.md new file mode 100644 index 000000000..d57d41cc4 --- /dev/null +++ b/internal/contracts/oracle_contract/README.md @@ -0,0 +1,9 @@ +## Oracle helper contract + +Oracle helper contract NEF and manifest files are generated automatically by +`TestGenerateHelperContracts` and are used in tests. Do not modify these files manually. +To regenerate these files: + +1. Open `TestGenerateHelperContracts` and set `saveState` flag to `true`. +2. Run `TestGenerateHelperContracts`. +3. Set `saveState` back to `false`. \ No newline at end of file diff --git a/pkg/core/test_data/oracle_contract/oracle.manifest.json b/internal/contracts/oracle_contract/oracle.manifest.json old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/oracle_contract/oracle.manifest.json rename to internal/contracts/oracle_contract/oracle.manifest.json diff --git a/pkg/core/test_data/oracle_contract/oracle.nef b/internal/contracts/oracle_contract/oracle.nef old mode 100755 new mode 100644 similarity index 100% rename from pkg/core/test_data/oracle_contract/oracle.nef rename to internal/contracts/oracle_contract/oracle.nef diff --git a/pkg/core/blockchain_core_test.go b/pkg/core/blockchain_core_test.go index 808c997c4..93791d447 100644 --- a/pkg/core/blockchain_core_test.go +++ b/pkg/core/blockchain_core_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" @@ -1118,7 +1119,7 @@ func TestVerifyTx(t *testing.T) { func TestVerifyHashAgainstScript(t *testing.T) { bc := newTestChain(t) - cs, csInvalid := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, csInvalid := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid)) @@ -1695,7 +1696,7 @@ func TestRemoveUntraceable(t *testing.T) { func TestInvalidNotification(t *testing.T) { bc := newTestChain(t) - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack") @@ -1709,7 +1710,7 @@ func TestInvalidNotification(t *testing.T) { func TestMPTDeleteNoKey(t *testing.T) { bc := newTestChain(t) - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key") require.NoError(t, err) diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index 07771e534..f46b04e2b 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -1,15 +1,13 @@ package core import ( - "encoding/json" "errors" "fmt" "math" "math/big" - "os" - "path/filepath" "testing" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" @@ -39,7 +37,6 @@ import ( "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" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -235,7 +232,7 @@ func TestRuntimeGetNotifications(t *testing.T) { func TestRuntimeGetInvocationCounter(t *testing.T) { v, ic, bc := createVM(t) - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) ic.Invocations[hash.Hash160([]byte{2})] = 42 @@ -661,432 +658,6 @@ func createVMAndContractState(t testing.TB) (*vm.VM, *state.Contract, *interop.C return v, contractState, context, chain } -var ( - helper1ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper1.nef") - helper1ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper1.manifest.json") - helper2ContractNEFPath = filepath.Join("test_data", "management_helper", "management_helper2.nef") - helper2ContractManifestPath = filepath.Join("test_data", "management_helper", "management_helper2.manifest.json") -) - -// TestGenerateManagementHelperContracts generates 2 helper contracts second of which is -// allowed to call the first. It uses test chain to define Management and StdLib -// native hashes and saves generated NEF and manifest to ../test_data/management_contract folder. -// Set `saveState` flag to true and run the test to rewrite NEF and manifest files. -func TestGenerateManagementHelperContracts(t *testing.T) { - const saveState = false - - bc := newTestChain(t) - mgmtHash := bc.contracts.Management.Hash - stdHash := bc.contracts.Std.Hash - neoHash := bc.contracts.NEO.Hash - singleChainValidator := testchain.PrivateKey(testchain.IDToOrder(0)) - acc := wallet.NewAccountFromPrivateKey(singleChainValidator) - require.NoError(t, acc.ConvertMultisig(1, keys.PublicKeys{singleChainValidator.PublicKey()})) - singleChainValidatorHash := acc.Contract.ScriptHash() - - w := io.NewBufBinWriter() - emit.Opcodes(w.BinWriter, opcode.ABORT) - addOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET) - addMultiOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET) - ret7Off := w.Len() - emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET) - dropOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET) - initOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET) - add3Off := w.Len() - emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET) - invalidRetOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET) - justRetOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.RET) - verifyOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, - opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) - deployOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3) - emit.String(w.BinWriter, "create") // 8 bytes - emit.Int(w.BinWriter, 2) // 1 byte - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte - emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) - emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes - emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+3, opcode.RET) - emit.String(w.BinWriter, "update") // 8 bytes - emit.Int(w.BinWriter, 2) // 1 byte - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte - emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) - emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes - emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) - putValOff := w.Len() - emit.String(w.BinWriter, "initial") - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) - emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) - emit.Opcodes(w.BinWriter, opcode.RET) - getValOff := w.Len() - emit.String(w.BinWriter, "initial") - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) - emit.Syscall(w.BinWriter, interopnames.SystemStorageGet) - emit.Opcodes(w.BinWriter, opcode.RET) - delValOff := w.Len() - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) - emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete) - emit.Opcodes(w.BinWriter, opcode.RET) - onNEP17PaymentOff := w.Len() - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) - emit.Int(w.BinWriter, 4) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.String(w.BinWriter, "LastPayment") - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) - emit.Opcodes(w.BinWriter, opcode.RET) - onNEP11PaymentOff := w.Len() - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash) - emit.Int(w.BinWriter, 5) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.String(w.BinWriter, "LostPayment") - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) - emit.Opcodes(w.BinWriter, opcode.RET) - update3Off := w.Len() - emit.Int(w.BinWriter, 3) - emit.Opcodes(w.BinWriter, opcode.JMP, 2+1) - updateOff := w.Len() - emit.Int(w.BinWriter, 2) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All) - emit.Opcodes(w.BinWriter, opcode.DROP) - emit.Opcodes(w.BinWriter, opcode.RET) - destroyOff := w.Len() - emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All) - emit.Opcodes(w.BinWriter, opcode.DROP) - emit.Opcodes(w.BinWriter, opcode.RET) - invalidStackOff := w.Len() - emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item - emit.Opcodes(w.BinWriter, opcode.RET) - callT0Off := w.Len() - emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET) - callT1Off := w.Len() - emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET) - callT2Off := w.Len() - emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET) - burnGasOff := w.Len() - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas) - emit.Opcodes(w.BinWriter, opcode.RET) - invocCounterOff := w.Len() - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter) - emit.Opcodes(w.BinWriter, opcode.RET) - - script := w.Bytes() - m := manifest.NewManifest("TestMain") - m.ABI.Methods = []manifest.Method{ - { - Name: "add", - Offset: addOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("addend1", smartcontract.IntegerType), - manifest.NewParameter("addend2", smartcontract.IntegerType), - }, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "add", - Offset: addMultiOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("addend1", smartcontract.IntegerType), - manifest.NewParameter("addend2", smartcontract.IntegerType), - manifest.NewParameter("addend3", smartcontract.IntegerType), - }, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "ret7", - Offset: ret7Off, - Parameters: []manifest.Parameter{}, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "drop", - Offset: dropOff, - ReturnType: smartcontract.VoidType, - }, - { - Name: manifest.MethodInit, - Offset: initOff, - ReturnType: smartcontract.VoidType, - }, - { - Name: "add3", - Offset: add3Off, - Parameters: []manifest.Parameter{ - manifest.NewParameter("addend", smartcontract.IntegerType), - }, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "invalidReturn", - Offset: invalidRetOff, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "justReturn", - Offset: justRetOff, - ReturnType: smartcontract.VoidType, - }, - { - Name: manifest.MethodVerify, - Offset: verifyOff, - ReturnType: smartcontract.BoolType, - }, - { - Name: manifest.MethodDeploy, - Offset: deployOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("data", smartcontract.AnyType), - manifest.NewParameter("isUpdate", smartcontract.BoolType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "getValue", - Offset: getValOff, - ReturnType: smartcontract.StringType, - }, - { - Name: "putValue", - Offset: putValOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("value", smartcontract.StringType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "delValue", - Offset: delValOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("key", smartcontract.StringType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: manifest.MethodOnNEP11Payment, - Offset: onNEP11PaymentOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("from", smartcontract.Hash160Type), - manifest.NewParameter("amount", smartcontract.IntegerType), - manifest.NewParameter("tokenid", smartcontract.ByteArrayType), - manifest.NewParameter("data", smartcontract.AnyType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: manifest.MethodOnNEP17Payment, - Offset: onNEP17PaymentOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("from", smartcontract.Hash160Type), - manifest.NewParameter("amount", smartcontract.IntegerType), - manifest.NewParameter("data", smartcontract.AnyType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "update", - Offset: updateOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("nef", smartcontract.ByteArrayType), - manifest.NewParameter("manifest", smartcontract.ByteArrayType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "update", - Offset: update3Off, - Parameters: []manifest.Parameter{ - manifest.NewParameter("nef", smartcontract.ByteArrayType), - manifest.NewParameter("manifest", smartcontract.ByteArrayType), - manifest.NewParameter("data", smartcontract.AnyType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "destroy", - Offset: destroyOff, - ReturnType: smartcontract.VoidType, - }, - { - Name: "invalidStack", - Offset: invalidStackOff, - ReturnType: smartcontract.VoidType, - }, - { - Name: "callT0", - Offset: callT0Off, - Parameters: []manifest.Parameter{ - manifest.NewParameter("address", smartcontract.Hash160Type), - }, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "callT1", - Offset: callT1Off, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "callT2", - Offset: callT2Off, - ReturnType: smartcontract.IntegerType, - }, - { - Name: "burnGas", - Offset: burnGasOff, - Parameters: []manifest.Parameter{ - manifest.NewParameter("amount", smartcontract.IntegerType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "invocCounter", - Offset: invocCounterOff, - ReturnType: smartcontract.IntegerType, - }, - } - m.Permissions = make([]manifest.Permission, 2) - m.Permissions[0].Contract.Type = manifest.PermissionHash - m.Permissions[0].Contract.Value = bc.contracts.NEO.Hash - m.Permissions[0].Methods.Add("balanceOf") - - m.Permissions[1].Contract.Type = manifest.PermissionHash - m.Permissions[1].Contract.Value = util.Uint160{} - m.Permissions[1].Methods.Add("method") - - // Generate NEF file. - ne, err := nef.NewFile(script) - if err != nil { - panic(err) - } - ne.Tokens = []nef.MethodToken{ - { - Hash: neoHash, - Method: "balanceOf", - ParamCount: 1, - HasReturn: true, - CallFlag: callflag.ReadStates, - }, - { - Hash: util.Uint160{}, - Method: "method", - HasReturn: true, - CallFlag: callflag.ReadStates, - }, - } - ne.Checksum = ne.CalculateChecksum() - - // Write first NEF file. - bytes, err := ne.Bytes() - require.NoError(t, err) - if saveState { - err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm) - require.NoError(t, err) - } - - // Write first manifest file. - mData, err := json.Marshal(m) - require.NoError(t, err) - if saveState { - err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm) - require.NoError(t, err) - } - - // Create hash of the first contract assuming that sender is single-chain validator. - h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name) - - currScript := []byte{byte(opcode.RET)} - m = manifest.NewManifest("TestAux") - perm := manifest.NewPermission(manifest.PermissionHash, h) - perm.Methods.Add("add") - perm.Methods.Add("drop") - perm.Methods.Add("add3") - perm.Methods.Add("invalidReturn") - perm.Methods.Add("justReturn") - perm.Methods.Add("getValue") - m.Permissions = append(m.Permissions, *perm) - ne, err = nef.NewFile(currScript) - if err != nil { - panic(err) - } - - // Write second NEF file. - bytes, err = ne.Bytes() - require.NoError(t, err) - if saveState { - err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm) - require.NoError(t, err) - } - - // Write second manifest file. - mData, err = json.Marshal(m) - require.NoError(t, err) - if saveState { - err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm) - require.NoError(t, err) - } - - require.False(t, saveState) -} - -// getTestContractState returns 2 contracts second of which is allowed to call the first. -func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) { - errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate") - - neBytes, err := os.ReadFile(helper1ContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound)) - ne, err := nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err := os.ReadFile(helper1ContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound)) - m := &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - cs1 := &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Manifest: *m, - ID: id1, - }, - } - - neBytes, err = os.ReadFile(helper2ContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound)) - ne, err = nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err = os.ReadFile(helper2ContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound)) - m = &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - // Retrieve hash of the first contract from the permissions of the second contract. - require.Equal(t, 1, len(m.Permissions)) - require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type) - cs1.Hash = m.Permissions[0].Contract.Hash() - - cs2 := &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Manifest: *m, - ID: id2, - Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name), - }, - } - - return cs1, cs2 -} - func loadScript(ic *interop.Context, script []byte, args ...interface{}) { ic.SpawnVM() ic.VM.LoadScriptWithFlags(script, callflag.AllowCall) @@ -1108,7 +679,7 @@ func loadScriptWithHashAndFlags(ic *interop.Context, script []byte, hash util.Ui func TestContractCall(t *testing.T) { _, ic, bc := createVM(t) - cs, currCs := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) @@ -1500,7 +1071,7 @@ func TestRuntimeCheckWitness(t *testing.T) { func TestLoadToken(t *testing.T) { bc := newTestChain(t) - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) t.Run("good", func(t *testing.T) { @@ -1543,7 +1114,7 @@ func TestRuntimeGetNetwork(t *testing.T) { func TestRuntimeBurnGas(t *testing.T) { bc := newTestChain(t) - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) const sysFee = 2_000000 diff --git a/pkg/core/native/native_test/management_test.go b/pkg/core/native/native_test/management_test.go index eb4fa8872..c34b1c8d8 100644 --- a/pkg/core/native/native_test/management_test.go +++ b/pkg/core/native/native_test/management_test.go @@ -3,17 +3,14 @@ package native_test import ( "bytes" "encoding/json" - "errors" "fmt" - "os" - "path/filepath" "testing" - "github.com/nspcc-dev/neo-go/pkg/core/storage" - + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/pkg/core/chaindump" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/keys" "github.com/nspcc-dev/neo-go/pkg/io" @@ -23,20 +20,12 @@ import ( "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/nef" - "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/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) -var ( - helper1ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.nef") - helper1ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper1.manifest.json") - helper2ContractNEFPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.nef") - helper2ContractManifestPath = filepath.Join("..", "..", "test_data", "management_helper", "management_helper2.manifest.json") -) - func newManagementClient(t *testing.T) *neotest.ContractInvoker { return newNativeClient(t, nativenames.Management) } @@ -45,62 +34,11 @@ func TestManagement_MinimumDeploymentFee(t *testing.T) { testGetSet(t, newManagementClient(t), "MinimumDeploymentFee", 10_00000000, 0, 0) } -// getTestContractState returns 2 contracts second of which is allowed to call the first. -func getTestContractState(t *testing.T, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) { - errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate") - - neBytes, err := os.ReadFile(helper1ContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound)) - ne, err := nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err := os.ReadFile(helper1ContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound)) - m := &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - cs1 := &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Manifest: *m, - ID: id1, - }, - } - - neBytes, err = os.ReadFile(helper2ContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound)) - ne, err = nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err = os.ReadFile(helper2ContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound)) - m = &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - // Retrieve hash of the first contract from the permissions of the second contract. - require.Equal(t, 1, len(m.Permissions)) - require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type) - cs1.Hash = m.Permissions[0].Contract.Hash() - - cs2 := &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Manifest: *m, - ID: id2, - Hash: state.CreateContractHash(sender2, ne.Checksum, m.Name), - }, - } - - return cs1, cs2 -} - func TestManagement_ContractDeploy(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.Committee.ScriptHash()) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.Committee.ScriptHash()) manifestBytes, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nefBytes, err := cs1.NEF.Bytes() @@ -301,7 +239,7 @@ func TestManagement_StartFromHeight(t *testing.T) { c := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management)) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) manifestBytes, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nefBytes, err := cs1.NEF.Bytes() @@ -329,7 +267,7 @@ func TestManagement_DeployManifestOverflow(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) manif1, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nef1, err := nef.NewFile(cs1.NEF.Script) @@ -359,7 +297,7 @@ func TestManagement_ContractDeployAndUpdateWithParameter(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} cs1.ID = 1 cs1.Hash = state.CreateContractHash(c.CommitteeHash, cs1.NEF.Checksum, cs1.Manifest.Name) @@ -400,7 +338,7 @@ func TestManagement_ContractUpdate(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) // Allow calling management contract. cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} manifestBytes, err := json.Marshal(cs1.Manifest) @@ -535,7 +473,7 @@ func TestManagement_GetContract(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) manifestBytes, err := json.Marshal(cs1.Manifest) require.NoError(t, err) nefBytes, err := cs1.NEF.Bytes() @@ -560,7 +498,7 @@ func TestManagement_ContractDestroy(t *testing.T) { c := newManagementClient(t) managementInvoker := c.WithSigners(c.Committee) - cs1, _ := getTestContractState(t, 1, 2, c.CommitteeHash) + cs1, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, c.CommitteeHash) // Allow calling management contract. cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} manifestBytes, err := json.Marshal(cs1.Manifest) diff --git a/pkg/core/native/native_test/neo_test.go b/pkg/core/native/native_test/neo_test.go index 6da70d52e..d31dd4bab 100644 --- a/pkg/core/native/native_test/neo_test.go +++ b/pkg/core/native/native_test/neo_test.go @@ -7,6 +7,7 @@ import ( "sort" "testing" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" @@ -276,7 +277,7 @@ func TestNEO_TransferOnPayment(t *testing.T) { e := neoValidatorsInvoker.Executor managementValidatorsInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) - cs, _ := getTestContractState(t, 1, 2, e.CommitteeHash) + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, e.CommitteeHash) cs.Hash = state.CreateContractHash(e.Validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash manifB, err := json.Marshal(cs.Manifest) require.NoError(t, err) diff --git a/pkg/core/native/native_test/oracle_test.go b/pkg/core/native/native_test/oracle_test.go index 8b7ae6a4a..77247f80b 100644 --- a/pkg/core/native/native_test/oracle_test.go +++ b/pkg/core/native/native_test/oracle_test.go @@ -2,32 +2,28 @@ package native_test import ( "encoding/json" - "errors" - "fmt" "math" "math/big" - "os" "path/filepath" "strings" "testing" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neotest" "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/nef" - "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/stackitem" "github.com/stretchr/testify/require" ) +var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", "contracts") + func newOracleClient(t *testing.T) *neotest.ContractInvoker { return newNativeClient(t, nativenames.Oracle) } @@ -36,36 +32,6 @@ func TestGetSetPrice(t *testing.T) { testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64) } -// getOracleContractState reads pre-compiled oracle contract generated by -// TestGenerateOracleContract and returns its state. -func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract { - var ( - oracleContractNEFPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.nef") - oracleContractManifestPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.manifest.json") - ) - errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate") - - neBytes, err := os.ReadFile(oracleContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound)) - ne, err := nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err := os.ReadFile(oracleContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound)) - m := &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - return &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), - Manifest: *m, - ID: id, - }, - } -} - func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker, url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) { var filtItem interface{} @@ -86,7 +52,7 @@ func TestOracle_Request(t *testing.T) { designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation)) gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas)) - cs := getOracleContractState(t, e.Validator.ScriptHash(), 1) + cs := contracts.GetOracleContractState(t, pathToInternalContracts, e.Validator.ScriptHash(), 1) nBytes, err := cs.NEF.Bytes() require.NoError(t, err) mBytes, err := json.Marshal(cs.Manifest) diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 563f40b9b..6d7241f19 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/fee" @@ -302,7 +303,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { } } - cs, _ := getTestContractState(t, 4, 5, random.Uint160()) // sender and IDs are not important for the test + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs)) baseFee := chain.GetBaseExecFee() diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index b33bf78dd..4f5b898ac 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -2,12 +2,10 @@ package core import ( "bytes" - "encoding/json" "errors" "fmt" gio "io" "net/http" - "os" "path" "path/filepath" "strings" @@ -15,22 +13,16 @@ import ( "testing" "time" + "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/services/oracle" - "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/nef" "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/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,132 +30,10 @@ import ( ) var ( - oracleModulePath = filepath.Join("..", "services", "oracle") - oracleContractNEFPath = filepath.Join("test_data", "oracle_contract", "oracle.nef") - oracleContractManifestPath = filepath.Join("test_data", "oracle_contract", "oracle.manifest.json") + oracleModulePath = filepath.Join("..", "services", "oracle") + pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts") ) -// TestGenerateOracleContract generates helper contract that is able to call -// native Oracle contract and has callback method. It uses test chain to define -// Oracle and StdLib native hashes and saves generated NEF and manifest to ... folder. -// Set `saveState` flag to true and run the test to rewrite NEF and manifest files. -func TestGenerateOracleContract(t *testing.T) { - const saveState = false - - bc := newTestChain(t) - oracleHash := bc.contracts.Oracle.Hash - stdHash := bc.contracts.Std.Hash - - w := io.NewBufBinWriter() - emit.Int(w.BinWriter, 5) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.Int(w.BinWriter, int64(callflag.All)) - emit.String(w.BinWriter, "request") - emit.Bytes(w.BinWriter, oracleHash.BytesBE()) - emit.Syscall(w.BinWriter, interopnames.SystemContractCall) - emit.Opcodes(w.BinWriter, opcode.DROP) - emit.Opcodes(w.BinWriter, opcode.RET) - - // `handle` method aborts if len(userData) == 2 and does NOT perform witness checks - // for the sake of contract code simplicity (the contract is used in multiple testchains). - offset := w.Len() - - emit.Opcodes(w.BinWriter, opcode.OVER) - emit.Opcodes(w.BinWriter, opcode.SIZE) - emit.Int(w.BinWriter, 2) - emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3}) - emit.Opcodes(w.BinWriter, opcode.ABORT) - emit.Int(w.BinWriter, 4) // url, userData, code, result - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`) - emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`) - emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes - emit.String(w.BinWriter, "lastOracleResponse") - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) - emit.Syscall(w.BinWriter, interopnames.SystemStoragePut) - emit.Opcodes(w.BinWriter, opcode.RET) - - m := manifest.NewManifest("TestOracle") - m.ABI.Methods = []manifest.Method{ - { - Name: "requestURL", - Offset: 0, - Parameters: []manifest.Parameter{ - manifest.NewParameter("url", smartcontract.StringType), - manifest.NewParameter("filter", smartcontract.StringType), - manifest.NewParameter("callback", smartcontract.StringType), - manifest.NewParameter("userData", smartcontract.AnyType), - manifest.NewParameter("gasForResponse", smartcontract.IntegerType), - }, - ReturnType: smartcontract.VoidType, - }, - { - Name: "handle", - Offset: offset, - Parameters: []manifest.Parameter{ - manifest.NewParameter("url", smartcontract.StringType), - manifest.NewParameter("userData", smartcontract.AnyType), - manifest.NewParameter("code", smartcontract.IntegerType), - manifest.NewParameter("result", smartcontract.ByteArrayType), - }, - ReturnType: smartcontract.VoidType, - }, - } - - perm := manifest.NewPermission(manifest.PermissionHash, oracleHash) - perm.Methods.Add("request") - m.Permissions = append(m.Permissions, *perm) - - // Generate NEF file. - script := w.Bytes() - ne, err := nef.NewFile(script) - require.NoError(t, err) - - // Write NEF file. - bytes, err := ne.Bytes() - require.NoError(t, err) - if saveState { - err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm) - require.NoError(t, err) - } - - // Write manifest file. - mData, err := json.Marshal(m) - require.NoError(t, err) - if saveState { - err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm) - require.NoError(t, err) - } - - require.False(t, saveState) -} - -// getOracleContractState reads pre-compiled oracle contract generated by -// TestGenerateOracleContract and returns its state. -func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract { - errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate") - - neBytes, err := os.ReadFile(oracleContractNEFPath) - require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound)) - ne, err := nef.FileFromBytes(neBytes) - require.NoError(t, err) - - mBytes, err := os.ReadFile(oracleContractManifestPath) - require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound)) - m := &manifest.Manifest{} - err = json.Unmarshal(mBytes, m) - require.NoError(t, err) - - return &state.Contract{ - ContractBase: state.ContractBase{ - NEF: ne, - Hash: state.CreateContractHash(sender, ne.Checksum, m.Name), - Manifest: *m, - ID: id, - }, - } -} - func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain, url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 { var filtItem interface{} @@ -274,7 +144,7 @@ func TestOracle(t *testing.T) { orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) - cs := getOracleContractState(t, util.Uint160{}, 42) + cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) @@ -442,7 +312,7 @@ func TestOracleFull(t *testing.T) { orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } bc.SetOracle(orc) - cs := getOracleContractState(t, util.Uint160{}, 42) + cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) go bc.Run() @@ -467,7 +337,7 @@ func TestNotYetRunningOracle(t *testing.T) { orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } bc.SetOracle(orc) - cs := getOracleContractState(t, util.Uint160{}, 42) + cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) go bc.Run() diff --git a/pkg/core/test_data/management_helper/README.md b/pkg/core/test_data/management_helper/README.md deleted file mode 100644 index 853e322f6..000000000 --- a/pkg/core/test_data/management_helper/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## Management helper contracts - -Management helper contracts NEF and manifest files are generated automatically by -`TestGenerateManagementHelperContracts` and are used in tests. Do not modify these files manually. -To regenerate these files: - -1. Open `TestGenerateManagementHelperContracts` and set `saveState` flag to `true`. -2. Run `TestGenerateManagementHelperContracts`. -3. Set `saveState` back to `false`. \ No newline at end of file diff --git a/pkg/core/test_data/oracle_contract/README.md b/pkg/core/test_data/oracle_contract/README.md deleted file mode 100644 index 83fad42a7..000000000 --- a/pkg/core/test_data/oracle_contract/README.md +++ /dev/null @@ -1,9 +0,0 @@ -## Oracle helper contract - -Oracle helper contract NEF and manifest files are generated automatically by -`TestGenerateOracleContract` and are used in tests. Do not modify these files manually. -To regenerate these files: - -1. Open `TestGenerateOracleContract` and set `saveState` flag to `true`. -2. Run `TestGenerateOracleContract`. -3. Set `saveState` back to `false`. \ No newline at end of file From 9dd43583e0b2f9237311d7406dd534ac87105823 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 16 Mar 2022 12:41:55 +0300 Subject: [PATCH 07/15] core: fix helper test contract Second management helper contract should have valid ABI (at least one method should be mentioned in the ABI). --- internal/contracts/contracts_test.go | 7 +++++++ .../management_helper1.manifest.json | 2 +- .../management_helper/management_helper1.nef | Bin 506 -> 505 bytes .../management_helper2.manifest.json | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/contracts/contracts_test.go b/internal/contracts/contracts_test.go index 77cd71e18..c757f9651 100644 --- a/internal/contracts/contracts_test.go +++ b/internal/contracts/contracts_test.go @@ -466,6 +466,13 @@ func generateManagementHelperContracts(t *testing.T, saveState bool) { currScript := []byte{byte(opcode.RET)} m = manifest.NewManifest("TestAux") + m.ABI.Methods = []manifest.Method{ + { + Name: "simpleMethod", + Offset: 0, + ReturnType: smartcontract.VoidType, + }, + } perm := manifest.NewPermission(manifest.PermissionHash, h) perm.Methods.Add("add") perm.Methods.Add("drop") diff --git a/internal/contracts/management_helper/management_helper1.manifest.json b/internal/contracts/management_helper/management_helper1.manifest.json index baea0b094..b7b549053 100644 --- a/internal/contracts/management_helper/management_helper1.manifest.json +++ b/internal/contracts/management_helper/management_helper1.manifest.json @@ -1 +1 @@ -{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack","offset":325,"parameters":null,"returntype":"Void","safe":false},{"name":"callT0","offset":335,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":341,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":345,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":349,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":355,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack","offset":324,"parameters":null,"returntype":"Void","safe":false},{"name":"callT0","offset":334,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":340,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":344,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":348,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":354,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/internal/contracts/management_helper/management_helper1.nef b/internal/contracts/management_helper/management_helper1.nef index bccf14cdc1a994f5e1753af71b31e28194ab6fc2..7f37f6ea2df30427ac0d0fd27cfb1b0f9393b5e8 100644 GIT binary patch delta 23 fcmeyx{F8Y?H)F=e9$7}lLz7oCdb6yVAH5a;bNUH= delta 24 gcmey#{EK-)H)H0;9$7{vfdi9QF?zH3>iOCL0Cpz`Jpcdz diff --git a/internal/contracts/management_helper/management_helper2.manifest.json b/internal/contracts/management_helper/management_helper2.manifest.json index fb2614616..a02302fc0 100644 --- a/internal/contracts/management_helper/management_helper2.manifest.json +++ b/internal/contracts/management_helper/management_helper2.manifest.json @@ -1 +1 @@ -{"name":"TestAux","abi":{"methods":[],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0x00ecaa2f079b65e3b31572e4c2c160a1abd02997","methods":["add","drop","add3","invalidReturn","justReturn","getValue"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"name":"TestAux","abi":{"methods":[{"name":"simpleMethod","offset":0,"parameters":null,"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xf2bf3585b5b3287fd65bc0451b348714b4fdb908","methods":["add","drop","add3","invalidReturn","justReturn","getValue"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file From e0ab4ec6f0e53a6764bd9528dde8a6773850e997 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 16 Mar 2022 18:07:30 +0300 Subject: [PATCH 08/15] core: adjust helper test contract Method is allowed to have single return value on stack after its invocation. Make sure that test contract follows this rule. --- internal/contracts/contracts_test.go | 17 ++++++++++++----- .../management_helper1.manifest.json | 2 +- .../management_helper/management_helper1.nef | Bin 505 -> 506 bytes .../management_helper2.manifest.json | 2 +- pkg/core/blockchain_core_test.go | 10 +++++++--- 5 files changed, 21 insertions(+), 10 deletions(-) diff --git a/internal/contracts/contracts_test.go b/internal/contracts/contracts_test.go index c757f9651..9b6fea22d 100644 --- a/internal/contracts/contracts_test.go +++ b/internal/contracts/contracts_test.go @@ -226,9 +226,11 @@ func generateManagementHelperContracts(t *testing.T, saveState bool) { emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All) emit.Opcodes(w.BinWriter, opcode.DROP) emit.Opcodes(w.BinWriter, opcode.RET) - invalidStackOff := w.Len() + invalidStack1Off := w.Len() emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array - emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item + emit.Opcodes(w.BinWriter, opcode.RET) + invalidStack2Off := w.Len() + emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item emit.Opcodes(w.BinWriter, opcode.RET) callT0Off := w.Len() emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET) @@ -380,9 +382,14 @@ func generateManagementHelperContracts(t *testing.T, saveState bool) { ReturnType: smartcontract.VoidType, }, { - Name: "invalidStack", - Offset: invalidStackOff, - ReturnType: smartcontract.VoidType, + Name: "invalidStack1", + Offset: invalidStack1Off, + ReturnType: smartcontract.AnyType, + }, + { + Name: "invalidStack2", + Offset: invalidStack2Off, + ReturnType: smartcontract.AnyType, }, { Name: "callT0", diff --git a/internal/contracts/management_helper/management_helper1.manifest.json b/internal/contracts/management_helper/management_helper1.manifest.json index b7b549053..181b16b7c 100644 --- a/internal/contracts/management_helper/management_helper1.manifest.json +++ b/internal/contracts/management_helper/management_helper1.manifest.json @@ -1 +1 @@ -{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack","offset":324,"parameters":null,"returntype":"Void","safe":false},{"name":"callT0","offset":334,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":340,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":344,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":348,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":354,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"name":"TestMain","abi":{"methods":[{"name":"add","offset":1,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"add","offset":3,"parameters":[{"name":"addend1","type":"Integer"},{"name":"addend2","type":"Integer"},{"name":"addend3","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"ret7","offset":6,"parameters":[],"returntype":"Integer","safe":false},{"name":"drop","offset":8,"parameters":null,"returntype":"Void","safe":false},{"name":"_initialize","offset":10,"parameters":null,"returntype":"Void","safe":false},{"name":"add3","offset":15,"parameters":[{"name":"addend","type":"Integer"}],"returntype":"Integer","safe":false},{"name":"invalidReturn","offset":18,"parameters":null,"returntype":"Integer","safe":false},{"name":"justReturn","offset":21,"parameters":null,"returntype":"Void","safe":false},{"name":"verify","offset":22,"parameters":null,"returntype":"Boolean","safe":false},{"name":"_deploy","offset":27,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"getValue","offset":158,"parameters":null,"returntype":"String","safe":false},{"name":"putValue","offset":138,"parameters":[{"name":"value","type":"String"}],"returntype":"Void","safe":false},{"name":"delValue","offset":178,"parameters":[{"name":"key","type":"String"}],"returntype":"Void","safe":false},{"name":"onNEP11Payment","offset":215,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"tokenid","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"onNEP17Payment","offset":189,"parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"update","offset":244,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"update","offset":241,"parameters":[{"name":"nef","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false},{"name":"destroy","offset":284,"parameters":null,"returntype":"Void","safe":false},{"name":"invalidStack1","offset":324,"parameters":null,"returntype":"Any","safe":false},{"name":"invalidStack2","offset":329,"parameters":null,"returntype":"Any","safe":false},{"name":"callT0","offset":335,"parameters":[{"name":"address","type":"Hash160"}],"returntype":"Integer","safe":false},{"name":"callT1","offset":341,"parameters":null,"returntype":"Integer","safe":false},{"name":"callT2","offset":345,"parameters":null,"returntype":"Integer","safe":false},{"name":"burnGas","offset":349,"parameters":[{"name":"amount","type":"Integer"}],"returntype":"Void","safe":false},{"name":"invocCounter","offset":355,"parameters":null,"returntype":"Integer","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","methods":["balanceOf"]},{"contract":"0x0000000000000000000000000000000000000000","methods":["method"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file diff --git a/internal/contracts/management_helper/management_helper1.nef b/internal/contracts/management_helper/management_helper1.nef index 7f37f6ea2df30427ac0d0fd27cfb1b0f9393b5e8..85288654f2c42bb4ded61bfb58acbc60a729bb2f 100644 GIT binary patch delta 23 fcmey#{EK-)H)H0;9#2L_hsjqN6 Date: Wed, 16 Mar 2022 18:08:50 +0300 Subject: [PATCH 09/15] core: export GetMaxNotValidBeforeDelta blockchain API Mostly for tests, but it can also be useful as a separate API. --- pkg/core/blockchain.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 3694d0459..a7bc1f4c7 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -2342,6 +2342,14 @@ func (bc *Blockchain) GetMaxVerificationGAS() int64 { return bc.contracts.Policy.GetMaxVerificationGas(bc.dao) } +// GetMaxNotValidBeforeDelta returns maximum NotValidBeforeDelta Notary limit. +func (bc *Blockchain) GetMaxNotValidBeforeDelta() uint32 { + if !bc.config.P2PSigExtensions { + panic("disallowed call to Notary") + } + return bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao) +} + // GetStoragePrice returns current storage price. func (bc *Blockchain) GetStoragePrice() int64 { if bc.BlockHeight() == 0 { From ff13af804d2460e6fb2a669f55b0b6dd688e9b2a Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 11 Mar 2022 16:26:22 +0300 Subject: [PATCH 10/15] core: adjust persist-related Blockchain tests `newTestChain` runs blockchain, so persist is likely already happened before the first test iteration. Explicit call to persist makes no sence here. --- pkg/core/blockchain_core_test.go | 66 +++++++++++--------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/pkg/core/blockchain_core_test.go b/pkg/core/blockchain_core_test.go index fc1025cf3..b06623738 100644 --- a/pkg/core/blockchain_core_test.go +++ b/pkg/core/blockchain_core_test.go @@ -232,19 +232,14 @@ func TestGetHeader(t *testing.T) { err := bc.AddBlock(block) assert.Nil(t, err) - // Test unpersisted and persisted access - for i := 0; i < 2; i++ { - hash := block.Hash() - header, err := bc.GetHeader(hash) - require.NoError(t, err) - assert.Equal(t, &block.Header, header) + hash := block.Hash() + header, err := bc.GetHeader(hash) + require.NoError(t, err) + assert.Equal(t, &block.Header, header) - b2 := bc.newBlock() - _, err = bc.GetHeader(b2.Hash()) - assert.Error(t, err) - _, err = bc.persist(false) - assert.NoError(t, err) - } + b2 := bc.newBlock() + _, err = bc.GetHeader(b2.Hash()) + assert.Error(t, err) } func TestGetBlock(t *testing.T) { @@ -252,16 +247,11 @@ func TestGetBlock(t *testing.T) { blocks, err := bc.genBlocks(100) require.NoError(t, err) - // Test unpersisted and persisted access - for j := 0; j < 2; j++ { - for i := 0; i < len(blocks); i++ { - block, err := bc.GetBlock(blocks[i].Hash()) - require.NoErrorf(t, err, "can't get block %d: %s, attempt %d", i, err, j) - assert.Equal(t, blocks[i].Index, block.Index) - assert.Equal(t, blocks[i].Hash(), block.Hash()) - } - _, err = bc.persist(false) - assert.NoError(t, err) + for i := 0; i < len(blocks); i++ { + block, err := bc.GetBlock(blocks[i].Hash()) + require.NoErrorf(t, err, "can't get block %d: %s", i, err) + assert.Equal(t, blocks[i].Index, block.Index) + assert.Equal(t, blocks[i].Hash(), block.Hash()) } t.Run("store only header", func(t *testing.T) { @@ -1315,16 +1305,11 @@ func TestHasBlock(t *testing.T) { blocks, err := bc.genBlocks(50) require.NoError(t, err) - // Test unpersisted and persisted access - for j := 0; j < 2; j++ { - for i := 0; i < len(blocks); i++ { - assert.True(t, bc.HasBlock(blocks[i].Hash())) - } - newBlock := bc.newBlock() - assert.False(t, bc.HasBlock(newBlock.Hash())) - _, err = bc.persist(true) - assert.NoError(t, err) + for i := 0; i < len(blocks); i++ { + assert.True(t, bc.HasBlock(blocks[i].Hash())) } + newBlock := bc.newBlock() + assert.False(t, bc.HasBlock(newBlock.Hash())) } func TestGetTransaction(t *testing.T) { @@ -1349,18 +1334,13 @@ func TestGetTransaction(t *testing.T) { txSize := io.GetVarSize(tx2) assert.Nil(t, bc.AddBlock(block)) - // Test unpersisted and persisted access - for j := 0; j < 2; j++ { - tx, height, err := bc.GetTransaction(block.Transactions[0].Hash()) - require.Nil(t, err) - assert.Equal(t, block.Index, height) - assert.Equal(t, txSize, tx.Size()) - assert.Equal(t, block.Transactions[0], tx) - assert.Equal(t, 1, io.GetVarSize(tx.Attributes)) - assert.Equal(t, 1, io.GetVarSize(tx.Scripts)) - _, err = bc.persist(true) - assert.NoError(t, err) - } + tx, height, err := bc.GetTransaction(block.Transactions[0].Hash()) + require.Nil(t, err) + assert.Equal(t, block.Index, height) + assert.Equal(t, txSize, tx.Size()) + assert.Equal(t, block.Transactions[0], tx) + assert.Equal(t, 1, io.GetVarSize(tx.Attributes)) + assert.Equal(t, 1, io.GetVarSize(tx.Scripts)) } func TestGetClaimable(t *testing.T) { From 59f3fa1ef1c8fc9a159f759dbfef9b0be6047ae8 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 18 Mar 2022 18:11:24 +0300 Subject: [PATCH 11/15] neotest: adapt framework to work with *testing.B --- pkg/core/native/native_test/common_test.go | 2 +- pkg/core/native/native_test/ledger_test.go | 2 +- pkg/neotest/basic.go | 58 +++++++++++----------- pkg/neotest/chain/chain.go | 29 ++++++----- pkg/neotest/client.go | 14 +++--- pkg/neotest/compile.go | 4 +- pkg/neotest/signer.go | 2 +- 7 files changed, 55 insertions(+), 56 deletions(-) diff --git a/pkg/core/native/native_test/common_test.go b/pkg/core/native/native_test/common_test.go index da37b24b8..bf9c900ce 100644 --- a/pkg/core/native/native_test/common_test.go +++ b/pkg/core/native/native_test/common_test.go @@ -90,7 +90,7 @@ func setNodesByRole(t *testing.T, designateInvoker *neotest.ContractInvoker, ok func checkNodeRoles(t *testing.T, designateInvoker *neotest.ContractInvoker, ok bool, r noderoles.Role, index uint32, res keys.PublicKeys) { if ok { - designateInvoker.InvokeAndCheck(t, func(t *testing.T, stack []stackitem.Item) { + designateInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { require.Equal(t, 1, len(stack)) arr := stack[0].Value().([]stackitem.Item) require.Equal(t, len(res), len(arr)) diff --git a/pkg/core/native/native_test/ledger_test.go b/pkg/core/native/native_test/ledger_test.go index a869a9de9..ab71dbee0 100644 --- a/pkg/core/native/native_test/ledger_test.go +++ b/pkg/core/native/native_test/ledger_test.go @@ -82,7 +82,7 @@ func TestLedger_GetTransactionFromBlock(t *testing.T) { ledgerInvoker.Invoke(t, e.Chain.BlockHeight(), "currentIndex") // Adds a block. b := e.GetBlockByIndex(t, int(e.Chain.BlockHeight())) - check := func(t *testing.T, stack []stackitem.Item) { + check := func(t testing.TB, stack []stackitem.Item) { require.Equal(t, 1, len(stack)) actual, ok := stack[0].Value().([]stackitem.Item) require.True(t, ok) diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index c5e820eac..4665f2ee1 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -36,7 +36,7 @@ type Executor struct { } // NewExecutor creates new executor instance from provided blockchain and committee. -func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, validator, committee Signer) *Executor { +func NewExecutor(t testing.TB, bc blockchainer.Blockchainer, validator, committee Signer) *Executor { checkMultiSigner(t, validator) checkMultiSigner(t, committee) @@ -50,28 +50,28 @@ func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, validator, committe } // TopBlock returns block with the highest index. -func (e *Executor) TopBlock(t *testing.T) *block.Block { +func (e *Executor) TopBlock(t testing.TB) *block.Block { b, err := e.Chain.GetBlock(e.Chain.GetHeaderHash(int(e.Chain.BlockHeight()))) require.NoError(t, err) return b } // NativeHash returns native contract hash by name. -func (e *Executor) NativeHash(t *testing.T, name string) util.Uint160 { +func (e *Executor) NativeHash(t testing.TB, name string) util.Uint160 { h, err := e.Chain.GetNativeContractScriptHash(name) require.NoError(t, err) return h } // ContractHash returns contract hash by ID. -func (e *Executor) ContractHash(t *testing.T, id int32) util.Uint160 { +func (e *Executor) ContractHash(t testing.TB, id int32) util.Uint160 { h, err := e.Chain.GetContractScriptHash(id) require.NoError(t, err) return h } // NativeID returns native contract ID by name. -func (e *Executor) NativeID(t *testing.T, name string) int32 { +func (e *Executor) NativeID(t testing.TB, name string) int32 { h := e.NativeHash(t, name) cs := e.Chain.GetContractState(h) require.NotNil(t, cs) @@ -79,7 +79,7 @@ func (e *Executor) NativeID(t *testing.T, name string) int32 { } // NewUnsignedTx creates new unsigned transaction which invokes method of contract with hash. -func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { +func (e *Executor) NewUnsignedTx(t testing.TB, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { w := io.NewBufBinWriter() emit.AppCall(w.BinWriter, hash, method, callflag.All, args...) require.NoError(t, w.Err) @@ -93,14 +93,14 @@ func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string, // NewTx creates new transaction which invokes contract method. // Transaction is signed with signer. -func (e *Executor) NewTx(t *testing.T, signers []Signer, +func (e *Executor) NewTx(t testing.TB, signers []Signer, hash util.Uint160, method string, args ...interface{}) *transaction.Transaction { tx := e.NewUnsignedTx(t, hash, method, args...) return e.SignTx(t, tx, -1, signers...) } // SignTx signs a transaction using provided signers. -func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction { +func (e *Executor) SignTx(t testing.TB, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction { for _, acc := range signers { tx.Signers = append(tx.Signers, transaction.Signer{ Account: acc.ScriptHash(), @@ -118,7 +118,7 @@ func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int6 // NewAccount returns new signer holding 100.0 GAS (or given amount is specified). // This method advances the chain by one block with a transfer transaction. -func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer { +func (e *Executor) NewAccount(t testing.TB, expectedGASBalance ...int64) Signer { acc, err := wallet.NewAccount() require.NoError(t, err) @@ -138,7 +138,7 @@ func (e *Executor) NewAccount(t *testing.T, expectedGASBalance ...int64) Signer // precalculated contract hash matches the actual one. // data is an optional argument to `_deploy`. // Returns hash of the deploy transaction. -func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) util.Uint256 { +func (e *Executor) DeployContract(t testing.TB, c *Contract, data interface{}) util.Uint256 { return e.DeployContractBy(t, e.Validator, c, data) } @@ -146,7 +146,7 @@ func (e *Executor) DeployContract(t *testing.T, c *Contract, data interface{}) u // It also checks that precalculated contract hash matches the actual one. // data is an optional argument to `_deploy`. // Returns hash of the deploy transaction. -func (e *Executor) DeployContractBy(t *testing.T, signer Signer, c *Contract, data interface{}) util.Uint256 { +func (e *Executor) DeployContractBy(t testing.TB, signer Signer, c *Contract, data interface{}) util.Uint256 { tx := NewDeployTxBy(t, e.Chain, signer, c, data) e.AddNewBlock(t, tx) e.CheckHalt(t, tx.Hash()) @@ -165,7 +165,7 @@ func (e *Executor) DeployContractBy(t *testing.T, signer Signer, c *Contract, da // DeployContractCheckFAULT compiles and deploys contract to bc using validator // account. It checks that deploy transaction FAULTed with the specified error. -func (e *Executor) DeployContractCheckFAULT(t *testing.T, c *Contract, data interface{}, errMessage string) { +func (e *Executor) DeployContractCheckFAULT(t testing.TB, c *Contract, data interface{}, errMessage string) { tx := e.NewDeployTx(t, e.Chain, c, data) e.AddNewBlock(t, tx) e.CheckFault(t, tx.Hash(), errMessage) @@ -173,7 +173,7 @@ func (e *Executor) DeployContractCheckFAULT(t *testing.T, c *Contract, data inte // InvokeScript adds transaction with the specified script to the chain and // returns its hash. It does no faults check. -func (e *Executor) InvokeScript(t *testing.T, script []byte, signers []Signer) util.Uint256 { +func (e *Executor) InvokeScript(t testing.TB, script []byte, signers []Signer) util.Uint256 { tx := transaction.New(script, 0) tx.Nonce = Nonce() tx.ValidUntilBlock = e.Chain.BlockHeight() + 1 @@ -184,20 +184,20 @@ func (e *Executor) InvokeScript(t *testing.T, script []byte, signers []Signer) u // InvokeScriptCheckHALT adds transaction with the specified script to the chain // and checks it's HALTed with the specified items on stack. -func (e *Executor) InvokeScriptCheckHALT(t *testing.T, script []byte, signers []Signer, stack ...stackitem.Item) { +func (e *Executor) InvokeScriptCheckHALT(t testing.TB, script []byte, signers []Signer, stack ...stackitem.Item) { hash := e.InvokeScript(t, script, signers) e.CheckHalt(t, hash, stack...) } // InvokeScriptCheckFAULT adds transaction with the specified script to the // chain and checks it's FAULTed with the specified error. -func (e *Executor) InvokeScriptCheckFAULT(t *testing.T, script []byte, signers []Signer, errMessage string) { +func (e *Executor) InvokeScriptCheckFAULT(t testing.TB, script []byte, signers []Signer, errMessage string) { hash := e.InvokeScript(t, script, signers) e.CheckFault(t, hash, errMessage) } // CheckHalt checks that transaction persisted with HALT state. -func (e *Executor) CheckHalt(t *testing.T, h util.Uint256, stack ...stackitem.Item) *state.AppExecResult { +func (e *Executor) CheckHalt(t testing.TB, h util.Uint256, stack ...stackitem.Item) *state.AppExecResult { aer, err := e.Chain.GetAppExecResults(h, trigger.Application) require.NoError(t, err) require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) @@ -209,7 +209,7 @@ func (e *Executor) CheckHalt(t *testing.T, h util.Uint256, stack ...stackitem.It // CheckFault checks that transaction persisted with FAULT state. // Raised exception is also checked to contain s as a substring. -func (e *Executor) CheckFault(t *testing.T, h util.Uint256, s string) { +func (e *Executor) CheckFault(t testing.TB, h util.Uint256, s string) { aer, err := e.Chain.GetAppExecResults(h, trigger.Application) require.NoError(t, err) require.Equal(t, vm.FaultState, aer[0].VMState) @@ -219,7 +219,7 @@ func (e *Executor) CheckFault(t *testing.T, h util.Uint256, s string) { // CheckTxNotificationEvent checks that specified event was emitted at the specified position // during transaction script execution. Negative index corresponds to backwards enumeration. -func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index int, expected state.NotificationEvent) { +func (e *Executor) CheckTxNotificationEvent(t testing.TB, h util.Uint256, index int, expected state.NotificationEvent) { aer, err := e.Chain.GetAppExecResults(h, trigger.Application) require.NoError(t, err) l := len(aer[0].Events) @@ -231,24 +231,24 @@ func (e *Executor) CheckTxNotificationEvent(t *testing.T, h util.Uint256, index } // CheckGASBalance ensures that provided account owns specified amount of GAS. -func (e *Executor) CheckGASBalance(t *testing.T, acc util.Uint160, expected *big.Int) { +func (e *Executor) CheckGASBalance(t testing.TB, acc util.Uint160, expected *big.Int) { actual := e.Chain.GetUtilityTokenBalance(acc) require.Equal(t, expected, actual, fmt.Errorf("invalid GAS balance: expected %s, got %s", expected.String(), actual.String())) } // EnsureGASBalance ensures that provided account owns amount of GAS that satisfies provided condition. -func (e *Executor) EnsureGASBalance(t *testing.T, acc util.Uint160, isOk func(balance *big.Int) bool) { +func (e *Executor) EnsureGASBalance(t testing.TB, acc util.Uint160, isOk func(balance *big.Int) bool) { actual := e.Chain.GetUtilityTokenBalance(acc) require.True(t, isOk(actual), fmt.Errorf("invalid GAS balance: got %s, condition is not satisfied", actual.String())) } // NewDeployTx returns new deployment tx for contract signed by committee. -func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction { +func (e *Executor) NewDeployTx(t testing.TB, bc blockchainer.Blockchainer, c *Contract, data interface{}) *transaction.Transaction { return NewDeployTxBy(t, bc, e.Validator, c, data) } // NewDeployTxBy returns new deployment tx for contract signed by the specified signer. -func NewDeployTxBy(t *testing.T, bc blockchainer.Blockchainer, signer Signer, c *Contract, data interface{}) *transaction.Transaction { +func NewDeployTxBy(t testing.TB, bc blockchainer.Blockchainer, signer Signer, c *Contract, data interface{}) *transaction.Transaction { rawManifest, err := json.Marshal(c.Manifest) require.NoError(t, err) @@ -292,7 +292,7 @@ func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, si } // NewUnsignedBlock creates new unsigned block from txs. -func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transaction) *block.Block { +func (e *Executor) NewUnsignedBlock(t testing.TB, txs ...*transaction.Transaction) *block.Block { lastBlock := e.TopBlock(t) b := &block.Block{ Header: block.Header{ @@ -315,7 +315,7 @@ func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transactio } // AddNewBlock creates a new block from provided transactions and adds it on bc. -func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *block.Block { +func (e *Executor) AddNewBlock(t testing.TB, txs ...*transaction.Transaction) *block.Block { b := e.NewUnsignedBlock(t, txs...) e.SignBlock(b) require.NoError(t, e.Chain.AddBlock(b)) @@ -323,7 +323,7 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b } // GenerateNewBlocks adds specified number of empty blocks to the chain. -func (e *Executor) GenerateNewBlocks(t *testing.T, count int) { +func (e *Executor) GenerateNewBlocks(t testing.TB, count int) { for i := 0; i < count; i++ { e.AddNewBlock(t) } @@ -337,7 +337,7 @@ func (e *Executor) SignBlock(b *block.Block) *block.Block { } // AddBlockCheckHalt is a convenient wrapper over AddBlock and CheckHalt. -func (e *Executor) AddBlockCheckHalt(t *testing.T, txs ...*transaction.Transaction) *block.Block { +func (e *Executor) AddBlockCheckHalt(t testing.TB, txs ...*transaction.Transaction) *block.Block { b := e.AddNewBlock(t, txs...) for _, tx := range txs { e.CheckHalt(t, tx.Hash()) @@ -370,14 +370,14 @@ func TestInvoke(bc blockchainer.Blockchainer, tx *transaction.Transaction) (*vm. } // GetTransaction returns transaction and its height by the specified hash. -func (e *Executor) GetTransaction(t *testing.T, h util.Uint256) (*transaction.Transaction, uint32) { +func (e *Executor) GetTransaction(t testing.TB, h util.Uint256) (*transaction.Transaction, uint32) { tx, height, err := e.Chain.GetTransaction(h) require.NoError(t, err) return tx, height } // GetBlockByIndex returns block by the specified index. -func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block { +func (e *Executor) GetBlockByIndex(t testing.TB, idx int) *block.Block { h := e.Chain.GetHeaderHash(idx) require.NotEmpty(t, h) b, err := e.Chain.GetBlock(h) @@ -386,7 +386,7 @@ func (e *Executor) GetBlockByIndex(t *testing.T, idx int) *block.Block { } // GetTxExecResult returns application execution results for the specified transaction. -func (e *Executor) GetTxExecResult(t *testing.T, h util.Uint256) *state.AppExecResult { +func (e *Executor) GetTxExecResult(t testing.TB, h util.Uint256) *state.AppExecResult { aer, err := e.Chain.GetAppExecResults(h, trigger.Application) require.NoError(t, err) require.Equal(t, 1, len(aer)) diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index 8845bbcb6..453421b7c 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -121,13 +121,13 @@ func init() { // this package. MemoryStore is used as the backend storage, so all of the chain // contents is always in RAM. The Signer returned is validator (and committee at // the same time). -func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) { +func NewSingle(t testing.TB) (*core.Blockchain, neotest.Signer) { return NewSingleWithCustomConfig(t, nil) } // NewSingleWithCustomConfig is similar to NewSingle, but allows to override the // default configuration. -func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { +func NewSingleWithCustomConfig(t testing.TB, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { st := storage.NewMemoryStore() return NewSingleWithCustomConfigAndStore(t, f, st, true) } @@ -137,7 +137,7 @@ func NewSingleWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguratio // Run method is called on the Blockchain instance, if not then it's caller's // responsibility to do that before using the chain and its caller's responsibility // also to properly Close the chain when done. -func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) { +func NewSingleWithCustomConfigAndStore(t testing.TB, f func(cfg *config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, MaxTraceableBlocks: MaxTraceableBlocks, @@ -163,20 +163,22 @@ func NewSingleWithCustomConfigAndStore(t *testing.T, f func(cfg *config.Protocol // NewMulti creates new blockchain instance with four validators and six // committee members, otherwise not differring much from NewSingle. The // second value returned contains validators Signer, the third -- committee one. -func NewMulti(t *testing.T) (*core.Blockchain, neotest.Signer, neotest.Signer) { +func NewMulti(t testing.TB) (*core.Blockchain, neotest.Signer, neotest.Signer) { return NewMultiWithCustomConfig(t, nil) } // NewMultiWithCustomConfig is similar to NewMulti except it allows to override the // default configuration. -func NewMultiWithCustomConfig(t *testing.T, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { +func NewMultiWithCustomConfig(t testing.TB, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer, neotest.Signer) { return NewMultiWithCustomConfigAndStore(t, f, nil, true) } -// NewMultiWithCustomConfigAndStore creates new blockchain instance with custom -// protocol configuration, custom storage, 4 validators and 6 committee members. -// Second return value is for validator signer, third -- for committee. -func NewMultiWithCustomConfigAndStore(t *testing.T, f func(*config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer, neotest.Signer) { +// NewMultiWithCustomConfigAndStore is similar to NewMultiWithCustomConfig, but +// also allows to override backend Store being used. The last parameter controls if +// Run method is called on the Blockchain instance, if not then it's caller's +// responsibility to do that before using the chain and its caller's responsibility +// also to properly Close the chain when done. +func NewMultiWithCustomConfigAndStore(t testing.TB, f func(*config.ProtocolConfiguration), st storage.Store, run bool) (*core.Blockchain, neotest.Signer, neotest.Signer) { bc, validator, committee, err := NewMultiWithCustomConfigAndStoreNoCheck(t, f, st) require.NoError(t, err) if run { @@ -186,12 +188,9 @@ func NewMultiWithCustomConfigAndStore(t *testing.T, f func(*config.ProtocolConfi return bc, validator, committee } -// NewMultiWithCustomConfigAndStoreNoCheck creates new blockchain instance with -// custom protocol configuration, custom store, 4 validators and 6 committee -// members. Second return value is for validator signer, third -- for committee. -// The resulting blockchain instance is not running and constructor error is -// returned. -func NewMultiWithCustomConfigAndStoreNoCheck(t *testing.T, f func(*config.ProtocolConfiguration), st storage.Store) (*core.Blockchain, neotest.Signer, neotest.Signer, error) { +// NewMultiWithCustomConfigAndStoreNoCheck is similar to NewMultiWithCustomConfig, +// but do not perform Blockchain run and do not check Blockchain constructor error. +func NewMultiWithCustomConfigAndStoreNoCheck(t testing.TB, f func(*config.ProtocolConfiguration), st storage.Store) (*core.Blockchain, neotest.Signer, neotest.Signer, error) { protoCfg := config.ProtocolConfiguration{ Magic: netmode.UnitTestNet, MaxTraceableBlocks: MaxTraceableBlocks, diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go index 2255b4249..ba8aaf695 100644 --- a/pkg/neotest/client.go +++ b/pkg/neotest/client.go @@ -47,7 +47,7 @@ func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker { } // TestInvoke creates test VM and invokes method with args. -func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interface{}) (*vm.Stack, error) { +func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) { tx := c.PrepareInvokeNoSign(t, method, args...) b := c.NewUnsignedBlock(t, tx) ic := c.Chain.GetTestVM(trigger.Application, tx, b) @@ -66,18 +66,18 @@ func (c *ContractInvoker) WithSigners(signers ...Signer) *ContractInvoker { } // PrepareInvoke creates new invocation transaction. -func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction { +func (c *ContractInvoker) PrepareInvoke(t testing.TB, method string, args ...interface{}) *transaction.Transaction { return c.Executor.NewTx(t, c.Signers, c.Hash, method, args...) } // PrepareInvokeNoSign creates new unsigned invocation transaction. -func (c *ContractInvoker) PrepareInvokeNoSign(t *testing.T, method string, args ...interface{}) *transaction.Transaction { +func (c *ContractInvoker) PrepareInvokeNoSign(t testing.TB, method string, args ...interface{}) *transaction.Transaction { return c.Executor.NewUnsignedTx(t, c.Hash, method, args...) } // Invoke invokes method with args, persists transaction and checks the result. // Returns transaction hash. -func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string, args ...interface{}) util.Uint256 { +func (c *ContractInvoker) Invoke(t testing.TB, result interface{}, method string, args ...interface{}) util.Uint256 { tx := c.PrepareInvoke(t, method, args...) c.AddNewBlock(t, tx) c.CheckHalt(t, tx.Hash(), stackitem.Make(result)) @@ -86,7 +86,7 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string // InvokeAndCheck invokes method with args, persists transaction and checks the result // using provided function. Returns transaction hash. -func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testing.T, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 { +func (c *ContractInvoker) InvokeAndCheck(t testing.TB, checkResult func(t testing.TB, stack []stackitem.Item), method string, args ...interface{}) util.Uint256 { tx := c.PrepareInvoke(t, method, args...) c.AddNewBlock(t, tx) aer, err := c.Chain.GetAppExecResults(tx.Hash(), trigger.Application) @@ -99,7 +99,7 @@ func (c *ContractInvoker) InvokeAndCheck(t *testing.T, checkResult func(t *testi } // InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction. -func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 { +func (c *ContractInvoker) InvokeWithFeeFail(t testing.TB, message string, sysFee int64, method string, args ...interface{}) util.Uint256 { tx := c.PrepareInvokeNoSign(t, method, args...) c.Executor.SignTx(t, tx, sysFee, c.Signers...) c.AddNewBlock(t, tx) @@ -109,7 +109,7 @@ func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee // InvokeFail invokes method with args, persists transaction and checks the error message. // Returns transaction hash. -func (c *ContractInvoker) InvokeFail(t *testing.T, message string, method string, args ...interface{}) { +func (c *ContractInvoker) InvokeFail(t testing.TB, message string, method string, args ...interface{}) { tx := c.PrepareInvoke(t, method, args...) c.AddNewBlock(t, tx) c.CheckFault(t, tx.Hash(), message) diff --git a/pkg/neotest/compile.go b/pkg/neotest/compile.go index db215a221..4679ec269 100644 --- a/pkg/neotest/compile.go +++ b/pkg/neotest/compile.go @@ -25,7 +25,7 @@ type Contract struct { var contracts = make(map[string]*Contract) // CompileSource compiles contract from reader and returns it's NEF, manifest and hash. -func CompileSource(t *testing.T, sender util.Uint160, src io.Reader, opts *compiler.Options) *Contract { +func CompileSource(t testing.TB, sender util.Uint160, src io.Reader, opts *compiler.Options) *Contract { // nef.NewFile() cares about version a lot. config.Version = "neotest" @@ -43,7 +43,7 @@ func CompileSource(t *testing.T, sender util.Uint160, src io.Reader, opts *compi } // CompileFile compiles contract from file and returns it's NEF, manifest and hash. -func CompileFile(t *testing.T, sender util.Uint160, srcPath string, configPath string) *Contract { +func CompileFile(t testing.TB, sender util.Uint160, srcPath string, configPath string) *Contract { if c, ok := contracts[srcPath]; ok { return c } diff --git a/pkg/neotest/signer.go b/pkg/neotest/signer.go index e9580626c..e9f8ba9e3 100644 --- a/pkg/neotest/signer.go +++ b/pkg/neotest/signer.go @@ -162,7 +162,7 @@ func (m multiSigner) Single(n int) SingleSigner { return NewSingleSigner(wallet.NewAccountFromPrivateKey(m.accounts[n].PrivateKey())) } -func checkMultiSigner(t *testing.T, s Signer) { +func checkMultiSigner(t testing.TB, s Signer) { ms, ok := s.(multiSigner) require.True(t, ok, "expected to be a multi-signer") From a4dab3a5ba071bfe37e40fc4847424c4cf9698d1 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 23 Mar 2022 19:52:59 +0300 Subject: [PATCH 12/15] core: adjust error message of native call --- pkg/core/native/interop.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 077a18aab..c97850138 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -17,14 +17,15 @@ func Call(ic *interop.Context) error { return fmt.Errorf("native contract of version %d is not active", version) } var c interop.Contract + curr := ic.VM.GetCurrentScriptHash() for _, ctr := range ic.Natives { - if ctr.Metadata().Hash == ic.VM.GetCurrentScriptHash() { + if ctr.Metadata().Hash == curr { c = ctr break } } if c == nil { - return fmt.Errorf("native contract %d not found", version) + return fmt.Errorf("native contract %s (version %d) not found", curr.StringLE(), version) } history := c.Metadata().UpdateHistory if len(history) == 0 { From 35590f0c9692ac2e4779f952651d81ee23df607e Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 24 Mar 2022 14:51:02 +0300 Subject: [PATCH 13/15] circleci: update cache key to avoid fetching outdated cache Avoid the following problem: ``` Found a cache from build 55879 at deps- Size: 178 MiB Cached paths: * /go/pkg/mod Downloading cache archive... Validating cache... Unarchiving cache... Failed to unarchive cache Error untarring cache: Error extracting tarball /tmp/cache87609141 : tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download/cloud.google.com: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download/cloud.google.com/go: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download/cloud.google.com/go/@v: Cannot mkdir: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download/cloud.google.com/go/@v/list: Cannot open: No such file or directory tar: go: Cannot mkdir: Permission denied tar: go/pkg/mod/cache/download/cloud.google.com/go/@v/list.lock: Cannot open: No such file or directory tar: go: Cannot mkdir: Pe: exit status 2 ``` --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2703ce10c..717a6260f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,12 +17,12 @@ commands: gomod: steps: - restore_cache: - keys: [deps-] + keys: [deps-v2-] - run: name: Download go module dependencies command: go mod download - save_cache: - key: deps-{{ checksum "go.sum" }}-{{ checksum "go.sum" }} + key: deps-v2-{{ checksum "go.sum" }}-{{ checksum "go.sum" }} paths: [/home/circleci/go/pkg/mod] jobs: From 35ef58a47ea3fb6739dbf65618b3ac6488bdcd7b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Wed, 30 Mar 2022 15:53:35 +0300 Subject: [PATCH 14/15] core: move Oracle response script creation to a separate function --- pkg/core/native/oracle.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 44eee0418..3730769d3 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -86,9 +86,7 @@ func newOracle() *Oracle { o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle, oracleContractID)} defer o.UpdateHash() - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, o.Hash, "finish", callflag.All) - o.oracleScript = w.Bytes() + o.oracleScript = CreateOracleResponseScript(o.Hash) desc := newDescriptor("request", smartcontract.VoidType, manifest.NewParameter("url", smartcontract.StringType), @@ -526,3 +524,14 @@ func (o *Oracle) updateCache(d *dao.Simple) error { orc.AddRequests(reqs) return nil } + +// CreateOracleResponseScript returns script that is used to create native Oracle +// response. +func CreateOracleResponseScript(nativeOracleHash util.Uint160) []byte { + w := io.NewBufBinWriter() + emit.AppCall(w.BinWriter, nativeOracleHash, "finish", callflag.All) + if w.Err != nil { + panic(fmt.Errorf("failed to create Oracle response script: %w", w.Err)) + } + return w.Bytes() +} From 8965441288b14481cc344a1e93ce4b2b1bc88d09 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Fri, 11 Mar 2022 17:47:59 +0300 Subject: [PATCH 15/15] core: rebase core tests onto neotest --- pkg/core/basic_chain_test.go | 9 +- pkg/core/bench_test.go | 56 +- pkg/core/blockchain_core_test.go | 1602 ---------------- pkg/core/blockchain_neotest_test.go | 1638 +++++++++++++++++ pkg/core/helper_test.go | 282 --- ...em_test.go => interop_system_core_test.go} | 182 +- pkg/core/interop_system_neotest_test.go | 245 +++ pkg/core/native_contract_test.go | 392 ++-- pkg/core/native_designate_test.go | 58 - pkg/core/native_neo_test.go | 129 +- pkg/core/native_policy_test.go | 54 +- pkg/core/notary_test.go | 118 +- pkg/core/oracle_test.go | 159 +- pkg/core/stateroot_test.go | 124 +- pkg/neotest/basic.go | 20 +- pkg/neotest/chain/chain.go | 6 +- 16 files changed, 2401 insertions(+), 2673 deletions(-) rename pkg/core/{interop_system_test.go => interop_system_core_test.go} (84%) create mode 100644 pkg/core/interop_system_neotest_test.go diff --git a/pkg/core/basic_chain_test.go b/pkg/core/basic_chain_test.go index d9e8cbfc3..480db1eb8 100644 --- a/pkg/core/basic_chain_test.go +++ b/pkg/core/basic_chain_test.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "testing" + "time" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/chaindump" @@ -32,9 +33,15 @@ const ( // It is also used to retrieve smart contracts that should be deployed to // Basic chain. basicChainPrefix = "../rpc/server/testdata/" + // bcPersistInterval is the time period Blockchain persists changes to the + // underlying storage. + bcPersistInterval = time.Second ) -var notaryModulePath = filepath.Join("..", "services", "notary") +var ( + notaryModulePath = filepath.Join("..", "services", "notary") + pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts") +) // TestCreateBasicChain generates "../rpc/testdata/testblocks.acc" file which // contains data for RPC unit tests. It also is a nice integration test. diff --git a/pkg/core/bench_test.go b/pkg/core/bench_test.go index 8bbb6b772..d747a6401 100644 --- a/pkg/core/bench_test.go +++ b/pkg/core/bench_test.go @@ -1,34 +1,33 @@ -package core +package core_test import ( "fmt" "testing" "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "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/wallet" "github.com/stretchr/testify/require" ) -func BenchmarkVerifyWitness(t *testing.B) { - bc := newTestChain(t) - acc, err := wallet.NewAccount() - require.NoError(t, err) - - tx := bc.newTestTx(acc.Contract.ScriptHash(), []byte{byte(opcode.PUSH1)}) - require.NoError(t, acc.SignTx(netmode.UnitTestNet, tx)) +func BenchmarkBlockchain_VerifyWitness(t *testing.B) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + tx := e.NewTx(t, []neotest.Signer{acc}, e.NativeHash(t, nativenames.Gas), "transfer", acc.ScriptHash(), acc.Script(), 1, nil) t.ResetTimer() for n := 0; n < t.N; n++ { - _, _ = bc.VerifyWitness(tx.Signers[0].Account, tx, &tx.Scripts[0], 100000000) + _, err := bc.VerifyWitness(tx.Signers[0].Account, tx, &tx.Scripts[0], 100000000) + require.NoError(t, err) } } @@ -57,38 +56,35 @@ func BenchmarkBlockchain_ForEachNEP17Transfer(t *testing.B) { func benchmarkForEachNEP17Transfer(t *testing.B, ps storage.Store, startFromBlock, nBlocksToTake int) { var ( - nonce uint32 = 1 - chainHeight = 2_100 // constant chain height to be able to compare paging results - transfersPerBlock = state.TokenTransferBatchSize/4 + // 4 blocks per batch + chainHeight = 2_100 // constant chain height to be able to compare paging results + transfersPerBlock = state.TokenTransferBatchSize/4 + // 4 blocks per batch state.TokenTransferBatchSize/32 // shift ) - bc := newTestChainWithCustomCfgAndStore(t, ps, nil) - gasHash := bc.contracts.GAS.Hash + bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true) + + e := neotest.NewExecutor(t, bc, validators, committee) + gasHash := e.NativeHash(t, nativenames.Gas) + acc := random.Uint160() + from := e.Validator.ScriptHash() for j := 0; j < chainHeight; j++ { w := io.NewBufBinWriter() for i := 0; i < transfersPerBlock; i++ { - emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All, testchain.MultisigScriptHash(), acc, 1, nil) + emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All, from, acc, 1, nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) - require.NoError(t, w.Err) } + require.NoError(t, w.Err) script := w.Bytes() tx := transaction.New(script, int64(1100_0000*transfersPerBlock)) + tx.NetworkFee = 1_0000_000 tx.ValidUntilBlock = bc.BlockHeight() + 1 - tx.Nonce = nonce - nonce++ - tx.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.CalledByEntry, - AllowedContracts: nil, - AllowedGroups: nil, - }} - require.NoError(t, testchain.SignTx(bc, tx)) - b := bc.newBlock(tx) - require.NoError(t, bc.AddBlock(b)) - checkTxHalt(t, bc, tx.Hash()) + tx.Nonce = neotest.Nonce() + tx.Signers = []transaction.Signer{{Account: from, Scopes: transaction.CalledByEntry}} + require.NoError(t, validators.SignTx(netmode.UnitTestNet, tx)) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) } newestB, err := bc.GetBlock(bc.GetHeaderHash(int(bc.BlockHeight()) - startFromBlock + 1)) diff --git a/pkg/core/blockchain_core_test.go b/pkg/core/blockchain_core_test.go index b06623738..4a68fb16c 100644 --- a/pkg/core/blockchain_core_test.go +++ b/pkg/core/blockchain_core_test.go @@ -4,43 +4,20 @@ import ( "encoding/binary" "errors" "fmt" - "math/big" - "math/rand" - "path/filepath" "strings" "testing" "time" - "github.com/nspcc-dev/neo-go/internal/contracts" - "github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/fee" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" - "github.com/nspcc-dev/neo-go/pkg/core/mempool" - "github.com/nspcc-dev/neo-go/pkg/core/native" - "github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices" - "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/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/response/result/subscriptions" "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/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util/slice" - "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" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -72,44 +49,6 @@ func TestVerifyHeader(t *testing.T) { }) } -func TestAddHeaders(t *testing.T) { - bc := newTestChain(t) - lastBlock := bc.topBlock.Load().(*block.Block) - h1 := newBlock(bc.config, 1, lastBlock.Hash()).Header - h2 := newBlock(bc.config, 2, h1.Hash()).Header - h3 := newBlock(bc.config, 3, h2.Hash()).Header - - require.NoError(t, bc.AddHeaders()) - require.NoError(t, bc.AddHeaders(&h1, &h2)) - require.NoError(t, bc.AddHeaders(&h2, &h3)) - - assert.Equal(t, h3.Index, bc.HeaderHeight()) - assert.Equal(t, uint32(0), bc.BlockHeight()) - assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) - - // Add them again, they should not be added. - require.NoError(t, bc.AddHeaders(&h3, &h2, &h1)) - - assert.Equal(t, h3.Index, bc.HeaderHeight()) - assert.Equal(t, uint32(0), bc.BlockHeight()) - assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) - - h4 := newBlock(bc.config, 4, h3.Hash().Reverse()).Header - h5 := newBlock(bc.config, 5, h4.Hash()).Header - - assert.Error(t, bc.AddHeaders(&h4, &h5)) - assert.Equal(t, h3.Index, bc.HeaderHeight()) - assert.Equal(t, uint32(0), bc.BlockHeight()) - assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) - - h6 := newBlock(bc.config, 4, h3.Hash()).Header - h6.Script.InvocationScript = nil - assert.Error(t, bc.AddHeaders(&h6)) - assert.Equal(t, h3.Index, bc.HeaderHeight()) - assert.Equal(t, uint32(0), bc.BlockHeight()) - assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) -} - func TestAddBlock(t *testing.T) { const size = 3 bc := newTestChain(t) @@ -136,1371 +75,6 @@ func TestAddBlock(t *testing.T) { assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash()) } -func TestAddBlockStateRoot(t *testing.T) { - bc := newTestChainWithCustomCfg(t, func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true - }) - - sr, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight()) - require.NoError(t, err) - - tx := newNEP17Transfer(bc.contracts.NEO.Hash, neoOwner, util.Uint160{}, 1) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - addSigners(neoOwner, tx) - require.NoError(t, testchain.SignTx(bc, tx)) - - lastBlock := bc.topBlock.Load().(*block.Block) - b := newBlock(bc.config, lastBlock.Index+1, lastBlock.Hash(), tx) - err = bc.AddBlock(b) - require.True(t, errors.Is(err, ErrHdrStateRootSetting), "got: %v", err) - - u := sr.Root - u[0] ^= 0xFF - b = newBlockWithState(bc.config, lastBlock.Index+1, lastBlock.Hash(), &u, tx) - err = bc.AddBlock(b) - require.True(t, errors.Is(err, ErrHdrInvalidStateRoot), "got: %v", err) - - b = bc.newBlock(tx) - require.NoError(t, bc.AddBlock(b)) -} - -func TestAddHeadersStateRoot(t *testing.T) { - bc := newTestChainWithCustomCfg(t, func(c *config.Config) { - c.ProtocolConfiguration.StateRootInHeader = true - }) - - r := bc.stateRoot.CurrentLocalStateRoot() - h1 := bc.newBlock().Header - - // invalid stateroot - h1.PrevStateRoot[0] ^= 0xFF - require.True(t, errors.Is(bc.AddHeaders(&h1), ErrHdrInvalidStateRoot)) - - // valid stateroot - h1.PrevStateRoot = r - require.NoError(t, bc.AddHeaders(&h1)) - - // unable to verify stateroot (stateroot is computed for block #0 only => can - // verify stateroot of header #1 only) => just store the header - h2 := newBlockWithState(bc.config, 2, h1.Hash(), nil).Header - require.NoError(t, bc.AddHeaders(&h2)) -} - -func TestAddBadBlock(t *testing.T) { - bc := newTestChain(t) - // It has ValidUntilBlock == 0, which is wrong - tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.None, - }} - require.NoError(t, testchain.SignTx(bc, tx)) - b1 := bc.newBlock(tx) - - require.Error(t, bc.AddBlock(b1)) - bc.config.VerifyTransactions = false - require.NoError(t, bc.AddBlock(b1)) - - b2 := bc.newBlock() - b2.PrevHash = util.Uint256{} - - require.Error(t, bc.AddBlock(b2)) - bc.config.VerifyBlocks = false - require.NoError(t, bc.AddBlock(b2)) - - tx = transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx.ValidUntilBlock = 128 - tx.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.None, - }} - require.NoError(t, testchain.SignTx(bc, tx)) - require.NoError(t, bc.PoolTx(tx)) - bc.config.VerifyTransactions = true - bc.config.VerifyBlocks = true - b3 := bc.newBlock(tx) - require.NoError(t, bc.AddBlock(b3)) -} - -func TestGetHeader(t *testing.T) { - bc := newTestChain(t) - tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - addSigners(neoOwner, tx) - assert.Nil(t, testchain.SignTx(bc, tx)) - block := bc.newBlock(tx) - err := bc.AddBlock(block) - assert.Nil(t, err) - - hash := block.Hash() - header, err := bc.GetHeader(hash) - require.NoError(t, err) - assert.Equal(t, &block.Header, header) - - b2 := bc.newBlock() - _, err = bc.GetHeader(b2.Hash()) - assert.Error(t, err) -} - -func TestGetBlock(t *testing.T) { - bc := newTestChain(t) - blocks, err := bc.genBlocks(100) - require.NoError(t, err) - - for i := 0; i < len(blocks); i++ { - block, err := bc.GetBlock(blocks[i].Hash()) - require.NoErrorf(t, err, "can't get block %d: %s", i, err) - assert.Equal(t, blocks[i].Index, block.Index) - assert.Equal(t, blocks[i].Hash(), block.Hash()) - } - - t.Run("store only header", func(t *testing.T) { - t.Run("non-empty block", func(t *testing.T) { - tx, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, - random.Uint160(), 1, 1, 1000) - require.NoError(t, err) - b := bc.newBlock(tx) - require.NoError(t, bc.AddHeaders(&b.Header)) - - _, err = bc.GetBlock(b.Hash()) - require.Error(t, err) - - _, err = bc.GetHeader(b.Hash()) - require.NoError(t, err) - - require.NoError(t, bc.AddBlock(b)) - - _, err = bc.GetBlock(b.Hash()) - require.NoError(t, err) - }) - t.Run("empty block", func(t *testing.T) { - b := bc.newBlock() - require.NoError(t, bc.AddHeaders(&b.Header)) - - _, err = bc.GetBlock(b.Hash()) - require.NoError(t, err) - }) - }) -} - -func (bc *Blockchain) newTestTx(h util.Uint160, script []byte) *transaction.Transaction { - tx := transaction.New(script, 1_000_000) - tx.Nonce = rand.Uint32() - tx.ValidUntilBlock = 100 - tx.Signers = []transaction.Signer{{ - Account: h, - Scopes: transaction.CalledByEntry, - }} - tx.NetworkFee = int64(io.GetVarSize(tx)+200 /* witness */) * bc.FeePerByte() - tx.NetworkFee += 1_000_000 // verification cost - return tx -} - -func TestVerifyTx(t *testing.T) { - bc := newTestChain(t) - - accs := make([]*wallet.Account, 5) - for i := range accs { - var err error - accs[i], err = wallet.NewAccount() - require.NoError(t, err) - } - - notaryServiceFeePerKey := bc.contracts.Notary.GetNotaryServiceFeePerKey(bc.dao) - - oracleAcc := accs[2] - oraclePubs := keys.PublicKeys{oracleAcc.PrivateKey().PublicKey()} - require.NoError(t, oracleAcc.ConvertMultisig(1, oraclePubs)) - - neoHash := bc.contracts.NEO.Hash - gasHash := bc.contracts.GAS.Hash - w := io.NewBufBinWriter() - for _, sc := range []util.Uint160{neoHash, gasHash} { - for _, a := range accs { - amount := int64(1_000_000) - if sc.Equals(gasHash) { - amount = 1_000_000_000 - } - emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, - neoOwner, a.Contract.ScriptHash(), amount, nil) - emit.Opcodes(w.BinWriter, opcode.ASSERT) - } - } - emit.AppCall(w.BinWriter, gasHash, "transfer", callflag.All, - neoOwner, testchain.CommitteeScriptHash(), int64(1_000_000_000), nil) - emit.Opcodes(w.BinWriter, opcode.ASSERT) - require.NoError(t, w.Err) - - txMove := bc.newTestTx(neoOwner, w.Bytes()) - txMove.SystemFee = 1_000_000_000 - require.NoError(t, testchain.SignTx(bc, txMove)) - b := bc.newBlock(txMove) - require.NoError(t, bc.AddBlock(b)) - - aer, err := bc.GetAppExecResults(txMove.Hash(), trigger.Application) - require.NoError(t, err) - require.Equal(t, 1, len(aer)) - require.Equal(t, aer[0].VMState, vm.HaltState) - - res, err := invokeContractMethodGeneric(bc, 100000000, bc.contracts.Policy.Hash, "blockAccount", true, accs[1].PrivateKey().GetScriptHash().BytesBE()) - require.NoError(t, err) - checkResult(t, res, stackitem.NewBool(true)) - - checkErr := func(t *testing.T, expectedErr error, tx *transaction.Transaction) { - err := bc.VerifyTx(tx) - require.True(t, errors.Is(err, expectedErr), "expected: %v, got: %v", expectedErr, err) - } - - testScript := []byte{byte(opcode.PUSH1)} - h := accs[0].PrivateKey().GetScriptHash() - t.Run("Expired", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.ValidUntilBlock = 1 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrTxExpired, tx) - }) - t.Run("BlockedAccount", func(t *testing.T) { - tx := bc.newTestTx(accs[1].PrivateKey().GetScriptHash(), testScript) - require.NoError(t, accs[1].SignTx(netmode.UnitTestNet, tx)) - err := bc.VerifyTx(tx) - require.True(t, errors.Is(err, ErrPolicy)) - }) - t.Run("InsufficientGas", func(t *testing.T) { - balance := bc.GetUtilityTokenBalance(h) - tx := bc.newTestTx(h, testScript) - tx.SystemFee = balance.Int64() + 1 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInsufficientFunds, tx) - }) - t.Run("TooBigTx", func(t *testing.T) { - script := make([]byte, transaction.MaxTransactionSize) - tx := bc.newTestTx(h, script) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrTxTooBig, tx) - }) - t.Run("NetworkFee", func(t *testing.T) { - t.Run("SmallNetworkFee", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.NetworkFee = 1 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrTxSmallNetworkFee, tx) - }) - t.Run("AlmostEnoughNetworkFee", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) - expectedSize := io.GetVarSize(tx) + calcultedScriptSize - calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() - tx.NetworkFee = calculatedNetFee - 1 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - require.Equal(t, expectedSize, io.GetVarSize(tx)) - checkErr(t, ErrVerificationFailed, tx) - }) - t.Run("EnoughNetworkFee", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) - expectedSize := io.GetVarSize(tx) + calcultedScriptSize - calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() - tx.NetworkFee = calculatedNetFee - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - require.Equal(t, expectedSize, io.GetVarSize(tx)) - require.NoError(t, bc.VerifyTx(tx)) - }) - t.Run("CalculateNetworkFee, signature script", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - expectedSize := io.GetVarSize(tx) - verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) - expectedSize += calculatedScriptSize - expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() - tx.NetworkFee = expectedNetFee - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - actualSize := io.GetVarSize(tx) - require.Equal(t, expectedSize, actualSize) - interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, tx) - gasConsumed, err := bc.verifyHashAgainstScript(h, &tx.Scripts[0], interopCtx, -1) - require.NoError(t, err) - require.Equal(t, verificationNetFee, gasConsumed) - require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed) - }) - t.Run("CalculateNetworkFee, multisignature script", func(t *testing.T) { - multisigAcc := accs[4] - pKeys := keys.PublicKeys{multisigAcc.PrivateKey().PublicKey()} - require.NoError(t, multisigAcc.ConvertMultisig(1, pKeys)) - multisigHash := hash.Hash160(multisigAcc.Contract.Script) - tx := bc.newTestTx(multisigHash, testScript) - verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), multisigAcc.Contract.Script) - expectedSize := io.GetVarSize(tx) + calculatedScriptSize - expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() - tx.NetworkFee = expectedNetFee - require.NoError(t, multisigAcc.SignTx(netmode.UnitTestNet, tx)) - actualSize := io.GetVarSize(tx) - require.Equal(t, expectedSize, actualSize) - interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, tx) - gasConsumed, err := bc.verifyHashAgainstScript(multisigHash, &tx.Scripts[0], interopCtx, -1) - require.NoError(t, err) - require.Equal(t, verificationNetFee, gasConsumed) - require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed) - }) - }) - t.Run("InvalidTxScript", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.Script = append(tx.Script, 0xff) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidScript, tx) - }) - t.Run("InvalidVerificationScript", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - verif := []byte{byte(opcode.JMP), 3, 0xff, byte(opcode.PUSHT)} - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: hash.Hash160(verif), - Scopes: transaction.Global, - }) - tx.NetworkFee += 1000000 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - tx.Scripts = append(tx.Scripts, transaction.Witness{ - InvocationScript: []byte{}, - VerificationScript: verif, - }) - checkErr(t, ErrInvalidVerification, tx) - }) - t.Run("InvalidInvocationScript", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - verif := []byte{byte(opcode.PUSHT)} - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: hash.Hash160(verif), - Scopes: transaction.Global, - }) - tx.NetworkFee += 1000000 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - tx.Scripts = append(tx.Scripts, transaction.Witness{ - InvocationScript: []byte{byte(opcode.JMP), 3, 0xff}, - VerificationScript: verif, - }) - checkErr(t, ErrInvalidInvocation, tx) - }) - t.Run("Conflict", func(t *testing.T) { - balance := bc.GetUtilityTokenBalance(h).Int64() - tx := bc.newTestTx(h, testScript) - tx.NetworkFee = balance / 2 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - require.NoError(t, bc.PoolTx(tx)) - - tx2 := bc.newTestTx(h, testScript) - tx2.NetworkFee = balance / 2 - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2)) - err := bc.PoolTx(tx2) - require.True(t, errors.Is(err, ErrMemPoolConflict)) - }) - t.Run("InvalidWitnessHash", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - tx.Scripts[0].VerificationScript = []byte{byte(opcode.PUSHT)} - checkErr(t, ErrWitnessHashMismatch, tx) - }) - t.Run("InvalidWitnessSignature", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10] - checkErr(t, ErrVerificationFailed, tx) - }) - t.Run("InsufficientNetworkFeeForSecondWitness", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: accs[3].PrivateKey().GetScriptHash(), - Scopes: transaction.Global, - }) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - require.NoError(t, accs[3].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrVerificationFailed, tx) - }) - t.Run("OldTX", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - b := bc.newBlock(tx) - require.NoError(t, bc.AddBlock(b)) - - err := bc.VerifyTx(tx) - require.True(t, errors.Is(err, ErrAlreadyExists)) - }) - t.Run("MemPooledTX", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - require.NoError(t, bc.PoolTx(tx)) - - err := bc.PoolTx(tx) - require.True(t, errors.Is(err, ErrAlreadyExists)) - }) - t.Run("MemPoolOOM", func(t *testing.T) { - bc.memPool = mempool.New(1, 0, false) - tx1 := bc.newTestTx(h, testScript) - tx1.NetworkFee += 10000 // Give it more priority. - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx1)) - require.NoError(t, bc.PoolTx(tx1)) - - tx2 := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2)) - err := bc.PoolTx(tx2) - require.True(t, errors.Is(err, ErrOOM)) - }) - t.Run("Attribute", func(t *testing.T) { - t.Run("InvalidHighPriority", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority}) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - t.Run("ValidHighPriority", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority}) - tx.NetworkFee += 4_000_000 // multisig check - tx.Signers = []transaction.Signer{{ - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }} - rawScript := testchain.CommitteeVerificationScript() - require.NoError(t, err) - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - tx.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: rawScript, - }} - require.NoError(t, bc.VerifyTx(tx)) - }) - t.Run("Oracle", func(t *testing.T) { - orc := bc.contracts.Oracle - req := &state.OracleRequest{GasForResponse: 1000_0000} - require.NoError(t, orc.PutRequestInternal(1, req, bc.dao)) - - oracleScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(oraclePubs) - require.NoError(t, err) - oracleHash := hash.Hash160(oracleScript) - - // We need to create new transaction, - // because hashes are cached after signing. - getOracleTx := func(t *testing.T) *transaction.Transaction { - tx := bc.newTestTx(h, orc.GetOracleResponseScript()) - resp := &transaction.OracleResponse{ - ID: 1, - Code: transaction.Success, - Result: []byte{1, 2, 3}, - } - tx.Attributes = []transaction.Attribute{{ - Type: transaction.OracleResponseT, - Value: resp, - }} - tx.NetworkFee += 4_000_000 // multisig check - tx.SystemFee = int64(req.GasForResponse - uint64(tx.NetworkFee)) - tx.Signers = []transaction.Signer{{ - Account: oracleHash, - Scopes: transaction.None, - }} - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), oracleScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - return tx - } - - t.Run("NoOracleNodes", func(t *testing.T) { - tx := getOracleTx(t) - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - - txSetOracle := transaction.New([]byte{byte(opcode.RET)}, 0) // it's a hack, so we don't need a real script - setSigner(txSetOracle, testchain.CommitteeScriptHash()) - txSetOracle.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(txSetOracle), - VerificationScript: testchain.CommitteeVerificationScript(), - }} - bl := block.New(bc.config.StateRootInHeader) - bl.Index = bc.BlockHeight() + 1 - ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetOracle) - ic.SpawnVM() - ic.VM.LoadScript([]byte{byte(opcode.RET)}) - require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, noderoles.Oracle, oraclePubs)) - _, err = ic.DAO.Persist() - require.NoError(t, err) - - t.Run("Valid", func(t *testing.T) { - tx := getOracleTx(t) - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - require.NoError(t, bc.VerifyTx(tx)) - - t.Run("NativeVerify", func(t *testing.T) { - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: bc.contracts.Oracle.Hash, - Scopes: transaction.None, - }) - tx.Scripts = append(tx.Scripts, transaction.Witness{}) - t.Run("NonZeroVerification", func(t *testing.T) { - w := io.NewBufBinWriter() - emit.Opcodes(w.BinWriter, opcode.ABORT) - emit.Bytes(w.BinWriter, util.Uint160{}.BytesBE()) - emit.Int(w.BinWriter, 0) - emit.String(w.BinWriter, orc.Manifest.Name) - tx.Scripts[len(tx.Scripts)-1].VerificationScript = w.Bytes() - err := bc.VerifyTx(tx) - require.True(t, errors.Is(err, ErrNativeContractWitness), "got: %v", err) - }) - t.Run("Good", func(t *testing.T) { - tx.Scripts[len(tx.Scripts)-1].VerificationScript = nil - require.NoError(t, bc.VerifyTx(tx)) - }) - }) - }) - t.Run("InvalidRequestID", func(t *testing.T) { - tx := getOracleTx(t) - tx.Attributes[0].Value.(*transaction.OracleResponse).ID = 2 - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - t.Run("InvalidScope", func(t *testing.T) { - tx := getOracleTx(t) - tx.Signers[0].Scopes = transaction.Global - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - t.Run("InvalidScript", func(t *testing.T) { - tx := getOracleTx(t) - tx.Script = append(tx.Script, byte(opcode.NOP)) - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - t.Run("InvalidSigner", func(t *testing.T) { - tx := getOracleTx(t) - tx.Signers[0].Account = accs[0].Contract.ScriptHash() - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - t.Run("SmallFee", func(t *testing.T) { - tx := getOracleTx(t) - tx.SystemFee = 0 - require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) - checkErr(t, ErrInvalidAttribute, tx) - }) - }) - t.Run("NotValidBefore", func(t *testing.T) { - getNVBTx := func(height uint32) *transaction.Transaction { - tx := bc.newTestTx(h, testScript) - tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height}}) - tx.NetworkFee += 4_000_000 // multisig check - tx.Signers = []transaction.Signer{{ - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }} - rawScript := testchain.CommitteeVerificationScript() - require.NoError(t, err) - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - tx.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: rawScript, - }} - return tx - } - t.Run("Disabled", func(t *testing.T) { - tx := getNVBTx(bc.blockHeight + 1) - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("Enabled", func(t *testing.T) { - bc.config.P2PSigExtensions = true - t.Run("NotYetValid", func(t *testing.T) { - tx := getNVBTx(bc.blockHeight + 1) - require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute)) - }) - t.Run("positive", func(t *testing.T) { - tx := getNVBTx(bc.blockHeight) - require.NoError(t, bc.VerifyTx(tx)) - }) - }) - }) - t.Run("Reserved", func(t *testing.T) { - getReservedTx := func(attrType transaction.AttrType) *transaction.Transaction { - tx := bc.newTestTx(h, testScript) - tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: attrType, Value: &transaction.Reserved{Value: []byte{1, 2, 3}}}) - tx.NetworkFee += 4_000_000 // multisig check - tx.Signers = []transaction.Signer{{ - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }} - rawScript := testchain.CommitteeVerificationScript() - require.NoError(t, err) - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - tx.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: rawScript, - }} - return tx - } - t.Run("Disabled", func(t *testing.T) { - tx := getReservedTx(transaction.ReservedLowerBound + 3) - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("Enabled", func(t *testing.T) { - bc.config.ReservedAttributes = true - tx := getReservedTx(transaction.ReservedLowerBound + 3) - require.NoError(t, bc.VerifyTx(tx)) - }) - }) - t.Run("Conflicts", func(t *testing.T) { - getConflictsTx := func(hashes ...util.Uint256) *transaction.Transaction { - tx := bc.newTestTx(h, testScript) - tx.Attributes = make([]transaction.Attribute, len(hashes)) - for i, h := range hashes { - tx.Attributes[i] = transaction.Attribute{ - Type: transaction.ConflictsT, - Value: &transaction.Conflicts{ - Hash: h, - }, - } - } - tx.NetworkFee += 4_000_000 // multisig check - tx.Signers = []transaction.Signer{{ - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }} - rawScript := testchain.CommitteeVerificationScript() - require.NoError(t, err) - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - tx.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: rawScript, - }} - return tx - } - t.Run("disabled", func(t *testing.T) { - bc.config.P2PSigExtensions = false - tx := getConflictsTx(util.Uint256{1, 2, 3}) - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("enabled", func(t *testing.T) { - bc.config.P2PSigExtensions = true - t.Run("dummy on-chain conflict", func(t *testing.T) { - tx := bc.newTestTx(h, testScript) - require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) - conflicting := transaction.New([]byte{byte(opcode.RET)}, 1) - conflicting.Attributes = []transaction.Attribute{ - { - Type: transaction.ConflictsT, - Value: &transaction.Conflicts{ - Hash: tx.Hash(), - }, - }, - } - require.NoError(t, bc.dao.StoreAsTransaction(conflicting, bc.blockHeight, nil)) - require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts)) - }) - t.Run("attribute on-chain conflict", func(t *testing.T) { - tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx.ValidUntilBlock = 4242 - tx.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.None, - }} - require.NoError(t, testchain.SignTx(bc, tx)) - b := bc.newBlock(tx) - - require.NoError(t, bc.AddBlock(b)) - txConflict := getConflictsTx(tx.Hash()) - require.Error(t, bc.VerifyTx(txConflict)) - }) - t.Run("positive", func(t *testing.T) { - tx := getConflictsTx(random.Uint256()) - require.NoError(t, bc.VerifyTx(tx)) - }) - }) - }) - t.Run("NotaryAssisted", func(t *testing.T) { - notary, err := wallet.NewAccount() - require.NoError(t, err) - txSetNotary := transaction.New([]byte{byte(opcode.RET)}, 0) - setSigner(txSetNotary, testchain.CommitteeScriptHash()) - txSetNotary.Scripts = []transaction.Witness{{ - InvocationScript: testchain.SignCommittee(txSetNotary), - VerificationScript: testchain.CommitteeVerificationScript(), - }} - bl := block.New(false) - bl.Index = bc.BlockHeight() + 1 - ic := bc.newInteropContext(trigger.All, bc.dao, bl, txSetNotary) - ic.SpawnVM() - ic.VM.LoadScript([]byte{byte(opcode.RET)}) - require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, noderoles.P2PNotary, keys.PublicKeys{notary.PrivateKey().PublicKey()})) - _, err = ic.DAO.Persist() - require.NoError(t, err) - getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction { - tx := bc.newTestTx(h, testScript) - tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{ - NKeys: signaturesCount, - }}) - tx.NetworkFee += serviceFee // additional fee for NotaryAssisted attribute - tx.NetworkFee += 4_000_000 // multisig check - tx.Signers = []transaction.Signer{{ - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - } - rawScript := testchain.CommitteeVerificationScript() - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) - tx.NetworkFee += netFee - tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() - tx.Scripts = []transaction.Witness{ - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: rawScript, - }, - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...), - }, - } - return tx - } - t.Run("Disabled", func(t *testing.T) { - bc.config.P2PSigExtensions = false - tx := getNotaryAssistedTx(0, 0) - require.True(t, errors.Is(bc.VerifyTx(tx), ErrInvalidAttribute)) - }) - t.Run("Enabled, insufficient network fee", func(t *testing.T) { - bc.config.P2PSigExtensions = true - tx := getNotaryAssistedTx(1, 0) - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("Test verify", func(t *testing.T) { - bc.config.P2PSigExtensions = true - t.Run("no NotaryAssisted attribute", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Attributes = []transaction.Attribute{} - tx.Signers = []transaction.Signer{ - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }, - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("no deposit", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Signers = []transaction.Signer{ - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...), - }, - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("bad Notary signer scope", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Signers = []transaction.Signer{ - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.CalledByEntry, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }, - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("not signed by Notary", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Signers = []transaction.Signer{ - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("bad Notary node witness", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Signers = []transaction.Signer{ - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.None, - }, - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - } - acc, err := keys.NewPrivateKey() - require.NoError(t, err) - tx.Scripts = []transaction.Witness{ - { - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }, - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.SignHashable(uint32(testchain.Network()), tx)...), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("missing payer", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - tx.Signers = []transaction.Signer{ - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(testchain.Network()), tx)...), - }, - } - require.Error(t, bc.VerifyTx(tx)) - }) - t.Run("positive", func(t *testing.T) { - tx := getNotaryAssistedTx(1, (1+1)*notaryServiceFeePerKey) - require.NoError(t, bc.VerifyTx(tx)) - }) - }) - }) - }) - t.Run("Partially-filled transaction", func(t *testing.T) { - bc.config.P2PSigExtensions = true - getPartiallyFilledTx := func(nvb uint32, validUntil uint32) *transaction.Transaction { - tx := bc.newTestTx(h, testScript) - tx.ValidUntilBlock = validUntil - tx.Attributes = []transaction.Attribute{ - { - Type: transaction.NotValidBeforeT, - Value: &transaction.NotValidBefore{Height: nvb}, - }, - { - Type: transaction.NotaryAssistedT, - Value: &transaction.NotaryAssisted{NKeys: 0}, - }, - } - tx.Signers = []transaction.Signer{ - { - Account: bc.contracts.Notary.Hash, - Scopes: transaction.None, - }, - { - Account: testchain.MultisigScriptHash(), - Scopes: transaction.None, - }, - } - size := io.GetVarSize(tx) - netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), testchain.MultisigVerificationScript()) - tx.NetworkFee = netFee + // multisig witness verification price - int64(size)*bc.FeePerByte() + // fee for unsigned size - int64(sizeDelta)*bc.FeePerByte() + // fee for multisig size - 66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script) - 2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation) - notaryServiceFeePerKey + // fee for Notary attribute - fee.Opcode(bc.GetBaseExecFee(), // Notary verification script - opcode.PUSHDATA1, opcode.RET, // invocation script - opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call - nativeprices.NotaryVerificationPrice*bc.GetBaseExecFee() // Notary witness verification price - tx.Scripts = []transaction.Witness{ - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...), - VerificationScript: []byte{}, - }, - { - InvocationScript: testchain.Sign(tx), - VerificationScript: testchain.MultisigVerificationScript(), - }, - } - return tx - } - - mp := mempool.New(10, 1, false) - verificationF := func(tx *transaction.Transaction, data interface{}) error { - if data.(int) > 5 { - return errors.New("bad data") - } - return nil - } - t.Run("failed pre-verification", func(t *testing.T) { - tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1) - require.Error(t, bc.PoolTxWithData(tx, 6, mp, bc, verificationF)) // here and below let's use `bc` instead of proper NotaryFeer for the test simplicity. - }) - t.Run("GasLimitExceeded during witness verification", func(t *testing.T) { - tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1) - tx.NetworkFee-- // to check that NetworkFee was set correctly in getPartiallyFilledTx - tx.Scripts = []transaction.Witness{ - { - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...), - VerificationScript: []byte{}, - }, - { - InvocationScript: testchain.Sign(tx), - VerificationScript: testchain.MultisigVerificationScript(), - }, - } - require.Error(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF)) - }) - t.Run("bad NVB: too big", func(t *testing.T) { - tx := getPartiallyFilledTx(bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1, bc.blockHeight+1) - require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute)) - }) - t.Run("bad ValidUntilBlock: too small", func(t *testing.T) { - tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+bc.contracts.Notary.GetMaxNotValidBeforeDelta(bc.dao)+1) - require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), ErrInvalidAttribute)) - }) - t.Run("good", func(t *testing.T) { - tx := getPartiallyFilledTx(bc.blockHeight, bc.blockHeight+1) - require.NoError(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF)) - }) - }) -} - -func TestVerifyHashAgainstScript(t *testing.T) { - bc := newTestChain(t) - - cs, csInvalid := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid)) - - gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO) - t.Run("Contract", func(t *testing.T) { - t.Run("Missing", func(t *testing.T) { - newH := cs.Hash - newH[0] = ^newH[0] - w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - _, err := bc.verifyHashAgainstScript(newH, w, ic, gas) - require.True(t, errors.Is(err, ErrUnknownVerificationContract)) - }) - t.Run("Invalid", func(t *testing.T) { - w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - _, err := bc.verifyHashAgainstScript(csInvalid.Hash, w, ic, gas) - require.True(t, errors.Is(err, ErrInvalidVerificationContract)) - }) - t.Run("ValidSignature", func(t *testing.T) { - w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} - _, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas) - require.NoError(t, err) - }) - t.Run("InvalidSignature", func(t *testing.T) { - w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}} - _, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas) - require.True(t, errors.Is(err, ErrVerificationFailed)) - }) - }) - t.Run("NotEnoughGas", func(t *testing.T) { - verif := []byte{byte(opcode.PUSH1)} - w := &transaction.Witness{ - InvocationScript: []byte{byte(opcode.NOP)}, - VerificationScript: verif, - } - _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, 1) - require.True(t, errors.Is(err, ErrVerificationFailed)) - }) - t.Run("NoResult", func(t *testing.T) { - verif := []byte{byte(opcode.DROP)} - w := &transaction.Witness{ - InvocationScript: []byte{byte(opcode.PUSH1)}, - VerificationScript: verif, - } - _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) - require.True(t, errors.Is(err, ErrVerificationFailed)) - }) - t.Run("BadResult", func(t *testing.T) { - verif := make([]byte, 66) - verif[0] = byte(opcode.PUSHDATA1) - verif[1] = 64 - w := &transaction.Witness{ - InvocationScript: []byte{byte(opcode.NOP)}, - VerificationScript: verif, - } - _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) - require.True(t, errors.Is(err, ErrVerificationFailed)) - }) - t.Run("TooManyResults", func(t *testing.T) { - verif := []byte{byte(opcode.NOP)} - w := &transaction.Witness{ - InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.PUSH1)}, - VerificationScript: verif, - } - _, err := bc.verifyHashAgainstScript(hash.Hash160(verif), w, ic, gas) - require.True(t, errors.Is(err, ErrVerificationFailed)) - }) -} - -func TestIsTxStillRelevant(t *testing.T) { - bc := newTestChain(t) - - mp := bc.GetMemPool() - newTx := func(t *testing.T) *transaction.Transaction { - tx := transaction.New([]byte{byte(opcode.RET)}, 100) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - tx.Signers = []transaction.Signer{{ - Account: neoOwner, - Scopes: transaction.CalledByEntry, - }} - return tx - } - - t.Run("small ValidUntilBlock", func(t *testing.T) { - tx := newTx(t) - require.NoError(t, testchain.SignTx(bc, tx)) - - require.True(t, bc.IsTxStillRelevant(tx, nil, false)) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.False(t, bc.IsTxStillRelevant(tx, nil, false)) - }) - - t.Run("tx is already persisted", func(t *testing.T) { - tx := newTx(t) - tx.ValidUntilBlock = bc.BlockHeight() + 2 - require.NoError(t, testchain.SignTx(bc, tx)) - - require.True(t, bc.IsTxStillRelevant(tx, nil, false)) - require.NoError(t, bc.AddBlock(bc.newBlock(tx))) - require.False(t, bc.IsTxStillRelevant(tx, nil, false)) - }) - - t.Run("tx with Conflicts attribute", func(t *testing.T) { - tx1 := newTx(t) - require.NoError(t, testchain.SignTx(bc, tx1)) - - tx2 := newTx(t) - tx2.Attributes = []transaction.Attribute{{ - Type: transaction.ConflictsT, - Value: &transaction.Conflicts{Hash: tx1.Hash()}, - }} - require.NoError(t, testchain.SignTx(bc, tx2)) - - require.True(t, bc.IsTxStillRelevant(tx1, mp, false)) - require.NoError(t, bc.verifyAndPoolTx(tx2, mp, bc)) - require.False(t, bc.IsTxStillRelevant(tx1, mp, false)) - }) - t.Run("NotValidBefore", func(t *testing.T) { - tx3 := newTx(t) - tx3.Attributes = []transaction.Attribute{{ - Type: transaction.NotValidBeforeT, - Value: &transaction.NotValidBefore{Height: bc.BlockHeight() + 1}, - }} - tx3.ValidUntilBlock = bc.BlockHeight() + 2 - require.NoError(t, testchain.SignTx(bc, tx3)) - - require.False(t, bc.IsTxStillRelevant(tx3, nil, false)) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.True(t, bc.IsTxStillRelevant(tx3, nil, false)) - }) - t.Run("contract witness check fails", func(t *testing.T) { - src := fmt.Sprintf(`package verify - import ( - "github.com/nspcc-dev/neo-go/pkg/interop/contract" - "github.com/nspcc-dev/neo-go/pkg/interop/util" - ) - func Verify() bool { - addr := util.FromAddress("`+address.Uint160ToString(bc.contracts.Ledger.Hash)+`") - currentHeight := contract.Call(addr, "currentIndex", contract.ReadStates) - return currentHeight.(int) < %d - }`, bc.BlockHeight()+2) // deploy + next block - txDeploy, h, _, err := testchain.NewDeployTx(bc, "TestVerify.go", neoOwner, strings.NewReader(src), nil) - require.NoError(t, err) - txDeploy.ValidUntilBlock = bc.BlockHeight() + 1 - addSigners(neoOwner, txDeploy) - require.NoError(t, testchain.SignTx(bc, txDeploy)) - require.NoError(t, bc.AddBlock(bc.newBlock(txDeploy))) - - tx := newTx(t) - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: h, - Scopes: transaction.None, - }) - tx.NetworkFee += 10_000_000 - require.NoError(t, testchain.SignTx(bc, tx)) - tx.Scripts = append(tx.Scripts, transaction.Witness{}) - - require.True(t, bc.IsTxStillRelevant(tx, mp, false)) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.False(t, bc.IsTxStillRelevant(tx, mp, false)) - }) -} - -func TestMemPoolRemoval(t *testing.T) { - const added = 16 - const notAdded = 32 - bc := newTestChain(t) - addedTxes := make([]*transaction.Transaction, added) - notAddedTxes := make([]*transaction.Transaction, notAdded) - for i := range addedTxes { - addedTxes[i] = bc.newTestTx(testchain.MultisigScriptHash(), []byte{byte(opcode.PUSH1)}) - require.NoError(t, testchain.SignTx(bc, addedTxes[i])) - require.NoError(t, bc.PoolTx(addedTxes[i])) - } - for i := range notAddedTxes { - notAddedTxes[i] = bc.newTestTx(testchain.MultisigScriptHash(), []byte{byte(opcode.PUSH1)}) - require.NoError(t, testchain.SignTx(bc, notAddedTxes[i])) - require.NoError(t, bc.PoolTx(notAddedTxes[i])) - } - b := bc.newBlock(addedTxes...) - require.NoError(t, bc.AddBlock(b)) - mempool := bc.GetMemPool() - for _, tx := range addedTxes { - require.False(t, mempool.ContainsKey(tx.Hash())) - } - for _, tx := range notAddedTxes { - require.True(t, mempool.ContainsKey(tx.Hash())) - } -} - -func TestHasBlock(t *testing.T) { - bc := newTestChain(t) - blocks, err := bc.genBlocks(50) - require.NoError(t, err) - - for i := 0; i < len(blocks); i++ { - assert.True(t, bc.HasBlock(blocks[i].Hash())) - } - newBlock := bc.newBlock() - assert.False(t, bc.HasBlock(newBlock.Hash())) -} - -func TestGetTransaction(t *testing.T) { - bc := newTestChain(t) - tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - tx1.ValidUntilBlock = 16 - tx1.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.CalledByEntry, - }} - tx2 := transaction.New([]byte{byte(opcode.PUSH2)}, 0) - tx2.ValidUntilBlock = 16 - tx2.Signers = []transaction.Signer{{ - Account: testchain.MultisigScriptHash(), - Scopes: transaction.CalledByEntry, - }} - require.NoError(t, testchain.SignTx(bc, tx1, tx2)) - b1 := bc.newBlock(tx1) - - assert.Nil(t, bc.AddBlock(b1)) - block := bc.newBlock(tx2) - txSize := io.GetVarSize(tx2) - assert.Nil(t, bc.AddBlock(block)) - - tx, height, err := bc.GetTransaction(block.Transactions[0].Hash()) - require.Nil(t, err) - assert.Equal(t, block.Index, height) - assert.Equal(t, txSize, tx.Size()) - assert.Equal(t, block.Transactions[0], tx) - assert.Equal(t, 1, io.GetVarSize(tx.Attributes)) - assert.Equal(t, 1, io.GetVarSize(tx.Scripts)) -} - -func TestGetClaimable(t *testing.T) { - bc := newTestChain(t) - - _, err := bc.genBlocks(10) - require.NoError(t, err) - - t.Run("first generation period", func(t *testing.T) { - amount, err := bc.CalculateClaimable(neoOwner, 1) - require.NoError(t, err) - require.EqualValues(t, big.NewInt(5*native.GASFactor/10), amount) - }) -} - -func TestClose(t *testing.T) { - defer func() { - r := recover() - assert.NotNil(t, r) - }() - bc := initTestChain(t, nil, nil) - go bc.Run() - _, err := bc.genBlocks(10) - require.NoError(t, err) - bc.Close() - // It's a hack, but we use internal knowledge of MemoryStore - // implementation which makes it completely unusable (up to panicing) - // after Close(). - bc.dao.Store.Put([]byte{0}, []byte{1}) - - // This should never be executed. - assert.Nil(t, t) -} - -func TestSubscriptions(t *testing.T) { - // We use buffering here as a substitute for reader goroutines, events - // get queued up and we read them one by one here. - const chBufSize = 16 - blockCh := make(chan *block.Block, chBufSize) - txCh := make(chan *transaction.Transaction, chBufSize) - notificationCh := make(chan *subscriptions.NotificationEvent, chBufSize) - executionCh := make(chan *state.AppExecResult, chBufSize) - - bc := newTestChain(t) - bc.SubscribeForBlocks(blockCh) - bc.SubscribeForTransactions(txCh) - bc.SubscribeForNotifications(notificationCh) - bc.SubscribeForExecutions(executionCh) - - assert.Empty(t, notificationCh) - assert.Empty(t, executionCh) - assert.Empty(t, blockCh) - assert.Empty(t, txCh) - - blocks, err := bc.genBlocks(1) - require.NoError(t, err) - require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond) - assert.Len(t, notificationCh, 1) // validator bounty - assert.Len(t, executionCh, 2) - assert.Empty(t, txCh) - - b := <-blockCh - assert.Equal(t, blocks[0], b) - assert.Empty(t, blockCh) - - aer := <-executionCh - assert.Equal(t, b.Hash(), aer.Container) - aer = <-executionCh - assert.Equal(t, b.Hash(), aer.Container) - - notif := <-notificationCh - require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) - - script := io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte("yay!")) - emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) - require.NoError(t, script.Err) - txGood1 := transaction.New(script.Bytes(), 0) - txGood1.Signers = []transaction.Signer{{Account: neoOwner}} - txGood1.Nonce = 1 - txGood1.ValidUntilBlock = 1024 - require.NoError(t, testchain.SignTx(bc, txGood1)) - - // Reset() reuses the script buffer and we need to keep scripts. - script = io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte("nay!")) - emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) - emit.Opcodes(script.BinWriter, opcode.THROW) - require.NoError(t, script.Err) - txBad := transaction.New(script.Bytes(), 0) - txBad.Signers = []transaction.Signer{{Account: neoOwner}} - txBad.Nonce = 2 - txBad.ValidUntilBlock = 1024 - require.NoError(t, testchain.SignTx(bc, txBad)) - - script = io.NewBufBinWriter() - emit.Bytes(script.BinWriter, []byte("yay! yay! yay!")) - emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) - require.NoError(t, script.Err) - txGood2 := transaction.New(script.Bytes(), 0) - txGood2.Signers = []transaction.Signer{{Account: neoOwner}} - txGood2.Nonce = 3 - txGood2.ValidUntilBlock = 1024 - require.NoError(t, testchain.SignTx(bc, txGood2)) - - invBlock := newBlock(bc.config, bc.BlockHeight()+1, bc.CurrentHeaderHash(), txGood1, txBad, txGood2) - require.NoError(t, bc.AddBlock(invBlock)) - - require.Eventually(t, func() bool { - return len(blockCh) != 0 && len(txCh) != 0 && - len(notificationCh) != 0 && len(executionCh) != 0 - }, time.Second, 10*time.Millisecond) - - b = <-blockCh - require.Equal(t, invBlock, b) - assert.Empty(t, blockCh) - - exec := <-executionCh - require.Equal(t, b.Hash(), exec.Container) - require.Equal(t, exec.VMState, vm.HaltState) - - // 3 burn events for every tx and 1 mint for primary node - require.True(t, len(notificationCh) >= 4) - for i := 0; i < 4; i++ { - notif := <-notificationCh - require.Equal(t, bc.contracts.GAS.Hash, notif.ScriptHash) - } - - // Follow in-block transaction order. - for _, txExpected := range invBlock.Transactions { - tx := <-txCh - require.Equal(t, txExpected, tx) - exec := <-executionCh - require.Equal(t, tx.Hash(), exec.Container) - if exec.VMState == vm.HaltState { - notif := <-notificationCh - require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash) - } - } - assert.Empty(t, txCh) - assert.Len(t, notificationCh, 1) - assert.Len(t, executionCh, 1) - - notif = <-notificationCh - require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) - - exec = <-executionCh - require.Equal(t, b.Hash(), exec.Container) - require.Equal(t, exec.VMState, vm.HaltState) - - bc.UnsubscribeFromBlocks(blockCh) - bc.UnsubscribeFromTransactions(txCh) - bc.UnsubscribeFromNotifications(notificationCh) - bc.UnsubscribeFromExecutions(executionCh) - - // Ensure that new blocks are processed correctly after unsubscription. - _, err = bc.genBlocks(2 * chBufSize) - require.NoError(t, err) -} - func TestRemoveOldTransfers(t *testing.T) { // Creating proper number of transfers/blocks takes unneccessary time, so emulate // some DB with stale entries. @@ -1562,182 +136,6 @@ func TestRemoveOldTransfers(t *testing.T) { } } -func TestRemoveUntraceable(t *testing.T) { - check := func(t *testing.T, bc *Blockchain, tHash, bHash, sHash util.Uint256, errorExpected bool) { - _, _, err := bc.GetTransaction(tHash) - if errorExpected { - require.Error(t, err) - } else { - require.NoError(t, err) - } - _, err = bc.GetAppExecResults(tHash, trigger.Application) - if errorExpected { - require.Error(t, err) - } else { - require.NoError(t, err) - } - _, err = bc.GetBlock(bHash) - if errorExpected { - require.Error(t, err) - } else { - require.NoError(t, err) - } - _, err = bc.GetHeader(bHash) - require.NoError(t, err) - if !sHash.Equals(util.Uint256{}) { - sm := bc.GetStateModule() - _, err = sm.GetState(sHash, []byte{0xfb, 0xff, 0xff, 0xff, 0x0e}) // NEO committee key. - if errorExpected { - require.Error(t, err) - } else { - require.NoError(t, err) - } - } - } - t.Run("P2PStateExchangeExtensions off", func(t *testing.T) { - bc := newTestChainWithCustomCfg(t, func(c *config.Config) { - c.ProtocolConfiguration.MaxTraceableBlocks = 2 - c.ProtocolConfiguration.GarbageCollectionPeriod = 2 - c.ProtocolConfiguration.RemoveUntraceableBlocks = true - }) - - tx1, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1) - require.NoError(t, err) - b1 := bc.newBlock(tx1) - require.NoError(t, bc.AddBlock(b1)) - tx1Height := bc.BlockHeight() - sRoot, err := bc.GetStateModule().GetStateRoot(tx1Height) - require.NoError(t, err) - - tx2, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1) - require.NoError(t, err) - require.NoError(t, bc.AddBlock(bc.newBlock(tx2))) - - _, h1, err := bc.GetTransaction(tx1.Hash()) - require.NoError(t, err) - require.Equal(t, tx1Height, h1) - - check(t, bc, tx1.Hash(), b1.Hash(), sRoot.Root, false) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.NoError(t, bc.AddBlock(bc.newBlock())) - // Don't wait for Run(). - _, err = bc.persist(true) - require.NoError(t, err) - bc.tryRunGC(0) - check(t, bc, tx1.Hash(), b1.Hash(), sRoot.Root, true) - }) - t.Run("P2PStateExchangeExtensions on", func(t *testing.T) { - bc := newTestChainWithCustomCfg(t, func(c *config.Config) { - c.ProtocolConfiguration.MaxTraceableBlocks = 2 - c.ProtocolConfiguration.GarbageCollectionPeriod = 2 - c.ProtocolConfiguration.RemoveUntraceableBlocks = true - c.ProtocolConfiguration.P2PStateExchangeExtensions = true - c.ProtocolConfiguration.StateSyncInterval = 2 - c.ProtocolConfiguration.StateRootInHeader = true - }) - - tx1, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1) - require.NoError(t, err) - b1 := bc.newBlock(tx1) - require.NoError(t, bc.AddBlock(b1)) - tx1Height := bc.BlockHeight() - sRoot, err := bc.GetStateModule().GetStateRoot(tx1Height) - require.NoError(t, err) - - tx2, err := testchain.NewTransferFromOwner(bc, bc.contracts.NEO.Hash, util.Uint160{}, 1, 0, bc.BlockHeight()+1) - require.NoError(t, err) - b2 := bc.newBlock(tx2) - require.NoError(t, bc.AddBlock(b2)) - tx2Height := bc.BlockHeight() - - _, h1, err := bc.GetTransaction(tx1.Hash()) - require.NoError(t, err) - require.Equal(t, tx1Height, h1) - - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.NoError(t, bc.AddBlock(bc.newBlock())) - require.NoError(t, bc.AddBlock(bc.newBlock())) - - check(t, bc, tx1.Hash(), b1.Hash(), sRoot.Root, false) - check(t, bc, tx2.Hash(), b2.Hash(), sRoot.Root, false) - - require.NoError(t, bc.AddBlock(bc.newBlock())) - - check(t, bc, tx1.Hash(), b1.Hash(), util.Uint256{}, true) - check(t, bc, tx2.Hash(), b2.Hash(), util.Uint256{}, false) - _, h2, err := bc.GetTransaction(tx2.Hash()) - require.NoError(t, err) - require.Equal(t, tx2Height, h2) - }) -} - -func TestInvalidNotification(t *testing.T) { - bc := newTestChain(t) - - cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - - aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack1") - require.NoError(t, err) - require.Equal(t, 1, len(aer.Stack)) - require.Nil(t, aer.Stack[0]) - - aer, err = invokeContractMethod(bc, 1_00000000, cs.Hash, "invalidStack2") - require.NoError(t, err) - require.Equal(t, 1, len(aer.Stack)) - require.Equal(t, stackitem.InteropT, aer.Stack[0].Type()) -} - -// Test that deletion of non-existent doesn't result in error in tx or block addition. -func TestMPTDeleteNoKey(t *testing.T) { - bc := newTestChain(t) - - cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "delValue", "non-existent-key") - require.NoError(t, err) - require.Equal(t, vm.HaltState, aer.VMState) -} - -// Test that UpdateHistory is added to ProtocolConfiguration for all native contracts -// for all default configurations. If UpdateHistory is not added to config, then -// native contract is disabled. It's easy to forget about config while adding new -// native contract. -func TestConfigNativeUpdateHistory(t *testing.T) { - var prefixPath = filepath.Join("..", "..", "config") - check := func(t *testing.T, cfgFileSuffix interface{}) { - cfgPath := filepath.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix)) - cfg, err := config.LoadFile(cfgPath) - require.NoError(t, err, fmt.Errorf("failed to load %s", cfgPath)) - natives := native.NewContracts(cfg.ProtocolConfiguration) - assert.Equal(t, len(natives.Contracts), - len(cfg.ProtocolConfiguration.NativeUpdateHistories), - fmt.Errorf("protocol configuration file %s: extra or missing NativeUpdateHistory in NativeActivations section", cfgPath)) - for _, c := range natives.Contracts { - assert.NotNil(t, cfg.ProtocolConfiguration.NativeUpdateHistories[c.Metadata().Name], - fmt.Errorf("protocol configuration file %s: configuration for %s native contract is missing in NativeActivations section; "+ - "edit the test if the contract should be disabled", cfgPath, c.Metadata().Name)) - } - } - testCases := []interface{}{ - netmode.MainNet, - netmode.PrivNet, - netmode.TestNet, - netmode.UnitTestNet, - "privnet.docker.one", - "privnet.docker.two", - "privnet.docker.three", - "privnet.docker.four", - "privnet.docker.single", - "unit_testnet.single", - } - for _, tc := range testCases { - check(t, tc) - } -} - func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) { var ( stateSyncInterval = 4 diff --git a/pkg/core/blockchain_neotest_test.go b/pkg/core/blockchain_neotest_test.go index 7caa409ef..e09be9a04 100644 --- a/pkg/core/blockchain_neotest_test.go +++ b/pkg/core/blockchain_neotest_test.go @@ -4,20 +4,47 @@ import ( "encoding/binary" "errors" "fmt" + "math/big" + "path/filepath" "strings" "testing" + "time" + "github.com/nspcc-dev/neo-go/internal/contracts" + "github.com/nspcc-dev/neo-go/internal/random" + "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "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/dao" + "github.com/nspcc-dev/neo-go/pkg/core/fee" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" + "github.com/nspcc-dev/neo-go/pkg/core/mempool" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativeprices" + "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/interop/native/roles" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/neotest" "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/rpc/response/result/subscriptions" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "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" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -336,3 +363,1614 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) { require.NoError(t, err) }) } + +func TestBlockchain_AddHeaders(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + + newHeader := func(t *testing.T, index uint32, prevHash util.Uint256, timestamp uint64) *block.Header { + b := e.NewUnsignedBlock(t) + b.Index = index + b.PrevHash = prevHash + b.PrevStateRoot = util.Uint256{} + b.Timestamp = timestamp + e.SignBlock(b) + return &b.Header + } + b1 := e.NewUnsignedBlock(t) + h1 := &e.SignBlock(b1).Header + h2 := newHeader(t, h1.Index+1, h1.Hash(), h1.Timestamp+1) + h3 := newHeader(t, h2.Index+1, h2.Hash(), h2.Timestamp+1) + + require.NoError(t, bc.AddHeaders()) + require.NoError(t, bc.AddHeaders(h1, h2)) + require.NoError(t, bc.AddHeaders(h2, h3)) + + assert.Equal(t, h3.Index, bc.HeaderHeight()) + assert.Equal(t, uint32(0), bc.BlockHeight()) + assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) + + // Add them again, they should not be added. + require.NoError(t, bc.AddHeaders(h3, h2, h1)) + + assert.Equal(t, h3.Index, bc.HeaderHeight()) + assert.Equal(t, uint32(0), bc.BlockHeight()) + assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) + + h4Bad := newHeader(t, h3.Index+1, h3.Hash().Reverse(), h3.Timestamp+1) + h5Bad := newHeader(t, h4Bad.Index+1, h4Bad.Hash(), h4Bad.Timestamp+1) + + assert.Error(t, bc.AddHeaders(h4Bad, h5Bad)) + assert.Equal(t, h3.Index, bc.HeaderHeight()) + assert.Equal(t, uint32(0), bc.BlockHeight()) + assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) + + h4Bad2 := newHeader(t, h3.Index+1, h3.Hash().Reverse(), h3.Timestamp+1) + h4Bad2.Script.InvocationScript = []byte{} + assert.Error(t, bc.AddHeaders(h4Bad2)) + assert.Equal(t, h3.Index, bc.HeaderHeight()) + assert.Equal(t, uint32(0), bc.BlockHeight()) + assert.Equal(t, h3.Hash(), bc.CurrentHeaderHash()) +} + +func TestBlockchain_AddBlockStateRoot(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + + sr, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight()) + require.NoError(t, err) + + b := e.NewUnsignedBlock(t) + b.StateRootEnabled = false + b.PrevStateRoot = util.Uint256{} + e.SignBlock(b) + err = bc.AddBlock(b) + require.True(t, errors.Is(err, core.ErrHdrStateRootSetting), "got: %v", err) + + u := sr.Root + u[0] ^= 0xFF + b = e.NewUnsignedBlock(t) + b.PrevStateRoot = u + e.SignBlock(b) + err = bc.AddBlock(b) + require.True(t, errors.Is(err, core.ErrHdrInvalidStateRoot), "got: %v", err) + + b = e.NewUnsignedBlock(t) + e.SignBlock(b) + require.NoError(t, bc.AddBlock(b)) +} + +func TestBlockchain_AddHeadersStateRoot(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.StateRootInHeader = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + + b := e.NewUnsignedBlock(t) + e.SignBlock(b) + h1 := b.Header + r := bc.GetStateModule().CurrentLocalStateRoot() + + // invalid stateroot + h1.PrevStateRoot[0] ^= 0xFF + require.True(t, errors.Is(bc.AddHeaders(&h1), core.ErrHdrInvalidStateRoot)) + + // valid stateroot + h1.PrevStateRoot = r + require.NoError(t, bc.AddHeaders(&h1)) + + // unable to verify stateroot (stateroot is computed for block #0 only => can + // verify stateroot of header #1 only) => just store the header + b = e.NewUnsignedBlock(t) + b.PrevHash = h1.Hash() + b.Timestamp = h1.Timestamp + 1 + b.PrevStateRoot = util.Uint256{} + b.Index = h1.Index + 1 + e.SignBlock(b) + require.NoError(t, bc.AddHeaders(&b.Header)) +} + +func TestBlockchain_AddBadBlock(t *testing.T) { + check := func(t *testing.T, b *block.Block, cfg func(c *config.ProtocolConfiguration)) { + bc, _ := chain.NewSingleWithCustomConfig(t, cfg) + err := bc.AddBlock(b) + if cfg == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + neoHash := e.NativeHash(t, nativenames.Neo) + + tx := e.NewUnsignedTx(t, neoHash, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + tx.ValidUntilBlock = 0 // Intentionally make the transaction invalid. + e.SignTx(t, tx, -1, acc) + b := e.NewUnsignedBlock(t, tx) + e.SignBlock(b) + check(t, b, nil) + check(t, b, func(c *config.ProtocolConfiguration) { + c.VerifyBlocks = false + }) + + b = e.NewUnsignedBlock(t) + b.PrevHash = util.Uint256{} // Intentionally make block invalid. + e.SignBlock(b) + check(t, b, nil) + check(t, b, func(c *config.ProtocolConfiguration) { + c.VerifyBlocks = false + }) + + tx = e.NewUnsignedTx(t, neoHash, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) // Check the good tx. + e.SignTx(t, tx, -1, acc) + b = e.NewUnsignedBlock(t, tx) + e.SignBlock(b) + check(t, b, func(c *config.ProtocolConfiguration) { + c.VerifyTransactions = true + c.VerifyBlocks = true + }) +} + +func TestBlockchain_GetHeader(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + block := e.AddNewBlock(t) + hash := block.Hash() + header, err := bc.GetHeader(hash) + require.NoError(t, err) + assert.Equal(t, &block.Header, header) + + b2 := e.NewUnsignedBlock(t) + _, err = bc.GetHeader(b2.Hash()) + assert.Error(t, err) +} + +func TestBlockchain_GetBlock(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + blocks := e.GenerateNewBlocks(t, 10) + neoValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Neo)) + + for i := 0; i < len(blocks); i++ { + block, err := bc.GetBlock(blocks[i].Hash()) + require.NoErrorf(t, err, "can't get block %d: %s", i, err) + assert.Equal(t, blocks[i].Index, block.Index) + assert.Equal(t, blocks[i].Hash(), block.Hash()) + } + + t.Run("store only header", func(t *testing.T) { + t.Run("non-empty block", func(t *testing.T) { + tx := neoValidatorInvoker.PrepareInvoke(t, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + b := e.NewUnsignedBlock(t, tx) + e.SignBlock(b) + require.NoError(t, bc.AddHeaders(&b.Header)) + + _, err := bc.GetBlock(b.Hash()) + require.Error(t, err) + + _, err = bc.GetHeader(b.Hash()) + require.NoError(t, err) + + require.NoError(t, bc.AddBlock(b)) + + _, err = bc.GetBlock(b.Hash()) + require.NoError(t, err) + }) + t.Run("empty block", func(t *testing.T) { + b := e.NewUnsignedBlock(t) + e.SignBlock(b) + + require.NoError(t, bc.AddHeaders(&b.Header)) + + _, err := bc.GetBlock(b.Hash()) + require.NoError(t, err) + }) + }) +} + +func TestBlockchain_VerifyHashAgainstScript(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + cs, csInvalid := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) + c1 := &neotest.Contract{ + Hash: cs.Hash, + NEF: &cs.NEF, + Manifest: &cs.Manifest, + } + c2 := &neotest.Contract{ + Hash: csInvalid.Hash, + NEF: &csInvalid.NEF, + Manifest: &csInvalid.Manifest, + } + e.DeployContract(t, c1, nil) + e.DeployContract(t, c2, nil) + + gas := bc.GetMaxVerificationGAS() + t.Run("Contract", func(t *testing.T) { + t.Run("Missing", func(t *testing.T) { + newH := cs.Hash + newH[0] = ^newH[0] + w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} + _, err := bc.VerifyWitness(newH, nil, w, gas) + require.True(t, errors.Is(err, core.ErrUnknownVerificationContract)) + }) + t.Run("Invalid", func(t *testing.T) { + w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} + _, err := bc.VerifyWitness(csInvalid.Hash, nil, w, gas) + require.True(t, errors.Is(err, core.ErrInvalidVerificationContract)) + }) + t.Run("ValidSignature", func(t *testing.T) { + w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}} + _, err := bc.VerifyWitness(cs.Hash, nil, w, gas) + require.NoError(t, err) + }) + t.Run("InvalidSignature", func(t *testing.T) { + w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}} + _, err := bc.VerifyWitness(cs.Hash, nil, w, gas) + require.True(t, errors.Is(err, core.ErrVerificationFailed)) + }) + }) + t.Run("NotEnoughGas", func(t *testing.T) { + verif := []byte{byte(opcode.PUSH1)} + w := &transaction.Witness{ + InvocationScript: []byte{byte(opcode.NOP)}, + VerificationScript: verif, + } + _, err := bc.VerifyWitness(hash.Hash160(verif), nil, w, 1) + require.True(t, errors.Is(err, core.ErrVerificationFailed)) + }) + t.Run("NoResult", func(t *testing.T) { + verif := []byte{byte(opcode.DROP)} + w := &transaction.Witness{ + InvocationScript: []byte{byte(opcode.PUSH1)}, + VerificationScript: verif, + } + _, err := bc.VerifyWitness(hash.Hash160(verif), nil, w, gas) + require.True(t, errors.Is(err, core.ErrVerificationFailed)) + }) + t.Run("BadResult", func(t *testing.T) { + verif := make([]byte, 66) + verif[0] = byte(opcode.PUSHDATA1) + verif[1] = 64 + w := &transaction.Witness{ + InvocationScript: []byte{byte(opcode.NOP)}, + VerificationScript: verif, + } + _, err := bc.VerifyWitness(hash.Hash160(verif), nil, w, gas) + require.True(t, errors.Is(err, core.ErrVerificationFailed)) + }) + t.Run("TooManyResults", func(t *testing.T) { + verif := []byte{byte(opcode.NOP)} + w := &transaction.Witness{ + InvocationScript: []byte{byte(opcode.PUSH1), byte(opcode.PUSH1)}, + VerificationScript: verif, + } + _, err := bc.VerifyWitness(hash.Hash160(verif), nil, w, gas) + require.True(t, errors.Is(err, core.ErrVerificationFailed)) + }) +} + +func TestBlockchain_IsTxStillRelevant(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + + mp := bc.GetMemPool() + + t.Run("small ValidUntilBlock", func(t *testing.T) { + tx := e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}, bc.BlockHeight()+1) + + require.True(t, bc.IsTxStillRelevant(tx, nil, false)) + e.AddNewBlock(t) + require.False(t, bc.IsTxStillRelevant(tx, nil, false)) + }) + + t.Run("tx is already persisted", func(t *testing.T) { + tx := e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}, bc.BlockHeight()+2) + + require.True(t, bc.IsTxStillRelevant(tx, nil, false)) + e.AddNewBlock(t, tx) + require.False(t, bc.IsTxStillRelevant(tx, nil, false)) + }) + + t.Run("tx with Conflicts attribute", func(t *testing.T) { + tx1 := e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}, bc.BlockHeight()+5) + + tx2 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) + tx2.Nonce = neotest.Nonce() + tx2.ValidUntilBlock = e.Chain.BlockHeight() + 5 + tx2.Attributes = []transaction.Attribute{{ + Type: transaction.ConflictsT, + Value: &transaction.Conflicts{Hash: tx1.Hash()}, + }} + e.SignTx(t, tx2, -1, acc) + + require.True(t, bc.IsTxStillRelevant(tx1, mp, false)) + require.NoError(t, bc.PoolTx(tx2)) + require.False(t, bc.IsTxStillRelevant(tx1, mp, false)) + }) + t.Run("NotValidBefore", func(t *testing.T) { + tx3 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) + tx3.Nonce = neotest.Nonce() + tx3.Attributes = []transaction.Attribute{{ + Type: transaction.NotValidBeforeT, + Value: &transaction.NotValidBefore{Height: bc.BlockHeight() + 1}, + }} + tx3.ValidUntilBlock = bc.BlockHeight() + 2 + e.SignTx(t, tx3, -1, acc) + + require.False(t, bc.IsTxStillRelevant(tx3, nil, false)) + e.AddNewBlock(t) + require.True(t, bc.IsTxStillRelevant(tx3, nil, false)) + }) + t.Run("contract witness check fails", func(t *testing.T) { + src := fmt.Sprintf(`package verify + import ( + "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/util" + ) + func Verify() bool { + addr := util.FromAddress("`+address.Uint160ToString(e.NativeHash(t, nativenames.Ledger))+`") + currentHeight := contract.Call(addr, "currentIndex", contract.ReadStates) + return currentHeight.(int) < %d + }`, bc.BlockHeight()+2) // deploy + next block + c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{ + Name: "verification_contract", + }) + e.DeployContract(t, c, nil) + + tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = bc.BlockHeight() + 2 + tx.Signers = []transaction.Signer{ + { + Account: c.Hash, + Scopes: transaction.None, + }, + } + tx.NetworkFee += 10_000_000 + tx.Scripts = []transaction.Witness{{}} + + require.True(t, bc.IsTxStillRelevant(tx, mp, false)) + e.AddNewBlock(t) + require.False(t, bc.IsTxStillRelevant(tx, mp, false)) + }) +} + +func TestBlockchain_MemPoolRemoval(t *testing.T) { + const added = 16 + const notAdded = 32 + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + addedTxes := make([]*transaction.Transaction, added) + notAddedTxes := make([]*transaction.Transaction, notAdded) + for i := range addedTxes { + addedTxes[i] = e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}, 100) + require.NoError(t, bc.PoolTx(addedTxes[i])) + } + for i := range notAddedTxes { + notAddedTxes[i] = e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}, 100) + require.NoError(t, bc.PoolTx(notAddedTxes[i])) + } + mempool := bc.GetMemPool() + e.AddNewBlock(t, addedTxes...) + for _, tx := range addedTxes { + require.False(t, mempool.ContainsKey(tx.Hash())) + } + for _, tx := range notAddedTxes { + require.True(t, mempool.ContainsKey(tx.Hash())) + } +} + +func TestBlockchain_HasBlock(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + blocks := e.GenerateNewBlocks(t, 10) + + for i := 0; i < len(blocks); i++ { + assert.True(t, bc.HasBlock(blocks[i].Hash())) + } + newBlock := e.NewUnsignedBlock(t) + assert.False(t, bc.HasBlock(newBlock.Hash())) +} + +func TestBlockchain_GetTransaction(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + tx1 := e.PrepareInvocation(t, []byte{byte(opcode.PUSH1)}, []neotest.Signer{acc}) + e.AddNewBlock(t, tx1) + + tx2 := e.PrepareInvocation(t, []byte{byte(opcode.PUSH2)}, []neotest.Signer{acc}) + tx2Size := io.GetVarSize(tx2) + b := e.AddNewBlock(t, tx2) + + tx, height, err := bc.GetTransaction(tx2.Hash()) + require.Nil(t, err) + assert.Equal(t, b.Index, height) + assert.Equal(t, tx2Size, tx.Size()) + assert.Equal(t, b.Transactions[0], tx) +} + +func TestBlockchain_GetClaimable(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + e.GenerateNewBlocks(t, 10) + + t.Run("first generation period", func(t *testing.T) { + amount, err := bc.CalculateClaimable(acc.ScriptHash(), 1) + require.NoError(t, err) + require.EqualValues(t, big.NewInt(5*native.GASFactor/10), amount) + }) +} + +func TestBlockchain_Close(t *testing.T) { + st := storage.NewMemoryStore() + bc, acc := chain.NewSingleWithCustomConfigAndStore(t, nil, st, false) + e := neotest.NewExecutor(t, bc, acc, acc) + go bc.Run() + e.GenerateNewBlocks(t, 10) + bc.Close() + // It's a hack, but we use internal knowledge of MemoryStore + // implementation which makes it completely unusable (up to panicing) + // after Close(). + require.Panics(t, func() { + _ = st.PutChangeSet(map[string][]byte{"0": {1}}, nil) + }) +} + +func TestBlockchain_Subscriptions(t *testing.T) { + // We use buffering here as a substitute for reader goroutines, events + // get queued up and we read them one by one here. + const chBufSize = 16 + blockCh := make(chan *block.Block, chBufSize) + txCh := make(chan *transaction.Transaction, chBufSize) + notificationCh := make(chan *subscriptions.NotificationEvent, chBufSize) + executionCh := make(chan *state.AppExecResult, chBufSize) + + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + nativeGASHash := e.NativeHash(t, nativenames.Gas) + bc.SubscribeForBlocks(blockCh) + bc.SubscribeForTransactions(txCh) + bc.SubscribeForNotifications(notificationCh) + bc.SubscribeForExecutions(executionCh) + + assert.Empty(t, notificationCh) + assert.Empty(t, executionCh) + assert.Empty(t, blockCh) + assert.Empty(t, txCh) + + generatedB := e.AddNewBlock(t) + require.Eventually(t, func() bool { return len(blockCh) != 0 }, time.Second, 10*time.Millisecond) + assert.Len(t, notificationCh, 1) // validator bounty + assert.Len(t, executionCh, 2) + assert.Empty(t, txCh) + + b := <-blockCh + assert.Equal(t, generatedB, b) + assert.Empty(t, blockCh) + + aer := <-executionCh + assert.Equal(t, b.Hash(), aer.Container) + aer = <-executionCh + assert.Equal(t, b.Hash(), aer.Container) + + notif := <-notificationCh + require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) + + script := io.NewBufBinWriter() + emit.Bytes(script.BinWriter, []byte("yay!")) + emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) + require.NoError(t, script.Err) + txGood1 := e.PrepareInvocation(t, script.Bytes(), []neotest.Signer{acc}) + + // Reset() reuses the script buffer and we need to keep scripts. + script = io.NewBufBinWriter() + emit.Bytes(script.BinWriter, []byte("nay!")) + emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) + emit.Opcodes(script.BinWriter, opcode.THROW) + require.NoError(t, script.Err) + txBad := e.PrepareInvocation(t, script.Bytes(), []neotest.Signer{acc}) + + script = io.NewBufBinWriter() + emit.Bytes(script.BinWriter, []byte("yay! yay! yay!")) + emit.Syscall(script.BinWriter, interopnames.SystemRuntimeNotify) + require.NoError(t, script.Err) + txGood2 := e.PrepareInvocation(t, script.Bytes(), []neotest.Signer{acc}) + + invBlock := e.AddNewBlock(t, txGood1, txBad, txGood2) + + require.Eventually(t, func() bool { + return len(blockCh) != 0 && len(txCh) != 0 && + len(notificationCh) != 0 && len(executionCh) != 0 + }, time.Second, 10*time.Millisecond) + + b = <-blockCh + require.Equal(t, invBlock, b) + assert.Empty(t, blockCh) + + exec := <-executionCh + require.Equal(t, b.Hash(), exec.Container) + require.Equal(t, exec.VMState, vm.HaltState) + + // 3 burn events for every tx and 1 mint for primary node + require.True(t, len(notificationCh) >= 4) + for i := 0; i < 4; i++ { + notif := <-notificationCh + require.Equal(t, nativeGASHash, notif.ScriptHash) + } + + // Follow in-block transaction order. + for _, txExpected := range invBlock.Transactions { + tx := <-txCh + require.Equal(t, txExpected, tx) + exec := <-executionCh + require.Equal(t, tx.Hash(), exec.Container) + if exec.VMState == vm.HaltState { + notif := <-notificationCh + require.Equal(t, hash.Hash160(tx.Script), notif.ScriptHash) + } + } + assert.Empty(t, txCh) + assert.Len(t, notificationCh, 1) + assert.Len(t, executionCh, 1) + + notif = <-notificationCh + require.Equal(t, bc.UtilityTokenHash(), notif.ScriptHash) + + exec = <-executionCh + require.Equal(t, b.Hash(), exec.Container) + require.Equal(t, exec.VMState, vm.HaltState) + + bc.UnsubscribeFromBlocks(blockCh) + bc.UnsubscribeFromTransactions(txCh) + bc.UnsubscribeFromNotifications(notificationCh) + bc.UnsubscribeFromExecutions(executionCh) + + // Ensure that new blocks are processed correctly after unsubscription. + e.GenerateNewBlocks(t, 2*chBufSize) +} + +func TestBlockchain_RemoveUntraceable(t *testing.T) { + neoCommitteeKey := []byte{0xfb, 0xff, 0xff, 0xff, 0x0e} + check := func(t *testing.T, bc *core.Blockchain, tHash, bHash, sHash util.Uint256, errorExpected bool) { + _, _, err := bc.GetTransaction(tHash) + if errorExpected { + require.Error(t, err) + } else { + require.NoError(t, err) + } + _, err = bc.GetAppExecResults(tHash, trigger.Application) + if errorExpected { + require.Error(t, err) + } else { + require.NoError(t, err) + } + _, err = bc.GetBlock(bHash) + if errorExpected { + require.Error(t, err) + } else { + require.NoError(t, err) + } + _, err = bc.GetHeader(bHash) + require.NoError(t, err) + if !sHash.Equals(util.Uint256{}) { + sm := bc.GetStateModule() + _, err = sm.GetState(sHash, neoCommitteeKey) + if errorExpected { + require.Error(t, err) + } else { + require.NoError(t, err) + } + } + } + t.Run("P2PStateExchangeExtensions off", func(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.MaxTraceableBlocks = 2 + c.GarbageCollectionPeriod = 2 + c.RemoveUntraceableBlocks = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + neoValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Neo)) + + tx1Hash := neoValidatorInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + tx1Height := bc.BlockHeight() + b1 := e.TopBlock(t) + sRoot, err := bc.GetStateModule().GetStateRoot(tx1Height) + require.NoError(t, err) + + neoValidatorInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + + _, h1, err := bc.GetTransaction(tx1Hash) + require.NoError(t, err) + require.Equal(t, tx1Height, h1) + + check(t, bc, tx1Hash, b1.Hash(), sRoot.Root, false) + e.GenerateNewBlocks(t, 4) + + sm := bc.GetStateModule() + require.Eventually(t, func() bool { + _, err = sm.GetState(sRoot.Root, neoCommitteeKey) + return err != nil + }, 2*bcPersistInterval, 10*time.Millisecond) + check(t, bc, tx1Hash, b1.Hash(), sRoot.Root, true) + }) + t.Run("P2PStateExchangeExtensions on", func(t *testing.T) { + bc, acc := chain.NewSingleWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.MaxTraceableBlocks = 2 + c.GarbageCollectionPeriod = 2 + c.RemoveUntraceableBlocks = true + c.P2PStateExchangeExtensions = true + c.StateSyncInterval = 2 + c.StateRootInHeader = true + }) + e := neotest.NewExecutor(t, bc, acc, acc) + neoValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Neo)) + + tx1Hash := neoValidatorInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + tx1Height := bc.BlockHeight() + b1 := e.TopBlock(t) + sRoot, err := bc.GetStateModule().GetStateRoot(tx1Height) + require.NoError(t, err) + + tx2Hash := neoValidatorInvoker.Invoke(t, true, "transfer", acc.ScriptHash(), util.Uint160{1, 2, 3}, 1, nil) + tx2Height := bc.BlockHeight() + b2 := e.TopBlock(t) + + _, h1, err := bc.GetTransaction(tx1Hash) + require.NoError(t, err) + require.Equal(t, tx1Height, h1) + + e.GenerateNewBlocks(t, 3) + + check(t, bc, tx1Hash, b1.Hash(), sRoot.Root, false) + check(t, bc, tx2Hash, b2.Hash(), sRoot.Root, false) + + e.AddNewBlock(t) + + check(t, bc, tx1Hash, b1.Hash(), util.Uint256{}, true) + check(t, bc, tx2Hash, b2.Hash(), util.Uint256{}, false) + _, h2, err := bc.GetTransaction(tx2Hash) + require.NoError(t, err) + require.Equal(t, tx2Height, h2) + }) +} + +func TestBlockchain_InvalidNotification(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) + e.DeployContract(t, &neotest.Contract{ + Hash: cs.Hash, + NEF: &cs.NEF, + Manifest: &cs.Manifest, + }, nil) + + cValidatorInvoker := e.ValidatorInvoker(cs.Hash) + cValidatorInvoker.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) { + require.Equal(t, 1, len(stack)) + require.Nil(t, stack[0]) + }, "invalidStack1") + cValidatorInvoker.Invoke(t, stackitem.NewInterop(nil), "invalidStack2") +} + +// Test that deletion of non-existent doesn't result in error in tx or block addition. +func TestBlockchain_MPTDeleteNoKey(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) + e.DeployContract(t, &neotest.Contract{ + Hash: cs.Hash, + NEF: &cs.NEF, + Manifest: &cs.Manifest, + }, nil) + + cValidatorInvoker := e.ValidatorInvoker(cs.Hash) + cValidatorInvoker.Invoke(t, stackitem.Null{}, "delValue", "non-existent-key") +} + +// Test that UpdateHistory is added to ProtocolConfiguration for all native contracts +// for all default configurations. If UpdateHistory is not added to config, then +// native contract is disabled. It's easy to forget about config while adding new +// native contract. +func TestConfigNativeUpdateHistory(t *testing.T) { + var prefixPath = filepath.Join("..", "..", "config") + check := func(t *testing.T, cfgFileSuffix interface{}) { + cfgPath := filepath.Join(prefixPath, fmt.Sprintf("protocol.%s.yml", cfgFileSuffix)) + cfg, err := config.LoadFile(cfgPath) + require.NoError(t, err, fmt.Errorf("failed to load %s", cfgPath)) + natives := native.NewContracts(cfg.ProtocolConfiguration) + assert.Equal(t, len(natives.Contracts), + len(cfg.ProtocolConfiguration.NativeUpdateHistories), + fmt.Errorf("protocol configuration file %s: extra or missing NativeUpdateHistory in NativeActivations section", cfgPath)) + for _, c := range natives.Contracts { + assert.NotNil(t, cfg.ProtocolConfiguration.NativeUpdateHistories[c.Metadata().Name], + fmt.Errorf("protocol configuration file %s: configuration for %s native contract is missing in NativeActivations section; "+ + "edit the test if the contract should be disabled", cfgPath, c.Metadata().Name)) + } + } + testCases := []interface{}{ + netmode.MainNet, + netmode.PrivNet, + netmode.TestNet, + netmode.UnitTestNet, + "privnet.docker.one", + "privnet.docker.two", + "privnet.docker.three", + "privnet.docker.four", + "privnet.docker.single", + "unit_testnet.single", + } + for _, tc := range testCases { + check(t, tc) + } +} + +func TestBlockchain_VerifyTx(t *testing.T) { + bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + c.ReservedAttributes = true + }) + e := neotest.NewExecutor(t, bc, validator, committee) + + accs := make([]*wallet.Account, 5) + for i := range accs { + var err error + accs[i], err = wallet.NewAccount() + require.NoError(t, err) + } + + notaryServiceFeePerKey := bc.GetNotaryServiceFeePerKey() + + oracleAcc := accs[2] + oraclePubs := keys.PublicKeys{oracleAcc.PrivateKey().PublicKey()} + require.NoError(t, oracleAcc.ConvertMultisig(1, oraclePubs)) + + neoHash := e.NativeHash(t, nativenames.Neo) + gasHash := e.NativeHash(t, nativenames.Gas) + policyHash := e.NativeHash(t, nativenames.Policy) + designateHash := e.NativeHash(t, nativenames.Designation) + notaryHash := e.NativeHash(t, nativenames.Notary) + oracleHash := e.NativeHash(t, nativenames.Oracle) + + neoValidatorsInvoker := e.ValidatorInvoker(neoHash) + gasValidatorsInvoker := e.ValidatorInvoker(gasHash) + policySuperInvoker := e.NewInvoker(policyHash, validator, committee) + designateSuperInvoker := e.NewInvoker(designateHash, validator, committee) + neoOwner := validator.ScriptHash() + + neoAmount := int64(1_000_000) + gasAmount := int64(1_000_000_000) + txs := make([]*transaction.Transaction, 0, 2*len(accs)+2) + for _, a := range accs { + txs = append(txs, neoValidatorsInvoker.PrepareInvoke(t, "transfer", neoOwner, a.Contract.ScriptHash(), neoAmount, nil)) + txs = append(txs, gasValidatorsInvoker.PrepareInvoke(t, "transfer", neoOwner, a.Contract.ScriptHash(), gasAmount, nil)) + } + txs = append(txs, neoValidatorsInvoker.PrepareInvoke(t, "transfer", neoOwner, committee.ScriptHash(), neoAmount, nil)) + txs = append(txs, gasValidatorsInvoker.PrepareInvoke(t, "transfer", neoOwner, committee.ScriptHash(), gasAmount, nil)) + e.AddNewBlock(t, txs...) + for _, tx := range txs { + e.CheckHalt(t, tx.Hash(), stackitem.NewBool(true)) + } + policySuperInvoker.Invoke(t, true, "blockAccount", accs[1].PrivateKey().GetScriptHash().BytesBE()) + + checkErr := func(t *testing.T, expectedErr error, tx *transaction.Transaction) { + err := bc.VerifyTx(tx) + require.True(t, errors.Is(err, expectedErr), "expected: %v, got: %v", expectedErr, err) + } + + testScript := []byte{byte(opcode.PUSH1)} + newTestTx := func(t *testing.T, signer util.Uint160, script []byte) *transaction.Transaction { + tx := transaction.New(script, 1_000_000) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = e.Chain.BlockHeight() + 5 + tx.Signers = []transaction.Signer{{ + Account: signer, + Scopes: transaction.CalledByEntry, + }} + tx.NetworkFee = int64(io.GetVarSize(tx)+200 /* witness */) * bc.FeePerByte() + tx.NetworkFee += 1_000_000 // verification cost + return tx + } + + h := accs[0].PrivateKey().GetScriptHash() + t.Run("Expired", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.ValidUntilBlock = 1 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrTxExpired, tx) + }) + t.Run("BlockedAccount", func(t *testing.T) { + tx := newTestTx(t, accs[1].PrivateKey().GetScriptHash(), testScript) + require.NoError(t, accs[1].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrPolicy, tx) + }) + t.Run("InsufficientGas", func(t *testing.T) { + balance := bc.GetUtilityTokenBalance(h) + tx := newTestTx(t, h, testScript) + tx.SystemFee = balance.Int64() + 1 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInsufficientFunds, tx) + }) + t.Run("TooBigTx", func(t *testing.T) { + script := make([]byte, transaction.MaxTransactionSize) + tx := newTestTx(t, h, script) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrTxTooBig, tx) + }) + t.Run("NetworkFee", func(t *testing.T) { + t.Run("SmallNetworkFee", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.NetworkFee = 1 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrTxSmallNetworkFee, tx) + }) + t.Run("AlmostEnoughNetworkFee", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) + expectedSize := io.GetVarSize(tx) + calcultedScriptSize + calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() + tx.NetworkFee = calculatedNetFee - 1 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + require.Equal(t, expectedSize, io.GetVarSize(tx)) + checkErr(t, core.ErrVerificationFailed, tx) + }) + t.Run("EnoughNetworkFee", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + verificationNetFee, calcultedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) + expectedSize := io.GetVarSize(tx) + calcultedScriptSize + calculatedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() + tx.NetworkFee = calculatedNetFee + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + require.Equal(t, expectedSize, io.GetVarSize(tx)) + require.NoError(t, bc.VerifyTx(tx)) + }) + t.Run("CalculateNetworkFee, signature script", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + expectedSize := io.GetVarSize(tx) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), accs[0].Contract.Script) + expectedSize += calculatedScriptSize + expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() + tx.NetworkFee = expectedNetFee + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + actualSize := io.GetVarSize(tx) + require.Equal(t, expectedSize, actualSize) + gasConsumed, err := bc.VerifyWitness(h, tx, &tx.Scripts[0], -1) + require.NoError(t, err) + require.Equal(t, verificationNetFee, gasConsumed) + require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed) + }) + t.Run("CalculateNetworkFee, multisignature script", func(t *testing.T) { + multisigAcc := accs[4] + pKeys := keys.PublicKeys{multisigAcc.PrivateKey().PublicKey()} + require.NoError(t, multisigAcc.ConvertMultisig(1, pKeys)) + multisigHash := hash.Hash160(multisigAcc.Contract.Script) + tx := newTestTx(t, multisigHash, testScript) + verificationNetFee, calculatedScriptSize := fee.Calculate(bc.GetBaseExecFee(), multisigAcc.Contract.Script) + expectedSize := io.GetVarSize(tx) + calculatedScriptSize + expectedNetFee := verificationNetFee + int64(expectedSize)*bc.FeePerByte() + tx.NetworkFee = expectedNetFee + require.NoError(t, multisigAcc.SignTx(netmode.UnitTestNet, tx)) + actualSize := io.GetVarSize(tx) + require.Equal(t, expectedSize, actualSize) + gasConsumed, err := bc.VerifyWitness(multisigHash, tx, &tx.Scripts[0], -1) + require.NoError(t, err) + require.Equal(t, verificationNetFee, gasConsumed) + require.Equal(t, expectedNetFee, bc.FeePerByte()*int64(actualSize)+gasConsumed) + }) + }) + t.Run("InvalidTxScript", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.Script = append(tx.Script, 0xff) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidScript, tx) + }) + t.Run("InvalidVerificationScript", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + verif := []byte{byte(opcode.JMP), 3, 0xff, byte(opcode.PUSHT)} + tx.Signers = append(tx.Signers, transaction.Signer{ + Account: hash.Hash160(verif), + Scopes: transaction.Global, + }) + tx.NetworkFee += 1000000 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + tx.Scripts = append(tx.Scripts, transaction.Witness{ + InvocationScript: []byte{}, + VerificationScript: verif, + }) + checkErr(t, core.ErrInvalidVerification, tx) + }) + t.Run("InvalidInvocationScript", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + verif := []byte{byte(opcode.PUSHT)} + tx.Signers = append(tx.Signers, transaction.Signer{ + Account: hash.Hash160(verif), + Scopes: transaction.Global, + }) + tx.NetworkFee += 1000000 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + tx.Scripts = append(tx.Scripts, transaction.Witness{ + InvocationScript: []byte{byte(opcode.JMP), 3, 0xff}, + VerificationScript: verif, + }) + checkErr(t, core.ErrInvalidInvocation, tx) + }) + t.Run("Conflict", func(t *testing.T) { + balance := bc.GetUtilityTokenBalance(h).Int64() + tx := newTestTx(t, h, testScript) + tx.NetworkFee = balance / 2 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + require.NoError(t, bc.PoolTx(tx)) + + tx2 := newTestTx(t, h, testScript) + tx2.NetworkFee = balance / 2 + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2)) + err := bc.PoolTx(tx2) + require.True(t, errors.Is(err, core.ErrMemPoolConflict)) + }) + t.Run("InvalidWitnessHash", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + tx.Scripts[0].VerificationScript = []byte{byte(opcode.PUSHT)} + checkErr(t, core.ErrWitnessHashMismatch, tx) + }) + t.Run("InvalidWitnessSignature", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + tx.Scripts[0].InvocationScript[10] = ^tx.Scripts[0].InvocationScript[10] + checkErr(t, core.ErrVerificationFailed, tx) + }) + t.Run("InsufficientNetworkFeeForSecondWitness", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.Signers = append(tx.Signers, transaction.Signer{ + Account: accs[3].PrivateKey().GetScriptHash(), + Scopes: transaction.Global, + }) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + require.NoError(t, accs[3].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrVerificationFailed, tx) + }) + t.Run("OldTX", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + e.AddNewBlock(t, tx) + + checkErr(t, core.ErrAlreadyExists, tx) + }) + t.Run("MemPooledTX", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + require.NoError(t, bc.PoolTx(tx)) + + err := bc.PoolTx(tx) + require.True(t, errors.Is(err, core.ErrAlreadyExists)) + }) + t.Run("MemPoolOOM", func(t *testing.T) { + mp := mempool.New(1, 0, false) + tx1 := newTestTx(t, h, testScript) + tx1.NetworkFee += 10000 // Give it more priority. + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx1)) + require.NoError(t, bc.PoolTx(tx1, mp)) + + tx2 := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx2)) + err := bc.PoolTx(tx2, mp) + require.True(t, errors.Is(err, core.ErrOOM)) + }) + t.Run("Attribute", func(t *testing.T) { + t.Run("InvalidHighPriority", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority}) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + t.Run("ValidHighPriority", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.HighPriority}) + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: committee.ScriptHash(), + Scopes: transaction.None, + }} + rawScript := committee.Script() + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() + tx.Scripts = []transaction.Witness{{ + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: rawScript, + }} + require.NoError(t, bc.VerifyTx(tx)) + }) + t.Run("Oracle", func(t *testing.T) { + cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) + e.DeployContract(t, &neotest.Contract{ + Hash: cs.Hash, + NEF: &cs.NEF, + Manifest: &cs.Manifest, + }, nil) + cInvoker := e.ValidatorInvoker(cs.Hash) + + const gasForResponse int64 = 10_000_000 + putOracleRequest(t, cInvoker, "https://get.1234", new(string), "handle", []byte{}, gasForResponse) + + oracleScript, err := smartcontract.CreateMajorityMultiSigRedeemScript(oraclePubs) + require.NoError(t, err) + oracleMultisigHash := hash.Hash160(oracleScript) + + respScript := native.CreateOracleResponseScript(oracleHash) + + // We need to create new transaction, + // because hashes are cached after signing. + getOracleTx := func(t *testing.T) *transaction.Transaction { + tx := transaction.New(respScript, 1000_0000) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = bc.BlockHeight() + 1 + resp := &transaction.OracleResponse{ + ID: 0, + Code: transaction.Success, + Result: []byte{1, 2, 3}, + } + tx.Attributes = []transaction.Attribute{{ + Type: transaction.OracleResponseT, + Value: resp, + }} + tx.NetworkFee += 4_000_000 // multisig check + tx.SystemFee = gasForResponse - tx.NetworkFee + tx.Signers = []transaction.Signer{{ + Account: oracleMultisigHash, + Scopes: transaction.None, + }} + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), oracleScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * bc.FeePerByte() + return tx + } + + t.Run("NoOracleNodes", func(t *testing.T) { + tx := getOracleTx(t) + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + + keys := make([]interface{}, 0, len(oraclePubs)) + for _, p := range oraclePubs { + keys = append(keys, p.Bytes()) + } + designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.Oracle), keys) + + t.Run("Valid", func(t *testing.T) { + tx := getOracleTx(t) + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + require.NoError(t, bc.VerifyTx(tx)) + + t.Run("NativeVerify", func(t *testing.T) { + tx.Signers = append(tx.Signers, transaction.Signer{ + Account: oracleHash, + Scopes: transaction.None, + }) + tx.Scripts = append(tx.Scripts, transaction.Witness{}) + t.Run("NonZeroVerification", func(t *testing.T) { + w := io.NewBufBinWriter() + emit.Opcodes(w.BinWriter, opcode.ABORT) + emit.Bytes(w.BinWriter, util.Uint160{}.BytesBE()) + emit.Int(w.BinWriter, 0) + emit.String(w.BinWriter, nativenames.Oracle) + tx.Scripts[len(tx.Scripts)-1].VerificationScript = w.Bytes() + err := bc.VerifyTx(tx) + require.True(t, errors.Is(err, core.ErrNativeContractWitness), "got: %v", err) + }) + t.Run("Good", func(t *testing.T) { + tx.Scripts[len(tx.Scripts)-1].VerificationScript = nil + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + }) + t.Run("InvalidRequestID", func(t *testing.T) { + tx := getOracleTx(t) + tx.Attributes[0].Value.(*transaction.OracleResponse).ID = 2 + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + t.Run("InvalidScope", func(t *testing.T) { + tx := getOracleTx(t) + tx.Signers[0].Scopes = transaction.Global + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + t.Run("InvalidScript", func(t *testing.T) { + tx := getOracleTx(t) + tx.Script = append(tx.Script, byte(opcode.NOP)) + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + t.Run("InvalidSigner", func(t *testing.T) { + tx := getOracleTx(t) + tx.Signers[0].Account = accs[0].Contract.ScriptHash() + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + t.Run("SmallFee", func(t *testing.T) { + tx := getOracleTx(t) + tx.SystemFee = 0 + require.NoError(t, oracleAcc.SignTx(netmode.UnitTestNet, tx)) + checkErr(t, core.ErrInvalidAttribute, tx) + }) + }) + t.Run("NotValidBefore", func(t *testing.T) { + getNVBTx := func(e *neotest.Executor, height uint32) *transaction.Transaction { + tx := newTestTx(t, h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotValidBeforeT, Value: &transaction.NotValidBefore{Height: height}}) + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: e.Validator.ScriptHash(), + Scopes: transaction.None, + }} + size := io.GetVarSize(tx) + rawScript := e.Validator.Script() + netFee, sizeDelta := fee.Calculate(e.Chain.GetBaseExecFee(), rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * e.Chain.FeePerByte() + tx.Scripts = []transaction.Witness{{ + InvocationScript: e.Validator.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: rawScript, + }} + return tx + } + t.Run("Disabled", func(t *testing.T) { + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = false + c.ReservedAttributes = false + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + tx := getNVBTx(eBad, bcBad.BlockHeight()) + err := bcBad.VerifyTx(tx) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "invalid attribute: NotValidBefore attribute was found, but P2PSigExtensions are disabled")) + }) + t.Run("Enabled", func(t *testing.T) { + t.Run("NotYetValid", func(t *testing.T) { + tx := getNVBTx(e, bc.BlockHeight()+1) + require.True(t, errors.Is(bc.VerifyTx(tx), core.ErrInvalidAttribute)) + }) + t.Run("positive", func(t *testing.T) { + tx := getNVBTx(e, bc.BlockHeight()) + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + }) + t.Run("Reserved", func(t *testing.T) { + getReservedTx := func(e *neotest.Executor, attrType transaction.AttrType) *transaction.Transaction { + tx := newTestTx(t, h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: attrType, Value: &transaction.Reserved{Value: []byte{1, 2, 3}}}) + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: e.Validator.ScriptHash(), + Scopes: transaction.None, + }} + rawScript := e.Validator.Script() + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(e.Chain.GetBaseExecFee(), rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * e.Chain.FeePerByte() + tx.Scripts = []transaction.Witness{{ + InvocationScript: e.Validator.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: rawScript, + }} + return tx + } + t.Run("Disabled", func(t *testing.T) { + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = false + c.ReservedAttributes = false + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + tx := getReservedTx(eBad, transaction.ReservedLowerBound+3) + err := bcBad.VerifyTx(tx) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "invalid attribute: attribute of reserved type was found, but ReservedAttributes are disabled")) + }) + t.Run("Enabled", func(t *testing.T) { + tx := getReservedTx(e, transaction.ReservedLowerBound+3) + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + t.Run("Conflicts", func(t *testing.T) { + getConflictsTx := func(e *neotest.Executor, hashes ...util.Uint256) *transaction.Transaction { + tx := newTestTx(t, h, testScript) + tx.Attributes = make([]transaction.Attribute, len(hashes)) + for i, h := range hashes { + tx.Attributes[i] = transaction.Attribute{ + Type: transaction.ConflictsT, + Value: &transaction.Conflicts{ + Hash: h, + }, + } + } + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: e.Validator.ScriptHash(), + Scopes: transaction.None, + }} + rawScript := e.Validator.Script() + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(e.Chain.GetBaseExecFee(), rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * e.Chain.FeePerByte() + tx.Scripts = []transaction.Witness{{ + InvocationScript: e.Validator.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: rawScript, + }} + return tx + } + t.Run("disabled", func(t *testing.T) { + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = false + c.ReservedAttributes = false + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + tx := getConflictsTx(eBad, util.Uint256{1, 2, 3}) + err := bcBad.VerifyTx(tx) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "invalid attribute: Conflicts attribute was found, but P2PSigExtensions are disabled")) + }) + t.Run("enabled", func(t *testing.T) { + t.Run("dummy on-chain conflict", func(t *testing.T) { + tx := newTestTx(t, h, testScript) + require.NoError(t, accs[0].SignTx(netmode.UnitTestNet, tx)) + conflicting := transaction.New([]byte{byte(opcode.RET)}, 1000_0000) + conflicting.ValidUntilBlock = bc.BlockHeight() + 1 + conflicting.Signers = []transaction.Signer{ + { + Account: validator.ScriptHash(), + Scopes: transaction.CalledByEntry, + }, + } + conflicting.Attributes = []transaction.Attribute{ + { + Type: transaction.ConflictsT, + Value: &transaction.Conflicts{ + Hash: tx.Hash(), + }, + }, + } + conflicting.NetworkFee = 1000_0000 + require.NoError(t, validator.SignTx(netmode.UnitTestNet, conflicting)) + e.AddNewBlock(t, conflicting) + require.True(t, errors.Is(bc.VerifyTx(tx), core.ErrHasConflicts)) + }) + t.Run("attribute on-chain conflict", func(t *testing.T) { + tx := neoValidatorsInvoker.Invoke(t, stackitem.NewBool(true), "transfer", neoOwner, neoOwner, 1, nil) + txConflict := getConflictsTx(e, tx) + require.Error(t, bc.VerifyTx(txConflict)) + }) + t.Run("positive", func(t *testing.T) { + tx := getConflictsTx(e, random.Uint256()) + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + }) + t.Run("NotaryAssisted", func(t *testing.T) { + notary, err := wallet.NewAccount() + require.NoError(t, err) + designateSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.P2PNotary), []interface{}{notary.PrivateKey().PublicKey().Bytes()}) + txSetNotary := transaction.New([]byte{byte(opcode.RET)}, 0) + txSetNotary.Signers = []transaction.Signer{ + { + Account: committee.ScriptHash(), + Scopes: transaction.Global, + }, + } + txSetNotary.Scripts = []transaction.Witness{{ + InvocationScript: e.Committee.SignHashable(uint32(netmode.UnitTestNet), txSetNotary), + VerificationScript: e.Committee.Script(), + }} + + getNotaryAssistedTx := func(e *neotest.Executor, signaturesCount uint8, serviceFee int64) *transaction.Transaction { + tx := newTestTx(t, h, testScript) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{ + NKeys: signaturesCount, + }}) + tx.NetworkFee += serviceFee // additional fee for NotaryAssisted attribute + tx.NetworkFee += 4_000_000 // multisig check + tx.Signers = []transaction.Signer{{ + Account: e.CommitteeHash, + Scopes: transaction.None, + }, + { + Account: notaryHash, + Scopes: transaction.None, + }, + } + rawScript := committee.Script() + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(e.Chain.GetBaseExecFee(), rawScript) + tx.NetworkFee += netFee + tx.NetworkFee += int64(size+sizeDelta) * e.Chain.FeePerByte() + tx.Scripts = []transaction.Witness{ + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: rawScript, + }, + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + } + return tx + } + t.Run("Disabled", func(t *testing.T) { + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = false + c.ReservedAttributes = false + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + tx := transaction.New(testScript, 1_000_000) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = e.Chain.BlockHeight() + 5 + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: 0}}) + tx.NetworkFee = 1_0000_0000 + eBad.SignTx(t, tx, 1_0000_0000, eBad.Committee) + err := bcBad.VerifyTx(tx) + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "invalid attribute: NotaryAssisted attribute was found, but P2PSigExtensions are disabled")) + }) + t.Run("Enabled, insufficient network fee", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, 0) + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("Test verify", func(t *testing.T) { + t.Run("no NotaryAssisted attribute", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Attributes = []transaction.Attribute{} + tx.Signers = []transaction.Signer{ + { + Account: committee.ScriptHash(), + Scopes: transaction.None, + }, + { + Account: notaryHash, + Scopes: transaction.None, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: committee.Script(), + }, + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("no deposit", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Signers = []transaction.Signer{ + { + Account: notaryHash, + Scopes: transaction.None, + }, + { + Account: committee.ScriptHash(), + Scopes: transaction.None, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: committee.Script(), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("bad Notary signer scope", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Signers = []transaction.Signer{ + { + Account: committee.ScriptHash(), + Scopes: transaction.None, + }, + { + Account: notaryHash, + Scopes: transaction.CalledByEntry, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: committee.Script(), + }, + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("not signed by Notary", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Signers = []transaction.Signer{ + { + Account: committee.ScriptHash(), + Scopes: transaction.None, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: committee.Script(), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("bad Notary node witness", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Signers = []transaction.Signer{ + { + Account: committee.ScriptHash(), + Scopes: transaction.None, + }, + { + Account: notaryHash, + Scopes: transaction.None, + }, + } + acc, err := keys.NewPrivateKey() + require.NoError(t, err) + tx.Scripts = []transaction.Witness{ + { + InvocationScript: committee.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: committee.Script(), + }, + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc.SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("missing payer", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + tx.Signers = []transaction.Signer{ + { + Account: notaryHash, + Scopes: transaction.None, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notary.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), tx)...), + }, + } + require.Error(t, bc.VerifyTx(tx)) + }) + t.Run("positive", func(t *testing.T) { + tx := getNotaryAssistedTx(e, 1, (1+1)*notaryServiceFeePerKey) + require.NoError(t, bc.VerifyTx(tx)) + }) + }) + }) + }) + t.Run("Partially-filled transaction", func(t *testing.T) { + getPartiallyFilledTx := func(nvb uint32, validUntil uint32) *transaction.Transaction { + tx := newTestTx(t, h, testScript) + tx.ValidUntilBlock = validUntil + tx.Attributes = []transaction.Attribute{ + { + Type: transaction.NotValidBeforeT, + Value: &transaction.NotValidBefore{Height: nvb}, + }, + { + Type: transaction.NotaryAssistedT, + Value: &transaction.NotaryAssisted{NKeys: 0}, + }, + } + tx.Signers = []transaction.Signer{ + { + Account: notaryHash, + Scopes: transaction.None, + }, + { + Account: validator.ScriptHash(), + Scopes: transaction.None, + }, + } + size := io.GetVarSize(tx) + netFee, sizeDelta := fee.Calculate(bc.GetBaseExecFee(), validator.Script()) + tx.NetworkFee = netFee + // multisig witness verification price + int64(size)*bc.FeePerByte() + // fee for unsigned size + int64(sizeDelta)*bc.FeePerByte() + // fee for multisig size + 66*bc.FeePerByte() + // fee for Notary signature size (66 bytes for Invocation script and 0 bytes for Verification script) + 2*bc.FeePerByte() + // fee for the length of each script in Notary witness (they are nil, so we did not take them into account during `size` calculation) + notaryServiceFeePerKey + // fee for Notary attribute + fee.Opcode(bc.GetBaseExecFee(), // Notary verification script + opcode.PUSHDATA1, opcode.RET, // invocation script + opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call + nativeprices.NotaryVerificationPrice*bc.GetBaseExecFee() // Notary witness verification price + tx.Scripts = []transaction.Witness{ + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...), + VerificationScript: []byte{}, + }, + { + InvocationScript: validator.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: validator.Script(), + }, + } + return tx + } + + mp := mempool.New(10, 1, false) + verificationF := func(tx *transaction.Transaction, data interface{}) error { + if data.(int) > 5 { + return errors.New("bad data") + } + return nil + } + t.Run("failed pre-verification", func(t *testing.T) { + tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+1) + require.Error(t, bc.PoolTxWithData(tx, 6, mp, bc, verificationF)) // here and below let's use `bc` instead of proper NotaryFeer for the test simplicity. + }) + t.Run("GasLimitExceeded during witness verification", func(t *testing.T) { + tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+1) + tx.NetworkFee-- // to check that NetworkFee was set correctly in getPartiallyFilledTx + tx.Scripts = []transaction.Witness{ + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64)...), + VerificationScript: []byte{}, + }, + { + InvocationScript: validator.SignHashable(uint32(netmode.UnitTestNet), tx), + VerificationScript: validator.Script(), + }, + } + require.Error(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF)) + }) + t.Run("bad NVB: too big", func(t *testing.T) { + tx := getPartiallyFilledTx(bc.BlockHeight()+bc.GetMaxNotValidBeforeDelta()+1, bc.BlockHeight()+1) + require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), core.ErrInvalidAttribute)) + }) + t.Run("bad ValidUntilBlock: too small", func(t *testing.T) { + tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+bc.GetMaxNotValidBeforeDelta()+1) + require.True(t, errors.Is(bc.PoolTxWithData(tx, 5, mp, bc, verificationF), core.ErrInvalidAttribute)) + }) + t.Run("good", func(t *testing.T) { + tx := getPartiallyFilledTx(bc.BlockHeight(), bc.BlockHeight()+1) + require.NoError(t, bc.PoolTxWithData(tx, 5, mp, bc, verificationF)) + }) + }) +} + +func TestBlockchain_Bug1728(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + + src := `package example + import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" + func init() { if true { } else { } } + func _deploy(_ interface{}, isUpdate bool) { + runtime.Log("Deploy") + }` + c := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(src), &compiler.Options{Name: "TestContract"}) + managementInvoker.DeployContract(t, c, nil) +} diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index e7f1cf38f..63134688a 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -1,42 +1,21 @@ package core import ( - "encoding/json" - "fmt" - "math/big" - "path/filepath" - "strings" "testing" "time" "github.com/nspcc-dev/neo-go/internal/testchain" - "github.com/nspcc-dev/neo-go/pkg/compiler" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" - "github.com/nspcc-dev/neo-go/pkg/core/fee" - "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/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/io" "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/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" - "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest" ) -// multisig address which possess all NEO. -var neoOwner = testchain.MultisigScriptHash() - // newTestChain should be called before newBlock invocation to properly setup // global state. func newTestChain(t testing.TB) *Blockchain { @@ -54,24 +33,6 @@ func newTestChainWithCustomCfgAndStore(t testing.TB, st storage.Store, f func(*c return chain } -func newLevelDBForTesting(t testing.TB) storage.Store { - dbPath := t.TempDir() - dbOptions := storage.LevelDBOptions{ - DataDirectoryPath: dbPath, - } - newLevelStore, err := storage.NewLevelDBStore(dbOptions) - require.Nil(t, err, "NewLevelDBStore error") - return newLevelStore -} - -func newBoltStoreForTesting(t testing.TB) storage.Store { - d := t.TempDir() - dbPath := filepath.Join(d, "test_bolt_db") - boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath}) - require.NoError(t, err) - return boltDBStore -} - func initTestChain(t testing.TB, st storage.Store, f func(*config.Config)) *Blockchain { chain, err := initTestChainNoCheck(t, st, f) require.NoError(t, err) @@ -165,246 +126,3 @@ func (bc *Blockchain) genBlocks(n int) ([]*block.Block, error) { } return blocks, nil } - -func TestBug1728(t *testing.T) { - src := `package example - import "github.com/nspcc-dev/neo-go/pkg/interop/runtime" - func init() { if true { } else { } } - func _deploy(_ interface{}, isUpdate bool) { - runtime.Log("Deploy") - }` - nf, di, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) - require.NoError(t, err) - m, err := di.ConvertToManifest(&compiler.Options{Name: "TestContract"}) - require.NoError(t, err) - - rawManifest, err := json.Marshal(m) - require.NoError(t, err) - rawNef, err := nf.Bytes() - require.NoError(t, err) - - bc := newTestChain(t) - - aer, err := invokeContractMethod(bc, 10000000000, - bc.contracts.Management.Hash, "deploy", rawNef, rawManifest) - require.NoError(t, err) - require.Equal(t, aer.VMState, vm.HaltState) -} - -func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction { - return newNEP17TransferWithAssert(sc, from, to, amount, true, additionalArgs...) -} - -func newNEP17TransferWithAssert(sc, from, to util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction { - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, sc, "transfer", callflag.All, from, to, amount, additionalArgs) - if needAssert { - emit.Opcodes(w.BinWriter, opcode.ASSERT) - } - if w.Err != nil { - panic(fmt.Errorf("failed to create NEP-17 transfer transaction: %w", w.Err)) - } - - script := w.Bytes() - return transaction.New(script, 11000000) -} - -func addSigners(sender util.Uint160, txs ...*transaction.Transaction) { - for _, tx := range txs { - tx.Signers = []transaction.Signer{{ - Account: sender, - Scopes: transaction.Global, - AllowedContracts: nil, - AllowedGroups: nil, - }} - } -} - -// Signer can be either bool or *wallet.Account. -// In the first case `true` means sign by committee, `false` means sign by validators. -func prepareContractMethodInvokeGeneric(chain *Blockchain, sysfee int64, - hash util.Uint160, method string, signer interface{}, args ...interface{}) (*transaction.Transaction, error) { - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, hash, method, callflag.All, args...) - if w.Err != nil { - return nil, w.Err - } - script := w.Bytes() - tx := transaction.New(script, 0) - tx.ValidUntilBlock = chain.blockHeight + 1 - var err error - switch s := signer.(type) { - case bool: - if s { - addSigners(testchain.CommitteeScriptHash(), tx) - setTxSystemFee(chain, sysfee, tx) - err = testchain.SignTxCommittee(chain, tx) - } else { - addSigners(neoOwner, tx) - setTxSystemFee(chain, sysfee, tx) - err = testchain.SignTx(chain, tx) - } - case *wallet.Account: - signTxWithAccounts(chain, sysfee, tx, s) - case []*wallet.Account: - signTxWithAccounts(chain, sysfee, tx, s...) - default: - panic("invalid signer") - } - if err != nil { - return nil, err - } - return tx, nil -} - -func setTxSystemFee(bc *Blockchain, sysFee int64, tx *transaction.Transaction) { - if sysFee >= 0 { - tx.SystemFee = sysFee - return - } - - lastBlock := bc.topBlock.Load().(*block.Block) - b := &block.Block{ - Header: block.Header{ - Index: lastBlock.Index + 1, - Timestamp: lastBlock.Timestamp + 1000, - }, - Transactions: []*transaction.Transaction{tx}, - } - - ttx := *tx // prevent setting 'hash' field - ic := bc.GetTestVM(trigger.Application, &ttx, b) - defer ic.Finalize() - - ic.VM.LoadWithFlags(tx.Script, callflag.All) - _ = ic.VM.Run() - tx.SystemFee = ic.VM.GasConsumed() -} - -func signTxWithAccounts(chain *Blockchain, sysFee int64, tx *transaction.Transaction, accs ...*wallet.Account) { - scope := transaction.CalledByEntry - for _, acc := range accs { - accH, _ := address.StringToUint160(acc.Address) - tx.Signers = append(tx.Signers, transaction.Signer{ - Account: accH, - Scopes: scope, - }) - scope = transaction.Global - } - setTxSystemFee(chain, sysFee, tx) - size := io.GetVarSize(tx) - for _, acc := range accs { - if acc.Contract.Deployed { - // don't need precise calculation for tests - tx.NetworkFee += 1000_0000 - continue - } - netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc.Contract.Script) - size += sizeDelta - tx.NetworkFee += netFee - } - tx.NetworkFee += int64(size) * chain.FeePerByte() - - for _, acc := range accs { - if err := acc.SignTx(testchain.Network(), tx); err != nil { - panic(err) - } - } -} - -func persistBlock(chain *Blockchain, txs ...*transaction.Transaction) ([]*state.AppExecResult, error) { - b := chain.newBlock(txs...) - err := chain.AddBlock(b) - if err != nil { - return nil, err - } - - aers := make([]*state.AppExecResult, len(txs)) - for i, tx := range txs { - res, err := chain.GetAppExecResults(tx.Hash(), trigger.Application) - if err != nil { - return nil, err - } - aers[i] = &res[0] - } - return aers, nil -} - -func invokeContractMethod(chain *Blockchain, sysfee int64, hash util.Uint160, method string, args ...interface{}) (*state.AppExecResult, error) { - return invokeContractMethodGeneric(chain, sysfee, hash, method, false, args...) -} - -func invokeContractMethodGeneric(chain *Blockchain, sysfee int64, hash util.Uint160, method string, - signer interface{}, args ...interface{}) (*state.AppExecResult, error) { - tx, err := prepareContractMethodInvokeGeneric(chain, sysfee, hash, - method, signer, args...) - if err != nil { - return nil, err - } - aers, err := persistBlock(chain, tx) - if err != nil { - return nil, err - } - return aers[0], nil -} - -func transferTokenFromMultisigAccountCheckOK(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) { - transferTx := transferTokenFromMultisigAccount(t, chain, to, tokenHash, amount, additionalArgs...) - res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application) - require.NoError(t, err) - require.Equal(t, vm.HaltState, res[0].VMState) - require.Equal(t, 0, len(res[0].Stack)) -} - -func transferTokenFromMultisigAccount(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, additionalArgs ...interface{}) *transaction.Transaction { - return transferTokenFromMultisigAccountWithAssert(t, chain, to, tokenHash, amount, true, additionalArgs...) -} - -func transferTokenFromMultisigAccountWithAssert(t *testing.T, chain *Blockchain, to, tokenHash util.Uint160, amount int64, needAssert bool, additionalArgs ...interface{}) *transaction.Transaction { - transferTx := newNEP17TransferWithAssert(tokenHash, testchain.MultisigScriptHash(), to, amount, needAssert, additionalArgs...) - transferTx.SystemFee = 100000000 - transferTx.ValidUntilBlock = chain.BlockHeight() + 1 - addSigners(neoOwner, transferTx) - require.NoError(t, testchain.SignTx(chain, transferTx)) - b := chain.newBlock(transferTx) - require.NoError(t, chain.AddBlock(b)) - return transferTx -} - -func checkResult(t *testing.T, result *state.AppExecResult, expected stackitem.Item) { - require.Equal(t, vm.HaltState, result.VMState, result.FaultException) - require.Equal(t, 1, len(result.Stack)) - require.Equal(t, expected, result.Stack[0]) -} - -func checkTxHalt(t testing.TB, bc *Blockchain, h util.Uint256) { - aer, err := bc.GetAppExecResults(h, trigger.Application) - require.NoError(t, err) - require.Equal(t, 1, len(aer)) - require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) -} - -func checkFAULTState(t *testing.T, result *state.AppExecResult) { - require.Equal(t, vm.FaultState, result.VMState) -} - -func checkBalanceOf(t *testing.T, chain *Blockchain, addr util.Uint160, expected int) { - balance := chain.GetUtilityTokenBalance(addr) - require.Equal(t, int64(expected), balance.Int64()) -} - -type NotaryFeerStub struct { - bc blockchainer.Blockchainer -} - -func (f NotaryFeerStub) FeePerByte() int64 { return f.bc.FeePerByte() } -func (f NotaryFeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int { - return f.bc.GetNotaryBalance(acc) -} -func (f NotaryFeerStub) BlockHeight() uint32 { return f.bc.BlockHeight() } -func (f NotaryFeerStub) P2PSigExtensionsEnabled() bool { return f.bc.P2PSigExtensionsEnabled() } -func NewNotaryFeerStub(bc blockchainer.Blockchainer) NotaryFeerStub { - return NotaryFeerStub{ - bc: bc, - } -} diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_core_test.go similarity index 84% rename from pkg/core/interop_system_test.go rename to pkg/core/interop_system_core_test.go index f46b04e2b..5224c0e4c 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_core_test.go @@ -3,20 +3,18 @@ package core import ( "errors" "fmt" - "math" "math/big" + "path/filepath" "testing" "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/iterator" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage" @@ -27,7 +25,6 @@ import ( "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/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/nef" @@ -40,6 +37,8 @@ import ( "github.com/stretchr/testify/require" ) +var pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts") + // Tests are taken from // https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs func TestRuntimeGetRandomCompatibility(t *testing.T) { @@ -69,29 +68,6 @@ func TestRuntimeGetRandomCompatibility(t *testing.T) { require.Equal(t, "217172703763162599519098299724476526911", ic.VM.Estack().Pop().BigInt().String()) } -func TestRuntimeGetRandomDifferentTransactions(t *testing.T) { - bc := newTestChain(t) - b, _ := bc.GetBlock(bc.GetHeaderHash(0)) - - tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - ic1 := bc.newInteropContext(trigger.Application, bc.dao.GetWrapped(), b, tx1) - ic1.VM = vm.New() - ic1.VM.LoadScript(tx1.Script) - - tx2 := transaction.New([]byte{byte(opcode.PUSH2)}, 0) - ic2 := bc.newInteropContext(trigger.Application, bc.dao.GetWrapped(), b, tx2) - ic2.VM = vm.New() - ic2.VM.LoadScript(tx2.Script) - - require.NoError(t, runtime.GetRandom(ic1)) - require.NoError(t, runtime.GetRandom(ic2)) - require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt()) - - require.NoError(t, runtime.GetRandom(ic1)) - require.NoError(t, runtime.GetRandom(ic2)) - require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt()) -} - func getSharpTestTx(sender util.Uint160) *transaction.Transaction { tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0) tx.Nonce = 0 @@ -113,81 +89,6 @@ func getSharpTestGenesis(t *testing.T) *block.Block { return b } -func TestContractCreateAccount(t *testing.T) { - v, ic, _ := createVM(t) - t.Run("Good", func(t *testing.T) { - priv, err := keys.NewPrivateKey() - require.NoError(t, err) - pub := priv.PublicKey() - v.Estack().PushVal(pub.Bytes()) - require.NoError(t, contractCreateStandardAccount(ic)) - - value := v.Estack().Pop().Bytes() - u, err := util.Uint160DecodeBytesBE(value) - require.NoError(t, err) - require.Equal(t, pub.GetScriptHash(), u) - }) - t.Run("InvalidKey", func(t *testing.T) { - v.Estack().PushVal([]byte{1, 2, 3}) - require.Error(t, contractCreateStandardAccount(ic)) - }) -} - -func TestContractCreateMultisigAccount(t *testing.T) { - v, ic, _ := createVM(t) - t.Run("Good", func(t *testing.T) { - m, n := 3, 5 - pubs := make(keys.PublicKeys, n) - arr := make([]stackitem.Item, n) - for i := range pubs { - pk, err := keys.NewPrivateKey() - require.NoError(t, err) - pubs[i] = pk.PublicKey() - arr[i] = stackitem.Make(pubs[i].Bytes()) - } - v.Estack().PushVal(stackitem.Make(arr)) - v.Estack().PushVal(m) - require.NoError(t, contractCreateMultisigAccount(ic)) - - expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs) - require.NoError(t, err) - value := v.Estack().Pop().Bytes() - u, err := util.Uint160DecodeBytesBE(value) - require.NoError(t, err) - require.Equal(t, hash.Hash160(expected), u) - }) - t.Run("InvalidKey", func(t *testing.T) { - v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make([]byte{1, 2, 3})})) - v.Estack().PushVal(1) - require.Error(t, contractCreateMultisigAccount(ic)) - }) - t.Run("Invalid m", func(t *testing.T) { - pk, err := keys.NewPrivateKey() - require.NoError(t, err) - v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())})) - v.Estack().PushVal(2) - require.Error(t, contractCreateMultisigAccount(ic)) - }) - t.Run("m overflows int64", func(t *testing.T) { - pk, err := keys.NewPrivateKey() - require.NoError(t, err) - v.Estack().PushVal(stackitem.Make([]stackitem.Item{stackitem.Make(pk.PublicKey().Bytes())})) - m := big.NewInt(math.MaxInt64) - m.Add(m, big.NewInt(1)) - v.Estack().PushVal(stackitem.NewBigInteger(m)) - require.Error(t, contractCreateMultisigAccount(ic)) - }) -} - -func TestRuntimeGasLeft(t *testing.T) { - v, ic, _ := createVM(t) - - v.GasLimit = 100 - v.AddGas(58) - require.NoError(t, runtime.GasLeft(ic)) - require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) -} - func TestRuntimeGetNotifications(t *testing.T) { v, ic, _ := createVM(t) @@ -1067,80 +968,3 @@ func TestRuntimeCheckWitness(t *testing.T) { }) }) } - -func TestLoadToken(t *testing.T) { - bc := newTestChain(t) - - cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - - t.Run("good", func(t *testing.T) { - aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT0", neoOwner.BytesBE()) - require.NoError(t, err) - realBalance, _ := bc.GetGoverningTokenBalance(neoOwner) - checkResult(t, aer, stackitem.Make(realBalance.Int64()+1)) - }) - t.Run("invalid param count", func(t *testing.T) { - aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT2") - require.NoError(t, err) - checkFAULTState(t, aer) - }) - t.Run("invalid contract", func(t *testing.T) { - aer, err := invokeContractMethod(bc, 1_00000000, cs.Hash, "callT1") - require.NoError(t, err) - checkFAULTState(t, aer) - }) -} - -func TestRuntimeGetNetwork(t *testing.T) { - bc := newTestChain(t) - - w := io.NewBufBinWriter() - emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork) - require.NoError(t, w.Err) - - tx := transaction.New(w.Bytes(), 10_000) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - addSigners(neoOwner, tx) - require.NoError(t, testchain.SignTx(bc, tx)) - - require.NoError(t, bc.AddBlock(bc.newBlock(tx))) - - aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) - require.NoError(t, err) - checkResult(t, &aer[0], stackitem.Make(uint32(bc.config.Magic))) -} - -func TestRuntimeBurnGas(t *testing.T) { - bc := newTestChain(t) - - cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - - const sysFee = 2_000000 - - t.Run("good", func(t *testing.T) { - aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(1)) - require.NoError(t, err) - require.Equal(t, vm.HaltState, aer.VMState) - - t.Run("gas limit exceeded", func(t *testing.T) { - aer, err = invokeContractMethod(bc, aer.GasConsumed, cs.Hash, "burnGas", int64(2)) - require.NoError(t, err) - require.Equal(t, vm.FaultState, aer.VMState) - }) - }) - t.Run("too big integer", func(t *testing.T) { - gas := big.NewInt(math.MaxInt64) - gas.Add(gas, big.NewInt(1)) - - aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", gas) - require.NoError(t, err) - checkFAULTState(t, aer) - }) - t.Run("zero GAS", func(t *testing.T) { - aer, err := invokeContractMethod(bc, sysFee, cs.Hash, "burnGas", int64(0)) - require.NoError(t, err) - checkFAULTState(t, aer) - }) -} diff --git a/pkg/core/interop_system_neotest_test.go b/pkg/core/interop_system_neotest_test.go new file mode 100644 index 000000000..f2ac39226 --- /dev/null +++ b/pkg/core/interop_system_neotest_test.go @@ -0,0 +1,245 @@ +package core_test + +import ( + "encoding/json" + "math" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/internal/contracts" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "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/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "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/stackitem" + "github.com/stretchr/testify/require" +) + +func TestSystemRuntimeGetRandom_DifferentTransactions(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + + w := io.NewBufBinWriter() + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetRandom) + require.NoError(t, w.Err) + script := w.Bytes() + + tx1 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) + tx2 := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) + e.AddNewBlock(t, tx1, tx2) + e.CheckHalt(t, tx1.Hash()) + e.CheckHalt(t, tx2.Hash()) + + res1 := e.GetTxExecResult(t, tx1.Hash()) + res2 := e.GetTxExecResult(t, tx2.Hash()) + + r1, err := res1.Stack[0].TryInteger() + require.NoError(t, err) + r2, err := res2.Stack[0].TryInteger() + require.NoError(t, err) + require.NotEqual(t, r1, r2) +} + +func TestSystemContractCreateStandardAccount(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + w := io.NewBufBinWriter() + + t.Run("Good", func(t *testing.T) { + priv, err := keys.NewPrivateKey() + require.NoError(t, err) + pub := priv.PublicKey() + + emit.Bytes(w.BinWriter, pub.Bytes()) + emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount) + require.NoError(t, w.Err) + script := w.Bytes() + + tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + + res := e.GetTxExecResult(t, tx.Hash()) + value := res.Stack[0].Value().([]byte) + u, err := util.Uint160DecodeBytesBE(value) + require.NoError(t, err) + require.Equal(t, pub.GetScriptHash(), u) + }) + t.Run("InvalidKey", func(t *testing.T) { + w.Reset() + emit.Bytes(w.BinWriter, []byte{1, 2, 3}) + emit.Syscall(w.BinWriter, interopnames.SystemContractCreateStandardAccount) + require.NoError(t, w.Err) + script := w.Bytes() + + tx := e.PrepareInvocation(t, script, []neotest.Signer{e.Validator}, bc.BlockHeight()+1) + e.AddNewBlock(t, tx) + e.CheckFault(t, tx.Hash(), "invalid prefix 1") + }) +} + +func TestSystemContractCreateMultisigAccount(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + w := io.NewBufBinWriter() + + createScript := func(t *testing.T, pubs []interface{}, m int) []byte { + w.Reset() + emit.Array(w.BinWriter, pubs...) + emit.Int(w.BinWriter, int64(m)) + emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount) + require.NoError(t, w.Err) + return w.Bytes() + } + t.Run("Good", func(t *testing.T) { + m, n := 3, 5 + pubs := make(keys.PublicKeys, n) + arr := make([]interface{}, n) + for i := range pubs { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + pubs[i] = pk.PublicKey() + arr[i] = pubs[i].Bytes() + } + script := createScript(t, arr, m) + + txH := e.InvokeScript(t, script, []neotest.Signer{acc}) + e.CheckHalt(t, txH) + res := e.GetTxExecResult(t, txH) + value := res.Stack[0].Value().([]byte) + u, err := util.Uint160DecodeBytesBE(value) + require.NoError(t, err) + expected, err := smartcontract.CreateMultiSigRedeemScript(m, pubs) + require.NoError(t, err) + require.Equal(t, hash.Hash160(expected), u) + }) + t.Run("InvalidKey", func(t *testing.T) { + script := createScript(t, []interface{}{[]byte{1, 2, 3}}, 1) + e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "invalid prefix 1") + }) + t.Run("Invalid m", func(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + script := createScript(t, []interface{}{pk.PublicKey().Bytes()}, 2) + e.InvokeScriptCheckFAULT(t, script, []neotest.Signer{acc}, "length of the signatures (2) is higher then the number of public keys") + }) + t.Run("m overflows int32", func(t *testing.T) { + pk, err := keys.NewPrivateKey() + require.NoError(t, err) + m := big.NewInt(math.MaxInt32) + m.Add(m, big.NewInt(1)) + w.Reset() + emit.Array(w.BinWriter, pk.Bytes()) + emit.BigInt(w.BinWriter, m) + emit.Syscall(w.BinWriter, interopnames.SystemContractCreateMultisigAccount) + require.NoError(t, w.Err) + e.InvokeScriptCheckFAULT(t, w.Bytes(), []neotest.Signer{acc}, "m must be positive and fit int32") + }) +} + +func TestSystemRuntimeGasLeft(t *testing.T) { + const runtimeGasLeftPrice = 1 << 4 + + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + w := io.NewBufBinWriter() + + gasLimit := 1100 + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft) + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGasLeft) + require.NoError(t, w.Err) + tx := transaction.New(w.Bytes(), int64(gasLimit)) + tx.Nonce = neotest.Nonce() + tx.ValidUntilBlock = e.Chain.BlockHeight() + 1 + e.SignTx(t, tx, int64(gasLimit), acc) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + res := e.GetTxExecResult(t, tx.Hash()) + l1 := res.Stack[0].Value().(*big.Int) + l2 := res.Stack[1].Value().(*big.Int) + + require.Equal(t, int64(gasLimit-runtimeGasLeftPrice*interop.DefaultBaseExecFee), l1.Int64()) + require.Equal(t, int64(gasLimit-2*runtimeGasLeftPrice*interop.DefaultBaseExecFee), l2.Int64()) +} + +func TestLoadToken(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) + rawManifest, err := json.Marshal(cs.Manifest) + require.NoError(t, err) + rawNef, err := cs.NEF.Bytes() + require.NoError(t, err) + tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + cInvoker := e.ValidatorInvoker(cs.Hash) + + t.Run("good", func(t *testing.T) { + realBalance, _ := bc.GetGoverningTokenBalance(acc.ScriptHash()) + cInvoker.Invoke(t, stackitem.NewBigInteger(big.NewInt(realBalance.Int64()+1)), "callT0", acc.ScriptHash()) + }) + t.Run("invalid param count", func(t *testing.T) { + cInvoker.InvokeFail(t, "method not found: callT2/1", "callT2", acc.ScriptHash()) + }) + t.Run("invalid contract", func(t *testing.T) { + cInvoker.InvokeFail(t, "token contract 0000000000000000000000000000000000000000 not found: key not found", "callT1") + }) +} + +func TestSystemRuntimeGetNetwork(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + w := io.NewBufBinWriter() + + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetNetwork) + require.NoError(t, w.Err) + e.InvokeScriptCheckHALT(t, w.Bytes(), []neotest.Signer{acc}, stackitem.NewBigInteger(big.NewInt(int64(bc.GetConfig().Magic)))) +} + +func TestSystemRuntimeBurnGas(t *testing.T) { + bc, acc := chain.NewSingle(t) + e := neotest.NewExecutor(t, bc, acc, acc) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 0, 1, acc.ScriptHash()) + rawManifest, err := json.Marshal(cs.Manifest) + require.NoError(t, err) + rawNef, err := cs.NEF.Bytes() + require.NoError(t, err) + tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + cInvoker := e.ValidatorInvoker(cs.Hash) + + t.Run("good", func(t *testing.T) { + h := cInvoker.Invoke(t, stackitem.Null{}, "burnGas", int64(1)) + res := e.GetTxExecResult(t, h) + + t.Run("gas limit exceeded", func(t *testing.T) { + tx := e.NewUnsignedTx(t, cs.Hash, "burnGas", int64(2)) + e.SignTx(t, tx, res.GasConsumed, acc) + e.AddNewBlock(t, tx) + e.CheckFault(t, tx.Hash(), "GAS limit exceeded") + }) + }) + t.Run("too big integer", func(t *testing.T) { + gas := big.NewInt(math.MaxInt64) + gas.Add(gas, big.NewInt(1)) + + cInvoker.InvokeFail(t, "invalid GAS value", "burnGas", gas) + }) + t.Run("zero GAS", func(t *testing.T) { + cInvoker.InvokeFail(t, "GAS must be positive", "burnGas", int64(0)) + }) +} diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 6d7241f19..2be2c46b4 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -1,324 +1,164 @@ -package core +package core_test import ( - "errors" - "math/big" + "encoding/json" + "fmt" + "strings" "testing" "github.com/nspcc-dev/neo-go/internal/contracts" - "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/fee" - "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "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/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) -type testNative struct { - meta interop.ContractMD - blocks chan uint32 -} - -func (tn *testNative) Initialize(_ *interop.Context) error { - return nil -} - -func (tn *testNative) Metadata() *interop.ContractMD { - return &tn.meta -} - -func (tn *testNative) OnPersist(ic *interop.Context) error { - select { - case tn.blocks <- ic.Block.Index: - return nil - default: - return errors.New("can't send index") - } -} - -func (tn *testNative) PostPersist(ic *interop.Context) error { - return nil -} - -var _ interop.Contract = (*testNative)(nil) - -// registerNative registers native contract in the blockchain. -func (bc *Blockchain) registerNative(c interop.Contract) { - bc.contracts.Contracts = append(bc.contracts.Contracts, c) - bc.config.NativeUpdateHistories[c.Metadata().Name] = c.Metadata().UpdateHistory -} - -const ( - testSumCPUFee = 1 << 15 // same as contract.Call - testSumStorageFee = 200 -) - -func newTestNative() *testNative { - cMD := interop.NewContractMD("Test.Native.Sum", 0) - cMD.UpdateHistory = []uint32{0} - tn := &testNative{ - meta: *cMD, - blocks: make(chan uint32, 1), - } - defer tn.meta.UpdateHash() - - desc := &manifest.Method{ - Name: "sum", - Parameters: []manifest.Parameter{ - manifest.NewParameter("addend1", smartcontract.IntegerType), - manifest.NewParameter("addend2", smartcontract.IntegerType), - }, - ReturnType: smartcontract.IntegerType, - Safe: true, - } - md := &interop.MethodAndPrice{ - Func: tn.sum, - CPUFee: testSumCPUFee, - StorageFee: testSumStorageFee, - RequiredFlags: callflag.NoneFlag, - } - tn.meta.AddMethod(md, desc) - - desc = &manifest.Method{ - Name: "callOtherContractNoReturn", - Parameters: []manifest.Parameter{ - manifest.NewParameter("contractHash", smartcontract.Hash160Type), - manifest.NewParameter("method", smartcontract.StringType), - manifest.NewParameter("arg", smartcontract.ArrayType), - }, - ReturnType: smartcontract.VoidType, - Safe: true, - } - md = &interop.MethodAndPrice{ - Func: tn.callOtherContractNoReturn, - CPUFee: testSumCPUFee, - RequiredFlags: callflag.NoneFlag} - tn.meta.AddMethod(md, desc) - - desc = &manifest.Method{ - Name: "callOtherContractWithReturn", - Parameters: []manifest.Parameter{ - manifest.NewParameter("contractHash", smartcontract.Hash160Type), - manifest.NewParameter("method", smartcontract.StringType), - manifest.NewParameter("arg", smartcontract.ArrayType), - }, - ReturnType: smartcontract.IntegerType, - } - md = &interop.MethodAndPrice{ - Func: tn.callOtherContractWithReturn, - CPUFee: testSumCPUFee, - RequiredFlags: callflag.NoneFlag} - tn.meta.AddMethod(md, desc) - - return tn -} - -func (tn *testNative) sum(_ *interop.Context, args []stackitem.Item) stackitem.Item { - s1, err := args[0].TryInteger() - if err != nil { - panic(err) - } - s2, err := args[1].TryInteger() - if err != nil { - panic(err) - } - return stackitem.NewBigInteger(s1.Add(s1, s2)) -} - -func toUint160(item stackitem.Item) util.Uint160 { - bs, err := item.TryBytes() - if err != nil { - panic(err) - } - u, err := util.Uint160DecodeBytesBE(bs) - if err != nil { - panic(err) - } - return u -} - -func (tn *testNative) call(ic *interop.Context, args []stackitem.Item, hasReturn bool) { - cs, err := ic.GetContract(toUint160(args[0])) - if err != nil { - panic(err) - } - bs, err := args[1].TryBytes() - if err != nil { - panic(err) - } - err = contract.CallFromNative(ic, tn.meta.Hash, cs, string(bs), args[2].Value().([]stackitem.Item), hasReturn) - if err != nil { - panic(err) - } -} - -func (tn *testNative) callOtherContractNoReturn(ic *interop.Context, args []stackitem.Item) stackitem.Item { - tn.call(ic, args, false) - return stackitem.Null{} -} - -func (tn *testNative) callOtherContractWithReturn(ic *interop.Context, args []stackitem.Item) stackitem.Item { - tn.call(ic, args, true) - bi := ic.VM.Estack().Pop().BigInt() - return stackitem.Make(bi.Add(bi, big.NewInt(1))) -} - func TestNativeContract_Invoke(t *testing.T) { - chain := newTestChain(t) + const ( + transferCPUFee = 1 << 17 + transferStorageFee = 50 + systemContractCallPrice = 1 << 15 + ) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + gasHash := e.NativeHash(t, nativenames.Gas) - tn := newTestNative() - chain.registerNative(tn) + baseExecFee := bc.GetBaseExecFee() + price := fee.Opcode(baseExecFee, opcode.SYSCALL, // System.Contract.Call + opcode.PUSHDATA1, // contract hash (20 byte) + opcode.PUSHDATA1, // method + opcode.PUSH15, // call flags + // `transfer` args: + opcode.PUSHDATA1, // from + opcode.PUSHDATA1, // to + opcode.PUSH1, // amount + opcode.PUSHNULL, // data + // end args + opcode.PUSH4, // amount of args + opcode.PACK, // pack args + ) + price += systemContractCallPrice*baseExecFee + // System.Contract.Call price + transferCPUFee*baseExecFee + // `transfer` itself + transferStorageFee*bc.GetStoragePrice() // `transfer` storage price - err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ - ContractBase: state.ContractBase{ - ID: 1, - NEF: tn.meta.NEF, - Hash: tn.meta.Hash, - Manifest: tn.meta.Manifest, - }, - }) - require.NoError(t, err) + tx := e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil) + e.SignTx(t, tx, -1, validator) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) - // System.Contract.Call + "sum" itself + opcodes for pushing arguments. - price := int64(testSumCPUFee * chain.GetBaseExecFee() * 2) - price += testSumStorageFee * chain.GetStoragePrice() - price += 3 * fee.Opcode(chain.GetBaseExecFee(), opcode.PUSHINT8) - price += 2 * fee.Opcode(chain.GetBaseExecFee(), opcode.SYSCALL, opcode.PUSHDATA1, opcode.PUSHINT8) - price += fee.Opcode(chain.GetBaseExecFee(), opcode.PACK) - res, err := invokeContractMethod(chain, price, tn.Metadata().Hash, "sum", int64(14), int64(28)) - require.NoError(t, err) - checkResult(t, res, stackitem.Make(42)) - _, err = chain.persist(false) - require.NoError(t, err) - - select { - case index := <-tn.blocks: - require.Equal(t, chain.blockHeight, index) - default: - require.Fail(t, "onPersist wasn't called") - } - - // Enough for Call and other opcodes, but not enough for "sum" call. - res, err = invokeContractMethod(chain, price-1, tn.Metadata().Hash, "sum", int64(14), int64(28)) - require.NoError(t, err) - checkFAULTState(t, res) + // Enough for Call and other opcodes, but not enough for "transfer" call. + tx = e.NewUnsignedTx(t, gasHash, "transfer", validator.ScriptHash(), validator.ScriptHash(), 1, nil) + e.SignTx(t, tx, price-1, validator) + e.AddNewBlock(t, tx) + e.CheckFault(t, tx.Hash(), "gas limit exceeded") } func TestNativeContract_InvokeInternal(t *testing.T) { - chain := newTestChain(t) - - tn := newTestNative() - chain.registerNative(tn) - - err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ - ContractBase: state.ContractBase{ - ID: 1, - NEF: tn.meta.NEF, - Manifest: tn.meta.Manifest, - }, - }) - require.NoError(t, err) - - d := dao.NewSimple(storage.NewMemoryStore(), chain.config.StateRootInHeader, chain.config.P2PSigExtensions) - ic := chain.newInteropContext(trigger.Application, d, nil, nil) - - sumOffset := 0 - for _, md := range tn.Metadata().Methods { - if md.MD.Name == "sum" { - sumOffset = md.MD.Offset - break - } - } + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + clState := bc.GetContractState(e.NativeHash(t, nativenames.CryptoLib)) + require.NotNil(t, clState) + md := clState.Manifest.ABI.GetMethod("ripemd160", 1) + require.NotNil(t, md) t.Run("fail, bad current script hash", func(t *testing.T) { + ic := bc.GetTestVM(trigger.Application, nil, nil) v := ic.SpawnVM() - v.LoadScriptWithHash(tn.Metadata().NEF.Script, util.Uint160{1, 2, 3}, callflag.All) - v.Estack().PushVal(14) - v.Estack().PushVal(28) - v.Context().Jump(sumOffset) + fakeH := util.Uint160{1, 2, 3} + v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All) + input := []byte{1, 2, 3, 4} + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) - // it's prohibited to call natives directly - require.Error(t, v.Run()) + // Bad current script hash + err := v.Run() + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error()) }) t.Run("fail, bad NativeUpdateHistory height", func(t *testing.T) { - tn.Metadata().UpdateHistory = []uint32{chain.blockHeight + 1} + bcBad, validatorBad, committeeBad := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.NativeUpdateHistories = map[string][]uint32{ + nativenames.Policy: {0}, + nativenames.Neo: {0}, + nativenames.Gas: {0}, + nativenames.Designation: {0}, + nativenames.StdLib: {0}, + nativenames.Management: {0}, + nativenames.Oracle: {0}, + nativenames.Ledger: {0}, + nativenames.CryptoLib: {1}, + } + }) + eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) + + ic := bcBad.GetTestVM(trigger.Application, nil, nil) v := ic.SpawnVM() - v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All) - v.Estack().PushVal(14) - v.Estack().PushVal(28) - v.Context().Jump(sumOffset) + v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history + input := []byte{1, 2, 3, 4} + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) - // it's prohibited to call natives before NativeUpdateHistory[0] height - require.Error(t, v.Run()) + // It's prohibited to call natives before NativeUpdateHistory[0] height. + err := v.Run() + require.Error(t, err) + require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1")) - // set the value back to 0 - tn.Metadata().UpdateHistory = []uint32{0} + // Add new block => CryptoLib should be active now. + eBad.AddNewBlock(t) + ic = bcBad.GetTestVM(trigger.Application, nil, nil) + v = ic.SpawnVM() + v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) + + require.NoError(t, v.Run()) + value := v.Estack().Pop().Bytes() + require.Equal(t, hash.RipeMD160(input).BytesBE(), value) }) t.Run("success", func(t *testing.T) { + ic := bc.GetTestVM(trigger.Application, nil, nil) v := ic.SpawnVM() - v.LoadScriptWithHash(tn.Metadata().NEF.Script, tn.Metadata().Hash, callflag.All) - v.Estack().PushVal(14) - v.Estack().PushVal(28) - v.Context().Jump(sumOffset) + v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) + input := []byte{1, 2, 3, 4} + v.Estack().PushVal(input) + v.Context().Jump(md.Offset) + require.NoError(t, v.Run()) - value := v.Estack().Pop().BigInt() - require.Equal(t, int64(42), value.Int64()) + value := v.Estack().Pop().Bytes() + require.Equal(t, hash.RipeMD160(input).BytesBE(), value) }) } func TestNativeContract_InvokeOtherContract(t *testing.T) { - chain := newTestChain(t) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + gasInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)) - tn := newTestNative() - chain.registerNative(tn) - - err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ - ContractBase: state.ContractBase{ - ID: 1, - Hash: tn.meta.Hash, - NEF: tn.meta.NEF, - Manifest: tn.meta.Manifest, - }, - }) + cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 1, 2, validator.ScriptHash()) + cs.Hash = state.CreateContractHash(validator.ScriptHash(), cs.NEF.Checksum, cs.Manifest.Name) // set proper hash + manifB, err := json.Marshal(cs.Manifest) require.NoError(t, err) + nefB, err := cs.NEF.Bytes() + require.NoError(t, err) + si, err := cs.ToStackItem() + require.NoError(t, err) + managementInvoker.Invoke(t, si, "deploy", nefB, manifB) - var drainTN = func(t *testing.T) { - select { - case <-tn.blocks: - default: - require.Fail(t, "testNative didn't send us block") - } - } - - cs, _ := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test - require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs)) - - baseFee := chain.GetBaseExecFee() t.Run("non-native, no return", func(t *testing.T) { - res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) - require.NoError(t, err) - drainTN(t) - require.Equal(t, vm.HaltState, res.VMState, res.FaultException) - checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty - }) - t.Run("non-native, with return", func(t *testing.T) { - res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash, - "callOtherContractWithReturn", cs.Hash, "ret7", []interface{}{}) - require.NoError(t, err) - drainTN(t) - checkResult(t, res, stackitem.Make(8)) + // `onNEP17Payment` will be invoked on test contract from GAS contract. + gasInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), cs.Hash, 1, nil) }) } diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index d8a5cb44c..3dd68f05f 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -10,69 +10,11 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" - "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" "github.com/stretchr/testify/require" ) -func (bc *Blockchain) setNodesByRole(t *testing.T, ok bool, r noderoles.Role, nodes keys.PublicKeys) { - w := io.NewBufBinWriter() - for _, pub := range nodes { - emit.Bytes(w.BinWriter, pub.Bytes()) - } - emit.Int(w.BinWriter, int64(len(nodes))) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.Int(w.BinWriter, int64(r)) - emit.Int(w.BinWriter, 2) - emit.Opcodes(w.BinWriter, opcode.PACK) - emit.AppCallNoArgs(w.BinWriter, bc.contracts.Designate.Hash, "designateAsRole", callflag.All) - require.NoError(t, w.Err) - tx := transaction.New(w.Bytes(), 0) - tx.NetworkFee = 10_000_000 - tx.SystemFee = 10_000_000 - tx.ValidUntilBlock = 100 - tx.Signers = []transaction.Signer{ - { - Account: testchain.MultisigScriptHash(), - Scopes: transaction.None, - }, - { - Account: testchain.CommitteeScriptHash(), - Scopes: transaction.CalledByEntry, - }, - } - require.NoError(t, testchain.SignTx(bc, tx)) - tx.Scripts = append(tx.Scripts, transaction.Witness{ - InvocationScript: testchain.SignCommittee(tx), - VerificationScript: testchain.CommitteeVerificationScript(), - }) - require.NoError(t, bc.AddBlock(bc.newBlock(tx))) - - aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) - require.NoError(t, err) - require.Equal(t, 1, len(aer)) - if ok { - require.Equal(t, vm.HaltState, aer[0].VMState) - require.Equal(t, 1, len(aer[0].Events)) - - ev := aer[0].Events[0] - require.Equal(t, bc.contracts.Designate.Hash, ev.ScriptHash) - require.Equal(t, native.DesignationEventName, ev.Name) - require.Equal(t, []stackitem.Item{ - stackitem.Make(int64(r)), - stackitem.Make(bc.BlockHeight()), - }, ev.Item.Value().([]stackitem.Item)) - } else { - require.Equal(t, vm.FaultState, aer[0].VMState) - require.Equal(t, 0, len(aer[0].Events)) - } -} - func TestDesignate_DesignateAsRole(t *testing.T) { bc := newTestChain(t) diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index b54a6f3f5..9c0c7b43c 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -1,20 +1,17 @@ -package core +package core_test import ( "fmt" "math/big" + "path/filepath" "testing" - "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/keys" - "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" - "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/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -40,96 +37,88 @@ func BenchmarkNEO_GetGASPerVote(t *testing.B) { } } -func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) { - bc := newTestChainWithCustomCfgAndStore(t, ps, nil) - - neo := bc.contracts.NEO - tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0) - ic := bc.newInteropContext(trigger.Application, bc.dao, nil, tx) - ic.SpawnVM() - ic.Block = bc.newBlock(tx) - - advanceChain := func(t *testing.B, count int) { - for i := 0; i < count; i++ { - require.NoError(t, bc.AddBlock(bc.newBlock())) - ic.Block.Index++ - } +func newLevelDBForTesting(t testing.TB) storage.Store { + dbPath := t.TempDir() + dbOptions := storage.LevelDBOptions{ + DataDirectoryPath: dbPath, } + newLevelStore, err := storage.NewLevelDBStore(dbOptions) + require.Nil(t, err, "NewLevelDBStore error") + return newLevelStore +} + +func newBoltStoreForTesting(t testing.TB) storage.Store { + d := t.TempDir() + dbPath := filepath.Join(d, "test_bolt_db") + boltDBStore, err := storage.NewBoltDBStore(storage.BoltDBOptions{FilePath: dbPath}) + require.NoError(t, err) + return boltDBStore +} + +func benchmarkGasPerVote(t *testing.B, ps storage.Store, nRewardRecords int, rewardDistance int) { + bc, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, ps, true) + cfg := bc.GetConfig() + + e := neotest.NewExecutor(t, bc, validators, committee) + neoHash := e.NativeHash(t, nativenames.Neo) + gasHash := e.NativeHash(t, nativenames.Gas) + neoSuperInvoker := e.NewInvoker(neoHash, validators, committee) + neoValidatorsInvoker := e.ValidatorInvoker(neoHash) + gasValidatorsInvoker := e.ValidatorInvoker(gasHash) // Vote for new committee. - sz := testchain.CommitteeSize() - accs := make([]*wallet.Account, sz) + sz := len(cfg.StandbyCommittee) + voters := make([]*wallet.Account, sz) candidates := make(keys.PublicKeys, sz) - txs := make([]*transaction.Transaction, 0, len(accs)) + txs := make([]*transaction.Transaction, 0, len(voters)*3) for i := 0; i < sz; i++ { priv, err := keys.NewPrivateKey() require.NoError(t, err) candidates[i] = priv.PublicKey() - accs[i], err = wallet.NewAccount() + voters[i], err = wallet.NewAccount() require.NoError(t, err) - require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[i])) + registerTx := neoSuperInvoker.PrepareInvoke(t, "registerCandidate", candidates[i].Bytes()) + txs = append(txs, registerTx) - to := accs[i].Contract.ScriptHash() - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All, - neoOwner.BytesBE(), to.BytesBE(), - big.NewInt(int64(sz-i)*1000000).Int64(), nil) - emit.Opcodes(w.BinWriter, opcode.ASSERT) - emit.AppCall(w.BinWriter, bc.contracts.GAS.Hash, "transfer", callflag.All, - neoOwner.BytesBE(), to.BytesBE(), - int64(1_000_000_000), nil) - emit.Opcodes(w.BinWriter, opcode.ASSERT) - require.NoError(t, w.Err) - tx := transaction.New(w.Bytes(), 1000_000_000) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - setSigner(tx, testchain.MultisigScriptHash()) - require.NoError(t, testchain.SignTx(bc, tx)) - txs = append(txs, tx) + to := voters[i].Contract.ScriptHash() + transferNeoTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, big.NewInt(int64(sz-i)*1000000).Int64(), nil) + txs = append(txs, transferNeoTx) + + transferGasTx := gasValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), to, int64(1_000_000_000), nil) + txs = append(txs, transferGasTx) } - require.NoError(t, bc.AddBlock(bc.newBlock(txs...))) + e.AddNewBlock(t, txs...) for _, tx := range txs { - checkTxHalt(t, bc, tx.Hash()) + e.CheckHalt(t, tx.Hash()) } + voteTxs := make([]*transaction.Transaction, 0, sz) for i := 0; i < sz; i++ { - priv := accs[i].PrivateKey() + priv := voters[i].PrivateKey() h := priv.GetScriptHash() - setSigner(tx, h) - ic.VM.Load(priv.PublicKey().GetVerificationScript()) - require.NoError(t, neo.VoteInternal(ic, h, candidates[i])) + voteTx := e.NewTx(t, []neotest.Signer{neotest.NewSingleSigner(voters[i])}, neoHash, "vote", h, candidates[i].Bytes()) + voteTxs = append(voteTxs, voteTx) + } + e.AddNewBlock(t, voteTxs...) + for _, tx := range voteTxs { + e.CheckHalt(t, tx.Hash()) } - _, err := ic.DAO.Persist() - require.NoError(t, err) // Collect set of nRewardRecords reward records for each voter. - advanceChain(t, nRewardRecords*testchain.CommitteeSize()) + e.GenerateNewBlocks(t, len(cfg.StandbyCommittee)) // Transfer some more NEO to first voter to update his balance height. - to := accs[0].Contract.ScriptHash() - w := io.NewBufBinWriter() - emit.AppCall(w.BinWriter, bc.contracts.NEO.Hash, "transfer", callflag.All, - neoOwner.BytesBE(), to.BytesBE(), int64(1), nil) - emit.Opcodes(w.BinWriter, opcode.ASSERT) - require.NoError(t, w.Err) - tx = transaction.New(w.Bytes(), 1000_000_000) - tx.ValidUntilBlock = bc.BlockHeight() + 1 - setSigner(tx, testchain.MultisigScriptHash()) - require.NoError(t, testchain.SignTx(bc, tx)) - require.NoError(t, bc.AddBlock(bc.newBlock(tx))) - - aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) - require.NoError(t, err) - require.Equal(t, 1, len(aer)) - require.Equal(t, vm.HaltState, aer[0].VMState, aer[0].FaultException) + to := voters[0].Contract.ScriptHash() + neoValidatorsInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), to, int64(1), nil) // Advance chain one more time to avoid same start/end rewarding bounds. - advanceChain(t, rewardDistance) + e.GenerateNewBlocks(t, rewardDistance) end := bc.BlockHeight() t.ResetTimer() t.ReportAllocs() t.StartTimer() for i := 0; i < t.N; i++ { - _, err := neo.CalculateBonus(ic.DAO, to, end) + _, err := bc.CalculateClaimable(to, end) require.NoError(t, err) } t.StopTimer() diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index a980e91fd..211f7476c 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -1,49 +1,67 @@ -package core +package core_test import ( + "fmt" "testing" - "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/stretchr/testify/require" ) -func TestFeePerByte(t *testing.T) { - chain := newTestChain(t) +func TestPolicy_FeePerByte(t *testing.T) { + bc, _, _ := chain.NewMulti(t) t.Run("get, internal method", func(t *testing.T) { - n := chain.contracts.Policy.GetFeePerByteInternal(chain.dao) + n := bc.FeePerByte() require.Equal(t, 1000, int(n)) }) } -func TestExecFeeFactor(t *testing.T) { - chain := newTestChain(t) +func TestPolicy_ExecFeeFactor(t *testing.T) { + bc, _, _ := chain.NewMulti(t) t.Run("get, internal method", func(t *testing.T) { - n := chain.contracts.Policy.GetExecFeeFactorInternal(chain.dao) + n := bc.GetBaseExecFee() require.EqualValues(t, interop.DefaultBaseExecFee, n) }) } -func TestStoragePrice(t *testing.T) { - chain := newTestChain(t) +func TestPolicy_StoragePrice(t *testing.T) { + bc, validators, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validators, committee) t.Run("get, internal method", func(t *testing.T) { - n := chain.contracts.Policy.GetStoragePriceInternal(chain.dao) + e.AddNewBlock(t) // avoid default value got from Blockchain. + + n := bc.GetStoragePrice() require.Equal(t, int64(native.DefaultStoragePrice), n) }) } -func TestBlockedAccounts(t *testing.T) { - chain := newTestChain(t) - transferTokenFromMultisigAccount(t, chain, testchain.CommitteeScriptHash(), - chain.contracts.GAS.Hash, 100_00000000) +func TestPolicy_BlockedAccounts(t *testing.T) { + bc, validators, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validators, committee) + policyHash := e.NativeHash(t, nativenames.Policy) + policySuperInvoker := e.NewInvoker(policyHash, validators, committee) + unlucky := e.NewAccount(t, 5_0000_0000) + policyUnluckyInvoker := e.NewInvoker(policyHash, unlucky) + + // Block unlucky account. + policySuperInvoker.Invoke(t, true, "blockAccount", unlucky.ScriptHash()) + + // Transaction from blocked account shouldn't be accepted. t.Run("isBlocked, internal method", func(t *testing.T) { - isBlocked := chain.contracts.Policy.IsBlockedInternal(chain.dao, random.Uint160()) - require.Equal(t, false, isBlocked) + tx := policyUnluckyInvoker.PrepareInvoke(t, "getStoragePrice") + b := e.NewUnsignedBlock(t, tx) + e.SignBlock(b) + expectedErr := fmt.Sprintf("transaction %s failed to verify: not allowed by policy: account %s is blocked", tx.Hash().StringLE(), unlucky.ScriptHash().StringLE()) + err := e.Chain.AddBlock(b) + require.Error(t, err) + require.Equal(t, expectedErr, err.Error()) }) } diff --git a/pkg/core/notary_test.go b/pkg/core/notary_test.go index 236bae50b..46f2e38db 100644 --- a/pkg/core/notary_test.go +++ b/pkg/core/notary_test.go @@ -1,8 +1,9 @@ -package core +package core_test import ( "errors" "fmt" + "math/big" "math/rand" "path" "path/filepath" @@ -10,29 +11,32 @@ import ( "testing" "time" - "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "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/mempool" - "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "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/interop/native/roles" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" + "github.com/nspcc-dev/neo-go/pkg/network" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/services/notary" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "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/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -var notaryModulePath = filepath.Join("..", "services", "notary") - -func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) { +func getTestNotary(t *testing.T, bc *core.Blockchain, walletPath, pass string, onTx func(tx *transaction.Transaction) error) (*wallet.Account, *notary.Notary, *mempool.Pool) { mainCfg := config.P2PNotary{ Enabled: true, UnlockWallet: config.Wallet{ @@ -46,7 +50,7 @@ func getTestNotary(t *testing.T, bc *Blockchain, walletPath, pass string, onTx f Log: zaptest.NewLogger(t), } mp := mempool.New(10, 1, true) - ntr, err := notary.NewNotary(cfg, testchain.Network(), mp, onTx) + ntr, err := notary.NewNotary(cfg, netmode.UnitTestNet, mp, onTx) require.NoError(t, err) w, err := wallet.NewWalletFromFile(path.Join(notaryModulePath, walletPath)) @@ -68,7 +72,14 @@ func dupNotaryRequest(t *testing.T, p *payload.P2PNotaryRequest) *payload.P2PNot } func TestNotary(t *testing.T) { - bc := newTestChain(t) + bc, validators, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) { + c.P2PSigExtensions = true + }) + e := neotest.NewExecutor(t, bc, validators, committee) + notaryHash := e.NativeHash(t, nativenames.Notary) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validators, committee) + gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)) + var ( nonce uint32 nvbDiffFallback uint32 = 20 @@ -145,8 +156,9 @@ func TestNotary(t *testing.T) { mp1.StopSubscriptions() }) - notaryNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()} - bc.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodes) + notaryNodes := []interface{}{acc1.PrivateKey().PublicKey().Bytes(), acc2.PrivateKey().PublicKey().Bytes()} + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.P2PNotary), notaryNodes) type requester struct { accounts []*wallet.Account @@ -193,7 +205,7 @@ func TestNotary(t *testing.T) { VerificationScript: []byte{}, }, } - err = requester.SignTx(testchain.Network(), fallback) + err = requester.SignTx(netmode.UnitTestNet, fallback) require.NoError(t, err) return fallback } @@ -251,7 +263,7 @@ func TestNotary(t *testing.T) { for j := range main.Scripts { main.Scripts[j].VerificationScript = verificationScripts[j] if i == j { - main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(testchain.Network()), main)...) + main.Scripts[j].InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, acc.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), main)...) } } main.Scripts = append(main.Scripts, transaction.Witness{}) // empty Notary witness @@ -296,12 +308,11 @@ func TestNotary(t *testing.T) { require.Equal(t, io.GetVarSize(completedTx), completedTx.Size()) for i := 0; i < len(completedTx.Scripts)-1; i++ { - interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx) - _, err := bc.verifyHashAgainstScript(completedTx.Signers[i].Account, &completedTx.Scripts[i], interopCtx, -1) + _, err := bc.VerifyWitness(completedTx.Signers[i].Account, completedTx, &completedTx.Scripts[i], -1) require.NoError(t, err) } require.Equal(t, transaction.Witness{ - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), requests[0].MainTransaction)...), + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), requests[0].MainTransaction)...), VerificationScript: []byte{}, }, completedTx.Scripts[len(completedTx.Scripts)-1]) } else { @@ -316,15 +327,14 @@ func TestNotary(t *testing.T) { require.Equal(t, 2, len(completedTx.Signers)) require.Equal(t, 2, len(completedTx.Scripts)) require.Equal(t, transaction.Witness{ - InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(testchain.Network()), req.FallbackTransaction)...), + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, acc1.PrivateKey().SignHashable(uint32(netmode.UnitTestNet), req.FallbackTransaction)...), VerificationScript: []byte{}, }, completedTx.Scripts[0]) // check that tx size was updated require.Equal(t, io.GetVarSize(completedTx), completedTx.Size()) - interopCtx := bc.newInteropContext(trigger.Verification, bc.dao, nil, completedTx) - _, err := bc.verifyHashAgainstScript(completedTx.Signers[1].Account, &completedTx.Scripts[1], interopCtx, -1) + _, err := bc.VerifyWitness(completedTx.Signers[1].Account, completedTx, &completedTx.Scripts[1], -1) require.NoError(t, err) } else { completedTx := getCompletedTx(t, false, req.FallbackTransaction.Hash()) @@ -483,7 +493,8 @@ func TestNotary(t *testing.T) { checkFallbackTxs(t, r, false) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + + e.AddNewBlock(t) checkMainTx(t, requesters, r, 1, false) checkFallbackTxs(t, r, false) // set account back for the next tests @@ -494,11 +505,11 @@ func TestNotary(t *testing.T) { requests, requesters := checkCompleteStandardRequest(t, 3, false) // check PostPersist with finalisation error setFinalizeWithError(true) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) // check PostPersist without finalisation error setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), true) // PostPersist: complete main transaction, multisignature account @@ -507,12 +518,12 @@ func TestNotary(t *testing.T) { checkFallbackTxs(t, requests, false) // check PostPersist with finalisation error setFinalizeWithError(true) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // check PostPersist without finalisation error setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), true) checkFallbackTxs(t, requests, false) @@ -521,15 +532,15 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteStandardRequest(t, 3, false) checkFallbackTxs(t, requests, false) // make fallbacks valid - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) // check PostPersist for valid fallbacks with finalisation error - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // check PostPersist for valid fallbacks without finalisation error setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, true) @@ -540,15 +551,15 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false) checkFallbackTxs(t, requests, false) // make fallbacks valid - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) // check PostPersist for valid fallbacks with finalisation error - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // check PostPersist for valid fallbacks without finalisation error setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests[:nSigs], true) // the rest of fallbacks should also be applied even if the main tx was already constructed by the moment they were sent @@ -559,14 +570,14 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteStandardRequest(t, 5, false) checkFallbackTxs(t, requests, false) // make fallbacks valid - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) // some of fallbacks should fail finalisation unluckies = []*payload.P2PNotaryRequest{requests[0], requests[4]} lucky := requests[1:4] setChoosy(true) // check PostPersist for lucky fallbacks - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, unluckies, false) @@ -574,7 +585,7 @@ func TestNotary(t *testing.T) { setChoosy(false) setFinalizeWithError(false) // check PostPersist for unlucky fallbacks - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, lucky, true) checkFallbackTxs(t, unluckies, true) @@ -585,19 +596,19 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteStandardRequest(t, 5, false, 1, 2, 3, 4, 5) checkFallbackTxs(t, requests, false) // generate blocks to reach the most earlier fallback's NVB - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) // check PostPersist for valid fallbacks without finalisation error // Add block before allowing tx to finalize to exclude race condition when // main transaction is finalized between `finalizeWithError` restore and adding new block. - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) mtx.RLock() start := len(completedTxes) mtx.RUnlock() setFinalizeWithError(false) for i := range requests { if i != 0 { - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) } require.Eventually(t, func() bool { mtx.RLock() @@ -615,13 +626,13 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteStandardRequest(t, 4, false) checkFallbackTxs(t, requests, false) // make fallbacks valid and remove one fallback - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) ntr1.UpdateNotaryNodes(keys.PublicKeys{randomAcc.PublicKey()}) ntr1.OnRequestRemoval(requests[3]) // non of the fallbacks should be completed setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // set account back for the next tests @@ -633,13 +644,13 @@ func TestNotary(t *testing.T) { requests, requesters = checkCompleteStandardRequest(t, 4, false) checkFallbackTxs(t, requests, false) // make fallbacks valid and remove one fallback - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) unlucky := requests[3] ntr1.OnRequestRemoval(unlucky) // rest of the fallbacks should be completed setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests[:3], true) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) @@ -648,20 +659,20 @@ func TestNotary(t *testing.T) { setFinalizeWithError(true) requests, requesters = checkCompleteStandardRequest(t, 4, false) // remove all fallbacks - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) for i := range requests { ntr1.OnRequestRemoval(requests[i]) } // then the whole request should be removed, i.e. there are no completed transactions setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // OnRequestRemoval: signature request, remove unexisting fallback ntr1.OnRequestRemoval(requests[0]) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) @@ -673,13 +684,13 @@ func TestNotary(t *testing.T) { checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // make fallbacks valid and remove the last fallback - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) unlucky = requests[nSigs-1] ntr1.OnRequestRemoval(unlucky) // then (m-1) out of n fallbacks should be completed setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests[:nSigs-1], true) require.Nil(t, completedTxes[unlucky.FallbackTransaction.Hash()]) @@ -690,20 +701,20 @@ func TestNotary(t *testing.T) { setFinalizeWithError(true) requests, requesters = checkCompleteMultisigRequest(t, nSigs, nKeys, false) // make fallbacks valid and then remove all of them - _, err = bc.genBlocks(int(nvbDiffFallback)) + e.GenerateNewBlocks(t, int(nvbDiffFallback)) require.NoError(t, err) for i := range requests { ntr1.OnRequestRemoval(requests[i]) } // then the whole request should be removed, i.e. there are no completed transactions setFinalizeWithError(false) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) // // OnRequestRemoval: multisignature request, remove unexisting fallbac, i.e. there still shouldn't be any completed transactions after this ntr1.OnRequestRemoval(requests[0]) - require.NoError(t, bc.AddBlock(bc.newBlock())) + e.AddNewBlock(t) checkMainTx(t, requesters, requests, len(requests), false) checkFallbackTxs(t, requests, false) @@ -712,11 +723,11 @@ func TestNotary(t *testing.T) { requester1, _ := wallet.NewAccount() requester2, _ := wallet.NewAccount() amount := int64(100_0000_0000) - feer := NewNotaryFeerStub(bc) - transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50)) - checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(amount)) - transferTokenFromMultisigAccountCheckOK(t, bc, bc.GetNotaryContractScriptHash(), bc.contracts.GAS.Hash, amount, requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight()+50)) - checkBalanceOf(t, bc, bc.contracts.Notary.Hash, int(2*amount)) + gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester1.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)}) + e.CheckGASBalance(t, notaryHash, big.NewInt(amount)) + gasValidatorInvoker.Invoke(t, true, "transfer", e.Validator.ScriptHash(), bc.GetNotaryContractScriptHash(), amount, []interface{}{requester2.PrivateKey().PublicKey().GetScriptHash(), int64(bc.BlockHeight() + 50)}) + e.CheckGASBalance(t, notaryHash, big.NewInt(2*amount)) + // create request for 2 standard signatures => main tx should be completed after the second request is added to the pool requests = createMixedRequest([]requester{ { @@ -728,6 +739,7 @@ func TestNotary(t *testing.T) { typ: notary.Signature, }, }) + feer := network.NewNotaryFeer(bc) require.NoError(t, mp1.Add(requests[0].FallbackTransaction, feer, requests[0])) require.NoError(t, mp1.Add(requests[1].FallbackTransaction, feer, requests[1])) require.Eventually(t, func() bool { diff --git a/pkg/core/oracle_test.go b/pkg/core/oracle_test.go index 4f5b898ac..5529c39ca 100644 --- a/pkg/core/oracle_test.go +++ b/pkg/core/oracle_test.go @@ -1,7 +1,9 @@ -package core +package core_test import ( "bytes" + "encoding/binary" + "encoding/json" "errors" "fmt" gio "io" @@ -16,37 +18,38 @@ import ( "github.com/nspcc-dev/neo-go/internal/contracts" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" - "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/native" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/interop/native/roles" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/util/slice" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) -var ( - oracleModulePath = filepath.Join("..", "services", "oracle") - pathToInternalContracts = filepath.Join("..", "..", "internal", "contracts") -) +var oracleModulePath = filepath.Join("..", "services", "oracle") -func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain, +func putOracleRequest(t *testing.T, oracleValidatorInvoker *neotest.ContractInvoker, url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 { var filtItem interface{} if filter != nil { filtItem = *filter } - res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL", - url, filtItem, cb, userData, gas) - require.NoError(t, err) - return res.Container + return oracleValidatorInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas) } -func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string, returnOracleRedirectionErrOn func(address string) bool) oracle.Config { +func getOracleConfig(t *testing.T, bc *core.Blockchain, w, pass string, returnOracleRedirectionErrOn func(address string) bool) oracle.Config { return oracle.Config{ Log: zaptest.NewLogger(t), Network: netmode.UnitTestNet, @@ -63,7 +66,7 @@ func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string, returnOracleR } } -func getTestOracle(t *testing.T, bc *Blockchain, walletPath, pass string) ( +func getTestOracle(t *testing.T, bc *core.Blockchain, walletPath, pass string) ( *wallet.Account, *oracle.Oracle, map[uint64]*responseWithSig, @@ -87,7 +90,19 @@ func getTestOracle(t *testing.T, bc *Blockchain, walletPath, pass string) ( // Compatibility test from C# code. // https://github.com/neo-project/neo-modules/blob/master/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs#L61 func TestCreateResponseTx(t *testing.T) { - bc := newTestChain(t) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + + cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) + rawManifest, err := json.Marshal(cs.Manifest) + require.NoError(t, err) + rawNef, err := cs.NEF.Bytes() + require.NoError(t, err) + tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + cInvoker := e.ValidatorInvoker(cs.Hash) require.Equal(t, int64(30), bc.GetBaseExecFee()) require.Equal(t, int64(1000), bc.FeePerByte()) @@ -106,10 +121,10 @@ func TestCreateResponseTx(t *testing.T) { Code: transaction.Success, Result: []byte{0}, } - require.NoError(t, bc.contracts.Oracle.PutRequestInternal(1, req, bc.dao)) + cInvoker.Invoke(t, stackitem.Null{}, "requestURL", req.URL, *req.Filter, req.CallbackMethod, req.UserData, int64(req.GasForResponse)) orc.UpdateOracleNodes(keys.PublicKeys{acc.PrivateKey().PublicKey()}) bc.SetOracle(orc) - tx, err := orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) + tx, err = orc.CreateResponseTx(int64(req.GasForResponse), 1, resp) require.NoError(t, err) assert.Equal(t, 166, tx.Size()) assert.Equal(t, int64(2198650), tx.NetworkFee) @@ -117,7 +132,7 @@ func TestCreateResponseTx(t *testing.T) { } func TestOracle_InvalidWallet(t *testing.T) { - bc := newTestChain(t) + bc, _, _ := chain.NewMulti(t) _, err := oracle.NewOracle(getOracleConfig(t, bc, "./testdata/oracle1.json", "invalid", nil)) require.Error(t, err) @@ -127,45 +142,65 @@ func TestOracle_InvalidWallet(t *testing.T) { } func TestOracle(t *testing.T) { - bc := newTestChain(t) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + managementInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Management)) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + nativeOracleH := e.NativeHash(t, nativenames.Oracle) + nativeOracleID := e.NativeID(t, nativenames.Oracle) - oracleCtr := bc.contracts.Oracle acc1, orc1, m1, ch1 := getTestOracle(t, bc, "./testdata/oracle1.json", "one") acc2, orc2, m2, ch2 := getTestOracle(t, bc, "./testdata/oracle2.json", "two") oracleNodes := keys.PublicKeys{acc1.PrivateKey().PublicKey(), acc2.PrivateKey().PublicKey()} // Must be set in native contract for tx verification. - bc.setNodesByRole(t, true, noderoles.Oracle, oracleNodes) + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.Oracle), []interface{}{oracleNodes[0].Bytes(), oracleNodes[1].Bytes()}) orc1.UpdateOracleNodes(oracleNodes.Copy()) orc2.UpdateOracleNodes(oracleNodes.Copy()) - orcNative := bc.contracts.Oracle - md, ok := orcNative.GetMethod(manifest.MethodVerify, -1) - require.True(t, ok) - orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) - orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) + nativeOracleState := bc.GetContractState(nativeOracleH) + require.NotNil(t, nativeOracleState) + md := nativeOracleState.Manifest.ABI.GetMethod(manifest.MethodVerify, -1) + require.NotNil(t, md) + oracleRespScript := native.CreateOracleResponseScript(nativeOracleH) + orc1.UpdateNativeContract(nativeOracleState.NEF.Script, slice.Copy(oracleRespScript), nativeOracleH, md.Offset) + orc2.UpdateNativeContract(nativeOracleState.NEF.Script, slice.Copy(oracleRespScript), nativeOracleH, md.Offset) - cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) + cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) + rawManifest, err := json.Marshal(cs.Manifest) + require.NoError(t, err) + rawNef, err := cs.NEF.Bytes() + require.NoError(t, err) + tx := managementInvoker.PrepareInvoke(t, "deploy", rawNef, rawManifest) + e.AddNewBlock(t, tx) + e.CheckHalt(t, tx.Hash()) + cInvoker := e.ValidatorInvoker(cs.Hash) - putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.timeout", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.notfound", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.forbidden", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://private.url", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.big", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.maxallowed", nil, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.maxallowed", nil, "handle", []byte{}, 100_000_000) + putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.1234", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.timeout", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.notfound", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.forbidden", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://private.url", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.big", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.maxallowed", nil, "handle", []byte{}, 100_000_000) flt := "$.Values[1]" - putOracleRequest(t, cs.Hash, bc, "https://get.filter", &flt, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.filterinv", &flt, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.filter", &flt, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.filterinv", &flt, "handle", []byte{}, 10_000_000) - putOracleRequest(t, cs.Hash, bc, "https://get.invalidcontent", nil, "handle", []byte{}, 10_000_000) + putOracleRequest(t, cInvoker, "https://get.invalidcontent", nil, "handle", []byte{}, 10_000_000) checkResp := func(t *testing.T, id uint64, resp *transaction.OracleResponse) *state.OracleRequest { - req, err := oracleCtr.GetRequestInternal(bc.dao, id) - require.NoError(t, err) + // Use a hack to get request from Oracle contract, because we can't use GetRequestInternal directly. + requestKey := make([]byte, 9) + requestKey[0] = 7 // prefixRequest from native Oracle contract + binary.BigEndian.PutUint64(requestKey[1:], id) + si := bc.GetStorageItem(nativeOracleID, requestKey) + require.NotNil(t, si) + req := new(state.OracleRequest) + require.NoError(t, stackitem.DeserializeConvertible(si, req)) reqs := map[uint64]*state.OracleRequest{id: req} orc1.ProcessRequestsInternal(reqs) @@ -198,7 +233,7 @@ func TestOracle(t *testing.T) { actualHash := cp.Hash() require.Equal(t, actualHash, cachedHash, "transaction hash was changed during ") - require.NoError(t, bc.verifyAndPoolTx(tx, bc.GetMemPool(), bc)) + require.NoError(t, bc.PoolTx(tx)) } t.Run("NormalRequest", func(t *testing.T) { @@ -306,21 +341,34 @@ func TestOracle(t *testing.T) { } func TestOracleFull(t *testing.T) { - bc := initTestChain(t, nil, nil) + bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false) + e := neotest.NewExecutor(t, bc, validator, committee) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") mp := bc.GetMemPool() orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } bc.SetOracle(orc) - cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - go bc.Run() orc.Start() - t.Cleanup(orc.Shutdown) + t.Cleanup(func() { + orc.Shutdown() + bc.Close() + }) - bc.setNodesByRole(t, true, noderoles.Oracle, keys.PublicKeys{acc.PrivateKey().PublicKey()}) - putOracleRequest(t, cs.Hash, bc, "https://get.1234", new(string), "handle", []byte{}, 10_000_000) + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.Oracle), []interface{}{acc.PrivateKey().PublicKey().Bytes()}) + + cs := contracts.GetOracleContractState(t, pathToInternalContracts, validator.ScriptHash(), 0) + e.DeployContract(t, &neotest.Contract{ + Hash: cs.Hash, + NEF: &cs.NEF, + Manifest: &cs.Manifest, + }, nil) + cInvoker := e.ValidatorInvoker(cs.Hash) + + putOracleRequest(t, cInvoker, "https://get.1234", new(string), "handle", []byte{}, 10_000_000) require.Eventually(t, func() bool { return mp.Count() == 1 }, time.Second*3, time.Millisecond*200) @@ -331,17 +379,20 @@ func TestOracleFull(t *testing.T) { } func TestNotYetRunningOracle(t *testing.T) { - bc := initTestChain(t, nil, nil) + bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, nil, false) + e := neotest.NewExecutor(t, bc, validator, committee) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + acc, orc, _, _ := getTestOracle(t, bc, "./testdata/oracle2.json", "two") mp := bc.GetMemPool() orc.OnTransaction = func(tx *transaction.Transaction) error { return mp.Add(tx, bc) } bc.SetOracle(orc) - cs := contracts.GetOracleContractState(t, pathToInternalContracts, util.Uint160{}, 42) - require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) - go bc.Run() - bc.setNodesByRole(t, true, noderoles.Oracle, keys.PublicKeys{acc.PrivateKey().PublicKey()}) + t.Cleanup(bc.Close) + + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.Oracle), []interface{}{acc.PrivateKey().PublicKey().Bytes()}) var req state.OracleRequest var reqs = make(map[uint64]*state.OracleRequest) diff --git a/pkg/core/stateroot_test.go b/pkg/core/stateroot_test.go index 0b27a92c3..3a958def5 100644 --- a/pkg/core/stateroot_test.go +++ b/pkg/core/stateroot_test.go @@ -1,6 +1,7 @@ -package core +package core_test import ( + "crypto/elliptic" "errors" "path/filepath" "sort" @@ -10,18 +11,25 @@ import ( "github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/state" + corestate "github.com/nspcc-dev/neo-go/pkg/core/stateroot" "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/interop/native/roles" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/neotest" + "github.com/nspcc-dev/neo-go/pkg/neotest/chain" "github.com/nspcc-dev/neo-go/pkg/network/payload" "github.com/nspcc-dev/neo-go/pkg/services/stateroot" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" "go.uber.org/atomic" @@ -70,64 +78,71 @@ func newMajorityMultisigWithGAS(t *testing.T, n int) (util.Uint160, keys.PublicK } func TestStateRoot(t *testing.T) { - bc := newTestChain(t) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)) h, pubs, accs := newMajorityMultisigWithGAS(t, 2) - bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) + validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()} + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.StateValidator), validatorNodes) updateIndex := bc.BlockHeight() - transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000) + + gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil) tmpDir := t.TempDir() w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass") cfg := createStateRootConfig(w.Path(), "pass") - srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, nil) + srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here. + srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, nil) require.NoError(t, err) - require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) - r, err := bc.stateRoot.GetStateRoot(bc.BlockHeight()) + require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight()) + r, err := bc.GetStateModule().GetStateRoot(bc.BlockHeight()) require.NoError(t, err) - require.Equal(t, r.Root, bc.stateRoot.CurrentLocalStateRoot()) + require.Equal(t, r.Root, bc.GetStateModule().CurrentLocalStateRoot()) t.Run("invalid message", func(t *testing.T) { require.Error(t, srv.OnPayload(&payload.Extensible{Data: []byte{42}})) - require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) + require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight()) }) t.Run("drop zero index", func(t *testing.T) { - r, err := bc.stateRoot.GetStateRoot(0) + r, err := bc.GetStateModule().GetStateRoot(0) require.NoError(t, err) data, err := testserdes.EncodeBinary(stateroot.NewMessage(stateroot.RootT, r)) require.NoError(t, err) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) - require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) + require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight()) }) t.Run("invalid height", func(t *testing.T) { - r, err := bc.stateRoot.GetStateRoot(1) + r, err := bc.GetStateModule().GetStateRoot(1) require.NoError(t, err) r.Index = 10 data := testSignStateRoot(t, r, pubs, accs...) require.Error(t, srv.OnPayload(&payload.Extensible{Data: data})) - require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) + require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight()) }) t.Run("invalid signer", func(t *testing.T) { accInv, err := wallet.NewAccount() require.NoError(t, err) pubs := keys.PublicKeys{accInv.PrivateKey().PublicKey()} require.NoError(t, accInv.ConvertMultisig(1, pubs)) - transferTokenFromMultisigAccount(t, bc, accInv.Contract.ScriptHash(), bc.contracts.GAS.Hash, 1_0000_0000) - r, err := bc.stateRoot.GetStateRoot(1) + gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), accInv.Contract.ScriptHash(), 1_0000_0000, nil) + r, err := bc.GetStateModule().GetStateRoot(1) require.NoError(t, err) data := testSignStateRoot(t, r, pubs, accInv) err = srv.OnPayload(&payload.Extensible{Data: data}) - require.True(t, errors.Is(err, ErrWitnessHashMismatch), "got: %v", err) - require.EqualValues(t, 0, bc.stateRoot.CurrentValidatedHeight()) + require.True(t, errors.Is(err, core.ErrWitnessHashMismatch), "got: %v", err) + require.EqualValues(t, 0, bc.GetStateModule().CurrentValidatedHeight()) }) - r, err = bc.stateRoot.GetStateRoot(updateIndex + 1) + r, err = bc.GetStateModule().GetStateRoot(updateIndex + 1) require.NoError(t, err) data := testSignStateRoot(t, r, pubs, accs...) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) - require.EqualValues(t, 2, bc.stateRoot.CurrentValidatedHeight()) + require.EqualValues(t, 2, bc.GetStateModule().CurrentValidatedHeight()) - r, err = bc.stateRoot.GetStateRoot(updateIndex + 1) + r, err = bc.GetStateModule().GetStateRoot(updateIndex + 1) require.NoError(t, err) require.NotEqual(t, 0, len(r.Witness)) require.Equal(t, h, r.Witness[0].ScriptHash()) @@ -145,26 +160,31 @@ func TestStateRootInitNonZeroHeight(t *testing.T) { var root util.Uint256 t.Run("init", func(t *testing.T) { // this is in a separate test to do proper cleanup - bc := newTestChainWithCustomCfgAndStore(t, st, nil) - bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) - transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000) + bc, validator, committee := chain.NewMultiWithCustomConfigAndStore(t, nil, st, true) + e := neotest.NewExecutor(t, bc, validator, committee) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)) + + validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()} + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.StateValidator), validatorNodes) + gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil) - _, err := persistBlock(bc) - require.NoError(t, err) tmpDir := t.TempDir() w := createAndWriteWallet(t, accs[0], filepath.Join(tmpDir, "w"), "pass") cfg := createStateRootConfig(w.Path(), "pass") - srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, nil) + srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here. + srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, nil) require.NoError(t, err) - r, err := bc.stateRoot.GetStateRoot(2) + r, err := bc.GetStateModule().GetStateRoot(2) require.NoError(t, err) data := testSignStateRoot(t, r, pubs, accs...) require.NoError(t, srv.OnPayload(&payload.Extensible{Data: data})) - require.EqualValues(t, 2, bc.stateRoot.CurrentValidatedHeight()) - root = bc.stateRoot.CurrentLocalStateRoot() + require.EqualValues(t, 2, bc.GetStateModule().CurrentValidatedHeight()) + root = bc.GetStateModule().CurrentLocalStateRoot() }) - bc2 := newTestChainWithCustomCfgAndStore(t, st, nil) + bc2, _, _ := chain.NewMultiWithCustomConfigAndStore(t, nil, st, true) srv := bc2.GetStateModule() require.EqualValues(t, 2, srv.CurrentValidatedHeight()) require.Equal(t, root, srv.CurrentLocalStateRoot()) @@ -192,7 +212,22 @@ func createStateRootConfig(walletPath, password string) config.StateRoot { func TestStateRootFull(t *testing.T) { tmpDir := t.TempDir() - bc := newTestChain(t) + bc, validator, committee := chain.NewMulti(t) + e := neotest.NewExecutor(t, bc, validator, committee) + designationSuperInvoker := e.NewInvoker(e.NativeHash(t, nativenames.Designation), validator, committee) + gasValidatorInvoker := e.ValidatorInvoker(e.NativeHash(t, nativenames.Gas)) + + getDesignatedByRole := func(t *testing.T, h uint32) keys.PublicKeys { + res, err := designationSuperInvoker.TestInvoke(t, "getDesignatedByRole", int64(noderoles.StateValidator), h) + require.NoError(t, err) + nodes := res.Pop().Value().([]stackitem.Item) + pubs := make(keys.PublicKeys, len(nodes)) + for i, node := range nodes { + pubs[i], err = keys.NewPublicKeyFromBytes(node.Value().([]byte), elliptic.P256()) + require.NoError(t, err) + } + return pubs + } h, pubs, accs := newMajorityMultisigWithGAS(t, 2) w := createAndWriteWallet(t, accs[1], filepath.Join(tmpDir, "wallet2"), "two") @@ -200,7 +235,8 @@ func TestStateRootFull(t *testing.T) { var lastValidated atomic.Value var lastHeight atomic.Uint32 - srv, err := stateroot.New(cfg, bc.stateRoot, zaptest.NewLogger(t), bc, func(ep *payload.Extensible) { + srMod := bc.GetStateModule().(*corestate.Module) // Take full responsibility here. + srv, err := stateroot.New(cfg, srMod, zaptest.NewLogger(t), bc, func(ep *payload.Extensible) { lastHeight.Store(ep.ValidBlockStart) lastValidated.Store(ep) }) @@ -208,16 +244,17 @@ func TestStateRootFull(t *testing.T) { srv.Start() t.Cleanup(srv.Shutdown) - bc.setNodesByRole(t, true, noderoles.StateValidator, pubs) - transferTokenFromMultisigAccount(t, bc, h, bc.contracts.GAS.Hash, 1_0000_0000) + validatorNodes := []interface{}{pubs[0].Bytes(), pubs[1].Bytes()} + designationSuperInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", + int64(roles.StateValidator), validatorNodes) + gasValidatorInvoker.Invoke(t, true, "transfer", validator.ScriptHash(), h, 1_0000_0000, nil) require.Eventually(t, func() bool { return lastHeight.Load() == 2 }, time.Second, time.Millisecond) - checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 2, 1) - _, err = persistBlock(bc) - require.NoError(t, err) + checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 2, 1, getDesignatedByRole) + e.AddNewBlock(t) require.Eventually(t, func() bool { return lastHeight.Load() == 3 }, time.Second, time.Millisecond) - checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 3, 1) + checkVoteBroadcasted(t, bc, lastValidated.Load().(*payload.Extensible), 3, 1, getDesignatedByRole) - r, err := bc.stateRoot.GetStateRoot(2) + r, err := bc.GetStateModule().GetStateRoot(2) require.NoError(t, err) require.NoError(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) require.NotNil(t, lastValidated.Load().(*payload.Extensible)) @@ -226,7 +263,7 @@ func TestStateRootFull(t *testing.T) { require.NoError(t, testserdes.DecodeBinary(lastValidated.Load().(*payload.Extensible).Data, msg)) require.NotEqual(t, stateroot.RootT, msg.Type) // not a sender for this root - r, err = bc.stateRoot.GetStateRoot(3) + r, err = bc.GetStateModule().GetStateRoot(3) require.NoError(t, err) require.Error(t, srv.AddSignature(2, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) require.NoError(t, srv.AddSignature(3, 0, accs[0].PrivateKey().SignHashable(uint32(netmode.UnitTestNet), r))) @@ -241,8 +278,8 @@ func TestStateRootFull(t *testing.T) { require.Equal(t, r.Root, actual.Root) } -func checkVoteBroadcasted(t *testing.T, bc *Blockchain, p *payload.Extensible, - height uint32, valIndex byte) { +func checkVoteBroadcasted(t *testing.T, bc *core.Blockchain, p *payload.Extensible, + height uint32, valIndex byte, getDesignatedByRole func(t *testing.T, h uint32) keys.PublicKeys) { require.NotNil(t, p) m := new(stateroot.Message) require.NoError(t, testserdes.DecodeBinary(p.Data, m)) @@ -255,8 +292,7 @@ func checkVoteBroadcasted(t *testing.T, bc *Blockchain, p *payload.Extensible, require.Equal(t, height, vote.Height) require.Equal(t, int32(valIndex), vote.ValidatorIndex) - pubs, _, err := bc.contracts.Designate.GetDesignatedByRole(bc.dao, noderoles.StateValidator, bc.BlockHeight()) - require.NoError(t, err) + pubs := getDesignatedByRole(t, bc.BlockHeight()) require.True(t, len(pubs) > int(valIndex)) require.True(t, pubs[valIndex].VerifyHashable(vote.Signature, uint32(netmode.UnitTestNet), r)) } diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index 4665f2ee1..7904914e2 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -174,12 +174,22 @@ func (e *Executor) DeployContractCheckFAULT(t testing.TB, c *Contract, data inte // InvokeScript adds transaction with the specified script to the chain and // returns its hash. It does no faults check. func (e *Executor) InvokeScript(t testing.TB, script []byte, signers []Signer) util.Uint256 { + tx := e.PrepareInvocation(t, script, signers) + e.AddNewBlock(t, tx) + return tx.Hash() +} + +// PrepareInvocation creates transaction with the specified script and signs it +// by the provided signer. +func (e *Executor) PrepareInvocation(t testing.TB, script []byte, signers []Signer, validUntilBlock ...uint32) *transaction.Transaction { tx := transaction.New(script, 0) tx.Nonce = Nonce() tx.ValidUntilBlock = e.Chain.BlockHeight() + 1 + if len(validUntilBlock) != 0 { + tx.ValidUntilBlock = validUntilBlock[0] + } e.SignTx(t, tx, -1, signers...) - e.AddNewBlock(t, tx) - return tx.Hash() + return tx } // InvokeScriptCheckHALT adds transaction with the specified script to the chain @@ -323,10 +333,12 @@ func (e *Executor) AddNewBlock(t testing.TB, txs ...*transaction.Transaction) *b } // GenerateNewBlocks adds specified number of empty blocks to the chain. -func (e *Executor) GenerateNewBlocks(t testing.TB, count int) { +func (e *Executor) GenerateNewBlocks(t testing.TB, count int) []*block.Block { + blocks := make([]*block.Block, count) for i := 0; i < count; i++ { - e.AddNewBlock(t) + blocks[i] = e.AddNewBlock(t) } + return blocks } // SignBlock add validators signature to b. diff --git a/pkg/neotest/chain/chain.go b/pkg/neotest/chain/chain.go index 453421b7c..a98b4c10b 100644 --- a/pkg/neotest/chain/chain.go +++ b/pkg/neotest/chain/chain.go @@ -128,8 +128,7 @@ func NewSingle(t testing.TB) (*core.Blockchain, neotest.Signer) { // NewSingleWithCustomConfig is similar to NewSingle, but allows to override the // default configuration. func NewSingleWithCustomConfig(t testing.TB, f func(*config.ProtocolConfiguration)) (*core.Blockchain, neotest.Signer) { - st := storage.NewMemoryStore() - return NewSingleWithCustomConfigAndStore(t, f, st, true) + return NewSingleWithCustomConfigAndStore(t, f, nil, true) } // NewSingleWithCustomConfigAndStore is similar to NewSingleWithCustomConfig, but @@ -150,6 +149,9 @@ func NewSingleWithCustomConfigAndStore(t testing.TB, f func(cfg *config.Protocol if f != nil { f(&protoCfg) } + if st == nil { + st = storage.NewMemoryStore() + } log := zaptest.NewLogger(t) bc, err := core.NewBlockchain(st, protoCfg, log) require.NoError(t, err)