diff --git a/cli/vm/cli.go b/cli/vm/cli.go index 4b4845d99..320bac2b4 100644 --- a/cli/vm/cli.go +++ b/cli/vm/cli.go @@ -333,7 +333,10 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg return nil, cli.NewExitError(fmt.Errorf("could not initialize blockchain: %w", err), 1) } // Do not run chain, we need only state-related functionality from it. - ic := chain.GetTestVM(trigger.Application, nil, nil) + ic, err := chain.GetTestVM(trigger.Application, nil, nil) + if err != nil { + return nil, cli.NewExitError(fmt.Errorf("failed to create test VM: %w", err), 1) + } vmcli := VMCLI{ chain: chain, @@ -483,7 +486,10 @@ func handleSlots(c *cli.Context) error { } func handleLoadNEF(c *cli.Context) error { - resetState(c.App) + err := resetState(c.App) + if err != nil { + return err + } v := getVMFromContext(c.App) args := c.Args() if len(args) < 2 { @@ -503,7 +509,10 @@ func handleLoadNEF(c *cli.Context) error { } func handleLoadBase64(c *cli.Context) error { - resetState(c.App) + err := resetState(c.App) + if err != nil { + return err + } v := getVMFromContext(c.App) args := c.Args() if len(args) < 1 { @@ -520,7 +529,10 @@ func handleLoadBase64(c *cli.Context) error { } func handleLoadHex(c *cli.Context) error { - resetState(c.App) + err := resetState(c.App) + if err != nil { + return err + } v := getVMFromContext(c.App) args := c.Args() if len(args) < 1 { @@ -537,7 +549,10 @@ func handleLoadHex(c *cli.Context) error { } func handleLoadGo(c *cli.Context) error { - resetState(c.App) + err := resetState(c.App) + if err != nil { + return err + } v := getVMFromContext(c.App) args := c.Args() if len(args) < 1 { @@ -564,7 +579,10 @@ func handleLoadGo(c *cli.Context) error { } func handleReset(c *cli.Context) error { - resetState(c.App) + err := resetState(c.App) + if err != nil { + return err + } changePrompt(c.App) return nil } @@ -577,11 +595,15 @@ func finalizeInteropContext(app *cli.App) { // resetInteropContext calls finalizer for current interop context and replaces // it with the newly created one. -func resetInteropContext(app *cli.App) { +func resetInteropContext(app *cli.App) error { finalizeInteropContext(app) bc := getChainFromContext(app) - newIc := bc.GetTestVM(trigger.Application, nil, nil) + newIc, err := bc.GetTestVM(trigger.Application, nil, nil) + if err != nil { + return fmt.Errorf("failed to create test VM: %w", err) + } setInteropContextInContext(app, newIc) + return nil } // resetManifest removes manifest from app context. @@ -591,9 +613,13 @@ func resetManifest(app *cli.App) { // resetState resets state of the app (clear interop context and manifest) so that it's ready // to load new program. -func resetState(app *cli.App) { - resetInteropContext(app) +func resetState(app *cli.App) error { + err := resetInteropContext(app) + if err != nil { + return fmt.Errorf("failed to reset interop context state: %w", err) + } resetManifest(app) + return nil } func getManifestFromFile(name string) (*manifest.Manifest, error) { diff --git a/internal/fakechain/fakechain.go b/internal/fakechain/fakechain.go index 3ae130061..34c1f9b6c 100644 --- a/internal/fakechain/fakechain.go +++ b/internal/fakechain/fakechain.go @@ -298,7 +298,7 @@ func (chain *FakeChain) GetStorageItem(id int32, key []byte) state.StorageItem { } // GetTestVM implements the Blockchainer interface. -func (chain *FakeChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context { +func (chain *FakeChain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) { panic("TODO") } diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index ccdfb0524..7e79f9b06 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -2241,19 +2241,28 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) { } // GetTestVM returns an interop context with VM set up for a test run. -func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context { +func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) { + if b == nil { + var err error + h := bc.BlockHeight() + 1 + b, err = bc.getFakeNextBlock(h) + if err != nil { + return nil, fmt.Errorf("failed to create fake block for height %d: %w", h, err) + } + } systemInterop := bc.newInteropContext(t, bc.dao, b, tx) _ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready. - return systemInterop + return systemInterop, nil } // GetTestHistoricVM returns an interop context with VM set up for a test run. -func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) { +func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, nextBlockHeight uint32) (*interop.Context, error) { if bc.config.KeepOnlyLatestState { return nil, errors.New("only latest state is supported") } - if b == nil { - return nil, errors.New("block is mandatory to produce test historic VM") + b, err := bc.getFakeNextBlock(nextBlockHeight) + if err != nil { + return nil, fmt.Errorf("failed to create fake block for height %d: %w", nextBlockHeight, err) } var mode = mpt.ModeAll if bc.config.RemoveUntraceableBlocks { @@ -2284,6 +2293,18 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact return systemInterop, nil } +// getFakeNextBlock returns fake block with the specified index and pre-filled Timestamp field. +func (bc *Blockchain) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) { + b := block.New(bc.config.StateRootInHeader) + b.Index = nextBlockHeight + hdr, err := bc.GetHeader(bc.GetHeaderHash(int(nextBlockHeight - 1))) + if err != nil { + return nil, err + } + b.Timestamp = hdr.Timestamp + uint64(bc.config.SecondsPerBlock*int(time.Second/time.Millisecond)) + return b, nil +} + // Various witness verification errors. var ( ErrWitnessHashMismatch = errors.New("witness hash mismatch") diff --git a/pkg/core/interop/contract/call_test.go b/pkg/core/interop/contract/call_test.go index f4159acd3..50648a4b6 100644 --- a/pkg/core/interop/contract/call_test.go +++ b/pkg/core/interop/contract/call_test.go @@ -32,7 +32,8 @@ var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal", func TestGetCallFlags(t *testing.T) { bc, _ := chain.NewSingle(t) - ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + ic, err := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + require.NoError(t, err) ic.VM.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All) require.NoError(t, contract.GetCallFlags(ic)) @@ -41,7 +42,8 @@ func TestGetCallFlags(t *testing.T) { func TestCall(t *testing.T) { bc, _ := chain.NewSingle(t) - ic := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + ic, err := bc.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + require.NoError(t, err) cs, currCs := contracts.GetTestContractState(t, pathToInternalContracts, 4, 5, random.Uint160()) // sender and IDs are not important for the test require.NoError(t, native.PutContractState(ic.DAO, cs)) diff --git a/pkg/core/interop/runtime/ext_test.go b/pkg/core/interop/runtime/ext_test.go index d5a878036..0b724379b 100644 --- a/pkg/core/interop/runtime/ext_test.go +++ b/pkg/core/interop/runtime/ext_test.go @@ -65,7 +65,8 @@ func getSharpTestGenesis(t *testing.T) *block.Block { func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) { chain, _ := chain.NewSingle(t) - ic := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + ic, err := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + require.NoError(t, err) v := ic.SpawnVM() return v, ic, chain } @@ -523,7 +524,8 @@ func TestGetRandomCompatibility(t *testing.T) { b := getSharpTestGenesis(t) tx := getSharpTestTx(util.Uint160{}) - ic := bc.GetTestVM(trigger.Application, tx, b) + ic, err := bc.GetTestVM(trigger.Application, tx, b) + require.NoError(t, err) ic.Network = 860833102 // Old mainnet magic used by C# tests. ic.VM = vm.New() @@ -550,7 +552,8 @@ func TestNotify(t *testing.T) { caller := random.Uint160() newIC := func(name string, args interface{}) *interop.Context { _, _, bc, cs := getDeployedInternal(t) - ic := bc.GetTestVM(trigger.Application, nil, nil) + ic, err := bc.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) ic.VM.LoadNEFMethod(&cs.NEF, caller, cs.Hash, callflag.NoneFlag, true, 0, -1, nil) ic.VM.Estack().PushVal(args) ic.VM.Estack().PushVal(name) diff --git a/pkg/core/interop/storage/storage_test.go b/pkg/core/interop/storage/storage_test.go index 7fa0f6f04..fec1820b2 100644 --- a/pkg/core/interop/storage/storage_test.go +++ b/pkg/core/interop/storage/storage_test.go @@ -300,7 +300,8 @@ func TestFind(t *testing.T) { func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) { chain, _ := chain.NewSingle(t) - ic := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + ic, err := chain.GetTestVM(trigger.Application, &transaction.Transaction{}, &block.Block{}) + require.NoError(t, err) v := ic.SpawnVM() return v, ic, chain } diff --git a/pkg/core/native/invocation_test.go b/pkg/core/native/invocation_test.go index b6f55d9dc..519dd30da 100644 --- a/pkg/core/native/invocation_test.go +++ b/pkg/core/native/invocation_test.go @@ -74,7 +74,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) { require.NotNil(t, md) t.Run("fail, bad current script hash", func(t *testing.T) { - ic := bc.GetTestVM(trigger.Application, nil, nil) + ic, err := bc.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) v := ic.SpawnVM() fakeH := util.Uint160{1, 2, 3} v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All) @@ -83,7 +84,7 @@ func TestNativeContract_InvokeInternal(t *testing.T) { v.Context().Jump(md.Offset) // Bad current script hash - err := v.Run() + 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()) }) @@ -104,7 +105,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) { }) eBad := neotest.NewExecutor(t, bcBad, validatorBad, committeeBad) - ic := bcBad.GetTestVM(trigger.Application, nil, nil) + ic, err := bcBad.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) v := ic.SpawnVM() v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history input := []byte{1, 2, 3, 4} @@ -112,13 +114,14 @@ func TestNativeContract_InvokeInternal(t *testing.T) { v.Context().Jump(md.Offset) // It's prohibited to call natives before NativeUpdateHistory[0] height. - err := v.Run() + err = v.Run() require.Error(t, err) require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1")) // Add new block => CryptoLib should be active now. eBad.AddNewBlock(t) - ic = bcBad.GetTestVM(trigger.Application, nil, nil) + ic, err = bcBad.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) v = ic.SpawnVM() v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history v.Estack().PushVal(input) @@ -130,7 +133,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) { }) t.Run("success", func(t *testing.T) { - ic := bc.GetTestVM(trigger.Application, nil, nil) + ic, err := bc.GetTestVM(trigger.Application, nil, nil) + require.NoError(t, err) v := ic.SpawnVM() v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) input := []byte{1, 2, 3, 4} diff --git a/pkg/neotest/basic.go b/pkg/neotest/basic.go index a115aa7a4..3ee1904ab 100644 --- a/pkg/neotest/basic.go +++ b/pkg/neotest/basic.go @@ -374,7 +374,8 @@ func TestInvoke(bc *core.Blockchain, tx *transaction.Transaction) (*vm.VM, error // `GetTestVM` as well as `Run` can use a transaction hash which will set a cached value. // This is unwanted behavior, so we explicitly copy the transaction to perform execution. ttx := *tx - ic := bc.GetTestVM(trigger.Application, &ttx, b) + ic, _ := bc.GetTestVM(trigger.Application, &ttx, b) + defer ic.Finalize() ic.VM.LoadWithFlags(tx.Script, callflag.All) diff --git a/pkg/neotest/client.go b/pkg/neotest/client.go index b9d676f2b..25ac8c158 100644 --- a/pkg/neotest/client.go +++ b/pkg/neotest/client.go @@ -51,11 +51,14 @@ func (e *Executor) ValidatorInvoker(h util.Uint160) *ContractInvoker { 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) + ic, err := c.Chain.GetTestVM(trigger.Application, tx, b) + if err != nil { + return nil, err + } t.Cleanup(ic.Finalize) ic.VM.LoadWithFlags(tx.Script, callflag.All) - err := ic.VM.Run() + err = ic.VM.Run() return ic.VM.Estack(), err } diff --git a/pkg/services/oracle/oracle.go b/pkg/services/oracle/oracle.go index 6dbb8f692..78322a4ba 100644 --- a/pkg/services/oracle/oracle.go +++ b/pkg/services/oracle/oracle.go @@ -29,7 +29,7 @@ type ( GetBaseExecFee() int64 GetConfig() config.ProtocolConfiguration GetMaxVerificationGAS() int64 - GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context + GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) } diff --git a/pkg/services/oracle/response.go b/pkg/services/oracle/response.go index d8523b2b2..f2d3667c0 100644 --- a/pkg/services/oracle/response.go +++ b/pkg/services/oracle/response.go @@ -3,6 +3,7 @@ package oracle import ( "encoding/hex" "errors" + "fmt" gio "io" "github.com/nspcc-dev/neo-go/pkg/core/fee" @@ -107,7 +108,10 @@ func (o *Oracle) CreateResponseTx(gasForResponse int64, vub uint32, resp *transa size := io.GetVarSize(tx) tx.Scripts = append(tx.Scripts, transaction.Witness{VerificationScript: oracleSignContract}) - gasConsumed, ok := o.testVerify(tx) + gasConsumed, ok, err := o.testVerify(tx) + if err != nil { + return nil, fmt.Errorf("failed to prepare `verify` invocation: %w", err) + } if !ok { return nil, errors.New("can't verify transaction") } @@ -131,18 +135,21 @@ func (o *Oracle) CreateResponseTx(gasForResponse int64, vub uint32, resp *transa return tx, nil } -func (o *Oracle) testVerify(tx *transaction.Transaction) (int64, bool) { +func (o *Oracle) testVerify(tx *transaction.Transaction) (int64, bool, error) { // (*Blockchain).GetTestVM calls Hash() method of the provided transaction; once being called, this // method caches transaction hash, but tx building is not yet completed and hash will be changed. // So, make a copy of the tx to avoid wrong hash caching. cp := *tx - ic := o.Chain.GetTestVM(trigger.Verification, &cp, nil) + ic, err := o.Chain.GetTestVM(trigger.Verification, &cp, nil) + if err != nil { + return 0, false, fmt.Errorf("failed to create test VM: %w", err) + } ic.VM.GasLimit = o.Chain.GetMaxVerificationGAS() ic.VM.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly) ic.VM.Context().Jump(o.verifyOffset) ok := isVerifyOk(ic) - return ic.VM.GasConsumed(), ok + return ic.VM.GasConsumed(), ok, nil } func isVerifyOk(ic *interop.Context) bool { diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index aaf4b31f1..6ddb6885e 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -1181,7 +1181,8 @@ func TestCreateNEP17TransferTx(t *testing.T) { require.NoError(t, err) require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, chain.VerifyTx(tx)) - ic := chain.GetTestVM(trigger.Application, tx, nil) + ic, err := chain.GetTestVM(trigger.Application, tx, nil) + require.NoError(t, err) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All) require.NoError(t, ic.VM.Run()) }) @@ -1195,7 +1196,8 @@ func TestCreateNEP17TransferTx(t *testing.T) { }) require.NoError(t, err) require.NoError(t, chain.VerifyTx(tx)) - ic := chain.GetTestVM(trigger.Application, tx, nil) + ic, err := chain.GetTestVM(trigger.Application, tx, nil) + require.NoError(t, err) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All) require.NoError(t, ic.VM.Run()) require.Equal(t, 2, len(ic.Notifications)) @@ -1228,7 +1230,8 @@ func TestCreateNEP17TransferTx(t *testing.T) { require.NoError(t, err) require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, chain.VerifyTx(tx)) - ic := chain.GetTestVM(trigger.Application, tx, nil) + ic, err := chain.GetTestVM(trigger.Application, tx, nil) + require.NoError(t, err) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All) require.NoError(t, ic.VM.Run()) }) diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 15d24db4a..f3c0601b8 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -89,8 +89,8 @@ type ( GetNotaryServiceFeePerKey() int64 GetStateModule() core.StateRoot GetStorageItem(id int32, key []byte) state.StorageItem - GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) - GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) *interop.Context + GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, nextBlockHeight uint32) (*interop.Context, error) + GetTestVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetValidators() ([]*keys.PublicKey, error) @@ -1065,11 +1065,10 @@ func (s *Server) invokeReadOnlyMulti(bw *io.BufBinWriter, h util.Uint160, method } script := bw.Bytes() tx := &transaction.Transaction{Script: script} - b, err := s.getFakeNextBlock(s.chain.BlockHeight() + 1) + ic, err := s.chain.GetTestVM(trigger.Application, tx, nil) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("faile to prepare test VM: %w", err) } - ic := s.chain.GetTestVM(trigger.Application, tx, b) ic.VM.GasLimit = core.HeaderVerificationGasLimit ic.VM.LoadScriptWithFlags(script, callflag.All) err = ic.VM.Run() @@ -1832,7 +1831,7 @@ func (s *Server) invokeFunction(reqParams params.Params) (interface{}, *neorpc.E // invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call. func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, *neorpc.Error) { - b, respErr := s.getHistoricParams(reqParams) + nextH, respErr := s.getHistoricParams(reqParams) if respErr != nil { return nil, respErr } @@ -1843,7 +1842,7 @@ func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, * if respErr != nil { return nil, respErr } - return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose) + return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, &nextH, verbose) } func (s *Server) getInvokeFunctionParams(reqParams params.Params) (*transaction.Transaction, bool, *neorpc.Error) { @@ -1899,7 +1898,7 @@ func (s *Server) invokescript(reqParams params.Params) (interface{}, *neorpc.Err // invokescripthistoric implements the `invokescripthistoric` RPC call. func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *neorpc.Error) { - b, respErr := s.getHistoricParams(reqParams) + nextH, respErr := s.getHistoricParams(reqParams) if respErr != nil { return nil, respErr } @@ -1910,7 +1909,7 @@ func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *ne if respErr != nil { return nil, respErr } - return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose) + return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, &nextH, verbose) } func (s *Server) getInvokeScriptParams(reqParams params.Params) (*transaction.Transaction, bool, *neorpc.Error) { @@ -1953,7 +1952,7 @@ func (s *Server) invokeContractVerify(reqParams params.Params) (interface{}, *ne // invokeContractVerifyHistoric implements the `invokecontractverifyhistoric` RPC call. func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interface{}, *neorpc.Error) { - b, respErr := s.getHistoricParams(reqParams) + nextH, respErr := s.getHistoricParams(reqParams) if respErr != nil { return nil, respErr } @@ -1964,7 +1963,7 @@ func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interfac if respErr != nil { return nil, respErr } - return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, b, false) + return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, &nextH, false) } func (s *Server) getInvokeContractVerifyParams(reqParams params.Params) (util.Uint160, *transaction.Transaction, []byte, *neorpc.Error) { @@ -2007,64 +2006,45 @@ func (s *Server) getInvokeContractVerifyParams(reqParams params.Params) (util.Ui // with the specified index to perform the historic call. It also checks that // specified stateroot is stored at the specified height for further request // handling consistency. -func (s *Server) getHistoricParams(reqParams params.Params) (*block.Block, *neorpc.Error) { +func (s *Server) getHistoricParams(reqParams params.Params) (uint32, *neorpc.Error) { if s.chain.GetConfig().KeepOnlyLatestState { - return nil, neorpc.NewInvalidRequestError(fmt.Sprintf("only latest state is supported: %s", errKeepOnlyLatestState)) + return 0, neorpc.NewInvalidRequestError(fmt.Sprintf("only latest state is supported: %s", errKeepOnlyLatestState)) } if len(reqParams) < 1 { - return nil, neorpc.ErrInvalidParams + return 0, neorpc.ErrInvalidParams } height, respErr := s.blockHeightFromParam(reqParams.Value(0)) if respErr != nil { hash, err := reqParams.Value(0).GetUint256() if err != nil { - return nil, neorpc.NewInvalidParamsError(fmt.Sprintf("invalid block hash or index or stateroot hash: %s", err)) + return 0, neorpc.NewInvalidParamsError(fmt.Sprintf("invalid block hash or index or stateroot hash: %s", err)) } b, err := s.chain.GetBlock(hash) if err != nil { stateH, err := s.chain.GetStateModule().GetLatestStateHeight(hash) if err != nil { - return nil, neorpc.NewInvalidParamsError(fmt.Sprintf("unknown block or stateroot: %s", err)) + return 0, neorpc.NewInvalidParamsError(fmt.Sprintf("unknown block or stateroot: %s", err)) } height = int(stateH) } else { height = int(b.Index) } } - b, err := s.getFakeNextBlock(uint32(height + 1)) - if err != nil { - return nil, neorpc.NewInternalServerError(fmt.Sprintf("can't create fake block for height %d: %s", height+1, err)) - } - return b, nil + return uint32(height) + 1, nil } -func (s *Server) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) { - // When transferring funds, script execution does no auto GAS claim, - // because it depends on persisting tx height. - // This is why we provide block here. - b := block.New(s.stateRootEnabled) - b.Index = nextBlockHeight - hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(nextBlockHeight - 1))) - if err != nil { - return nil, err - } - b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond)) - return b, nil -} - -func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*interop.Context, *neorpc.Error) { +func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, nextH *uint32, verbose bool) (*interop.Context, *neorpc.Error) { var ( err error ic *interop.Context ) - if b == nil { - b, err = s.getFakeNextBlock(s.chain.BlockHeight() + 1) + if nextH == nil { + ic, err = s.chain.GetTestVM(t, tx, nil) if err != nil { - return nil, neorpc.NewInternalServerError(fmt.Sprintf("can't create fake block: %s", err)) + return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to create test VM: %s", err)) } - ic = s.chain.GetTestVM(t, tx, b) } else { - ic, err = s.chain.GetTestHistoricVM(t, tx, b) + ic, err = s.chain.GetTestHistoricVM(t, tx, *nextH) if err != nil { return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to create historic VM: %s", err)) } @@ -2096,8 +2076,8 @@ func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contrac // witness invocation script in case of `verification` trigger (it pushes `verify` // arguments on stack before verification). In case of contract verification // contractScriptHash should be specified. -func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, b *block.Block, verbose bool) (*result.Invoke, *neorpc.Error) { - ic, respErr := s.prepareInvocationContext(t, script, contractScriptHash, tx, b, verbose) +func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, nextH *uint32, verbose bool) (*result.Invoke, *neorpc.Error) { + ic, respErr := s.prepareInvocationContext(t, script, contractScriptHash, tx, nextH, verbose) if respErr != nil { return nil, respErr } @@ -2111,16 +2091,12 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash var id uuid.UUID if sess != nil { - // b == nil only when we're not using MPT-backed storage, therefore + // nextH == nil only when we're not using MPT-backed storage, therefore // the second attempt won't stop here. - if s.config.SessionBackedByMPT && b == nil { + if s.config.SessionBackedByMPT && nextH == nil { ic.Finalize() - b, err = s.getFakeNextBlock(ic.Block.Index) - if err != nil { - return nil, neorpc.NewInternalServerError(fmt.Sprintf("unable to prepare block for historic call: %s", err)) - } // Rerun with MPT-backed storage. - return s.runScriptInVM(t, script, contractScriptHash, tx, b, verbose) + return s.runScriptInVM(t, script, contractScriptHash, tx, &ic.Block.Index, verbose) } id = uuid.New() sessionID := id.String()