core, rpc: move getFakeNextBlock to Blockchain

It's needed for VM CLI as far and may be improved later.
This commit is contained in:
Anna Shaleva 2022-10-06 13:24:57 +03:00
parent 0036c89d63
commit 79e13f73d8
13 changed files with 136 additions and 89 deletions

View file

@ -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) 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. // 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{ vmcli := VMCLI{
chain: chain, chain: chain,
@ -483,7 +486,10 @@ func handleSlots(c *cli.Context) error {
} }
func handleLoadNEF(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) v := getVMFromContext(c.App)
args := c.Args() args := c.Args()
if len(args) < 2 { if len(args) < 2 {
@ -503,7 +509,10 @@ func handleLoadNEF(c *cli.Context) error {
} }
func handleLoadBase64(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) v := getVMFromContext(c.App)
args := c.Args() args := c.Args()
if len(args) < 1 { if len(args) < 1 {
@ -520,7 +529,10 @@ func handleLoadBase64(c *cli.Context) error {
} }
func handleLoadHex(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) v := getVMFromContext(c.App)
args := c.Args() args := c.Args()
if len(args) < 1 { if len(args) < 1 {
@ -537,7 +549,10 @@ func handleLoadHex(c *cli.Context) error {
} }
func handleLoadGo(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) v := getVMFromContext(c.App)
args := c.Args() args := c.Args()
if len(args) < 1 { if len(args) < 1 {
@ -564,7 +579,10 @@ func handleLoadGo(c *cli.Context) error {
} }
func handleReset(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) changePrompt(c.App)
return nil return nil
} }
@ -577,11 +595,15 @@ func finalizeInteropContext(app *cli.App) {
// resetInteropContext calls finalizer for current interop context and replaces // resetInteropContext calls finalizer for current interop context and replaces
// it with the newly created one. // it with the newly created one.
func resetInteropContext(app *cli.App) { func resetInteropContext(app *cli.App) error {
finalizeInteropContext(app) finalizeInteropContext(app)
bc := getChainFromContext(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) setInteropContextInContext(app, newIc)
return nil
} }
// resetManifest removes manifest from app context. // 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 // resetState resets state of the app (clear interop context and manifest) so that it's ready
// to load new program. // to load new program.
func resetState(app *cli.App) { func resetState(app *cli.App) error {
resetInteropContext(app) err := resetInteropContext(app)
if err != nil {
return fmt.Errorf("failed to reset interop context state: %w", err)
}
resetManifest(app) resetManifest(app)
return nil
} }
func getManifestFromFile(name string) (*manifest.Manifest, error) { func getManifestFromFile(name string) (*manifest.Manifest, error) {

View file

@ -298,7 +298,7 @@ func (chain *FakeChain) GetStorageItem(id int32, key []byte) state.StorageItem {
} }
// GetTestVM implements the Blockchainer interface. // 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") panic("TODO")
} }

View file

@ -2241,19 +2241,28 @@ func (bc *Blockchain) GetEnrollments() ([]state.Validator, error) {
} }
// GetTestVM returns an interop context with VM set up for a test run. // 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 := bc.newInteropContext(t, bc.dao, b, tx)
_ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready. _ = 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. // 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 { if bc.config.KeepOnlyLatestState {
return nil, errors.New("only latest state is supported") return nil, errors.New("only latest state is supported")
} }
if b == nil { b, err := bc.getFakeNextBlock(nextBlockHeight)
return nil, errors.New("block is mandatory to produce test historic VM") if err != nil {
return nil, fmt.Errorf("failed to create fake block for height %d: %w", nextBlockHeight, err)
} }
var mode = mpt.ModeAll var mode = mpt.ModeAll
if bc.config.RemoveUntraceableBlocks { if bc.config.RemoveUntraceableBlocks {
@ -2284,6 +2293,18 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact
return systemInterop, nil 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. // Various witness verification errors.
var ( var (
ErrWitnessHashMismatch = errors.New("witness hash mismatch") ErrWitnessHashMismatch = errors.New("witness hash mismatch")

View file

@ -32,7 +32,8 @@ var pathToInternalContracts = filepath.Join("..", "..", "..", "..", "internal",
func TestGetCallFlags(t *testing.T) { func TestGetCallFlags(t *testing.T) {
bc, _ := chain.NewSingle(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) ic.VM.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{1, 2, 3}, callflag.All)
require.NoError(t, contract.GetCallFlags(ic)) require.NoError(t, contract.GetCallFlags(ic))
@ -41,7 +42,8 @@ func TestGetCallFlags(t *testing.T) {
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
bc, _ := chain.NewSingle(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 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)) require.NoError(t, native.PutContractState(ic.DAO, cs))

View file

@ -65,7 +65,8 @@ func getSharpTestGenesis(t *testing.T) *block.Block {
func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) { func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
chain, _ := chain.NewSingle(t) 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() v := ic.SpawnVM()
return v, ic, chain return v, ic, chain
} }
@ -523,7 +524,8 @@ func TestGetRandomCompatibility(t *testing.T) {
b := getSharpTestGenesis(t) b := getSharpTestGenesis(t)
tx := getSharpTestTx(util.Uint160{}) 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.Network = 860833102 // Old mainnet magic used by C# tests.
ic.VM = vm.New() ic.VM = vm.New()
@ -550,7 +552,8 @@ func TestNotify(t *testing.T) {
caller := random.Uint160() caller := random.Uint160()
newIC := func(name string, args interface{}) *interop.Context { newIC := func(name string, args interface{}) *interop.Context {
_, _, bc, cs := getDeployedInternal(t) _, _, 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.LoadNEFMethod(&cs.NEF, caller, cs.Hash, callflag.NoneFlag, true, 0, -1, nil)
ic.VM.Estack().PushVal(args) ic.VM.Estack().PushVal(args)
ic.VM.Estack().PushVal(name) ic.VM.Estack().PushVal(name)

View file

@ -300,7 +300,8 @@ func TestFind(t *testing.T) {
func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) { func createVM(t testing.TB) (*vm.VM, *interop.Context, *core.Blockchain) {
chain, _ := chain.NewSingle(t) 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() v := ic.SpawnVM()
return v, ic, chain return v, ic, chain
} }

View file

@ -74,7 +74,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
require.NotNil(t, md) require.NotNil(t, md)
t.Run("fail, bad current script hash", func(t *testing.T) { 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() v := ic.SpawnVM()
fakeH := util.Uint160{1, 2, 3} fakeH := util.Uint160{1, 2, 3}
v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All) v.LoadScriptWithHash(clState.NEF.Script, fakeH, callflag.All)
@ -83,7 +84,7 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
v.Context().Jump(md.Offset) v.Context().Jump(md.Offset)
// Bad current script hash // Bad current script hash
err := v.Run() err = v.Run()
require.Error(t, err) require.Error(t, err)
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native contract %s (version 0) not found", fakeH.StringLE())), err.Error()) 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) 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 := ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
input := []byte{1, 2, 3, 4} input := []byte{1, 2, 3, 4}
@ -112,13 +114,14 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
v.Context().Jump(md.Offset) v.Context().Jump(md.Offset)
// It's prohibited to call natives before NativeUpdateHistory[0] height. // It's prohibited to call natives before NativeUpdateHistory[0] height.
err := v.Run() err = v.Run()
require.Error(t, err) require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1")) require.True(t, strings.Contains(err.Error(), "native contract CryptoLib is active after height = 1"))
// Add new block => CryptoLib should be active now. // Add new block => CryptoLib should be active now.
eBad.AddNewBlock(t) 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 = ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) // hash is not affected by native update history
v.Estack().PushVal(input) v.Estack().PushVal(input)
@ -130,7 +133,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) {
}) })
t.Run("success", func(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 := ic.SpawnVM()
v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All) v.LoadScriptWithHash(clState.NEF.Script, clState.Hash, callflag.All)
input := []byte{1, 2, 3, 4} input := []byte{1, 2, 3, 4}

View file

@ -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. // `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. // This is unwanted behavior, so we explicitly copy the transaction to perform execution.
ttx := *tx ttx := *tx
ic := bc.GetTestVM(trigger.Application, &ttx, b) ic, _ := bc.GetTestVM(trigger.Application, &ttx, b)
defer ic.Finalize() defer ic.Finalize()
ic.VM.LoadWithFlags(tx.Script, callflag.All) ic.VM.LoadWithFlags(tx.Script, callflag.All)

View file

@ -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) { func (c *ContractInvoker) TestInvoke(t testing.TB, method string, args ...interface{}) (*vm.Stack, error) {
tx := c.PrepareInvokeNoSign(t, method, args...) tx := c.PrepareInvokeNoSign(t, method, args...)
b := c.NewUnsignedBlock(t, tx) 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) t.Cleanup(ic.Finalize)
ic.VM.LoadWithFlags(tx.Script, callflag.All) ic.VM.LoadWithFlags(tx.Script, callflag.All)
err := ic.VM.Run() err = ic.VM.Run()
return ic.VM.Estack(), err return ic.VM.Estack(), err
} }

View file

@ -29,7 +29,7 @@ type (
GetBaseExecFee() int64 GetBaseExecFee() int64
GetConfig() config.ProtocolConfiguration GetConfig() config.ProtocolConfiguration
GetMaxVerificationGAS() int64 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) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
} }

View file

@ -3,6 +3,7 @@ package oracle
import ( import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
gio "io" gio "io"
"github.com/nspcc-dev/neo-go/pkg/core/fee" "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) size := io.GetVarSize(tx)
tx.Scripts = append(tx.Scripts, transaction.Witness{VerificationScript: oracleSignContract}) 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 { if !ok {
return nil, errors.New("can't verify transaction") 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 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 // (*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. // 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. // So, make a copy of the tx to avoid wrong hash caching.
cp := *tx 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.GasLimit = o.Chain.GetMaxVerificationGAS()
ic.VM.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly) ic.VM.LoadScriptWithHash(o.oracleScript, o.oracleHash, callflag.ReadOnly)
ic.VM.Context().Jump(o.verifyOffset) ic.VM.Context().Jump(o.verifyOffset)
ok := isVerifyOk(ic) ok := isVerifyOk(ic)
return ic.VM.GasConsumed(), ok return ic.VM.GasConsumed(), ok, nil
} }
func isVerifyOk(ic *interop.Context) bool { func isVerifyOk(ic *interop.Context) bool {

View file

@ -1181,7 +1181,8 @@ func TestCreateNEP17TransferTx(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, acc.SignTx(testchain.Network(), tx))
require.NoError(t, chain.VerifyTx(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) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
}) })
@ -1195,7 +1196,8 @@ func TestCreateNEP17TransferTx(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, chain.VerifyTx(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) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
require.Equal(t, 2, len(ic.Notifications)) require.Equal(t, 2, len(ic.Notifications))
@ -1228,7 +1230,8 @@ func TestCreateNEP17TransferTx(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, acc.SignTx(testchain.Network(), tx)) require.NoError(t, acc.SignTx(testchain.Network(), tx))
require.NoError(t, chain.VerifyTx(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) ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
require.NoError(t, ic.VM.Run()) require.NoError(t, ic.VM.Run())
}) })

View file

@ -89,8 +89,8 @@ type (
GetNotaryServiceFeePerKey() int64 GetNotaryServiceFeePerKey() int64
GetStateModule() core.StateRoot GetStateModule() core.StateRoot
GetStorageItem(id int32, key []byte) state.StorageItem GetStorageItem(id int32, key []byte) state.StorageItem
GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, b *block.Block) (*interop.Context, error) GetTestHistoricVM(t trigger.Type, tx *transaction.Transaction, nextBlockHeight uint32) (*interop.Context, error)
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)
GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error) GetTokenLastUpdated(acc util.Uint160) (map[int32]uint32, error)
GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error)
GetValidators() ([]*keys.PublicKey, error) GetValidators() ([]*keys.PublicKey, error)
@ -1065,11 +1065,10 @@ func (s *Server) invokeReadOnlyMulti(bw *io.BufBinWriter, h util.Uint160, method
} }
script := bw.Bytes() script := bw.Bytes()
tx := &transaction.Transaction{Script: script} 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 { 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.GasLimit = core.HeaderVerificationGasLimit
ic.VM.LoadScriptWithFlags(script, callflag.All) ic.VM.LoadScriptWithFlags(script, callflag.All)
err = ic.VM.Run() err = ic.VM.Run()
@ -1832,7 +1831,7 @@ func (s *Server) invokeFunction(reqParams params.Params) (interface{}, *neorpc.E
// invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call. // invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call.
func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, *neorpc.Error) { func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, *neorpc.Error) {
b, respErr := s.getHistoricParams(reqParams) nextH, respErr := s.getHistoricParams(reqParams)
if respErr != nil { if respErr != nil {
return nil, respErr return nil, respErr
} }
@ -1843,7 +1842,7 @@ func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, *
if respErr != nil { if respErr != nil {
return nil, respErr 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) { 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. // invokescripthistoric implements the `invokescripthistoric` RPC call.
func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *neorpc.Error) { func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *neorpc.Error) {
b, respErr := s.getHistoricParams(reqParams) nextH, respErr := s.getHistoricParams(reqParams)
if respErr != nil { if respErr != nil {
return nil, respErr return nil, respErr
} }
@ -1910,7 +1909,7 @@ func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *ne
if respErr != nil { if respErr != nil {
return nil, respErr 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) { 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. // invokeContractVerifyHistoric implements the `invokecontractverifyhistoric` RPC call.
func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interface{}, *neorpc.Error) { func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interface{}, *neorpc.Error) {
b, respErr := s.getHistoricParams(reqParams) nextH, respErr := s.getHistoricParams(reqParams)
if respErr != nil { if respErr != nil {
return nil, respErr return nil, respErr
} }
@ -1964,7 +1963,7 @@ func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interfac
if respErr != nil { if respErr != nil {
return nil, respErr 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) { 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 // with the specified index to perform the historic call. It also checks that
// specified stateroot is stored at the specified height for further request // specified stateroot is stored at the specified height for further request
// handling consistency. // 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 { 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 { if len(reqParams) < 1 {
return nil, neorpc.ErrInvalidParams return 0, neorpc.ErrInvalidParams
} }
height, respErr := s.blockHeightFromParam(reqParams.Value(0)) height, respErr := s.blockHeightFromParam(reqParams.Value(0))
if respErr != nil { if respErr != nil {
hash, err := reqParams.Value(0).GetUint256() hash, err := reqParams.Value(0).GetUint256()
if err != nil { 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) b, err := s.chain.GetBlock(hash)
if err != nil { if err != nil {
stateH, err := s.chain.GetStateModule().GetLatestStateHeight(hash) stateH, err := s.chain.GetStateModule().GetLatestStateHeight(hash)
if err != nil { 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) height = int(stateH)
} else { } else {
height = int(b.Index) height = int(b.Index)
} }
} }
b, err := s.getFakeNextBlock(uint32(height + 1)) return uint32(height) + 1, nil
if err != nil {
return nil, neorpc.NewInternalServerError(fmt.Sprintf("can't create fake block for height %d: %s", height+1, err))
}
return b, nil
} }
func (s *Server) getFakeNextBlock(nextBlockHeight uint32) (*block.Block, error) { func (s *Server) prepareInvocationContext(t trigger.Type, script []byte, contractScriptHash util.Uint160, tx *transaction.Transaction, nextH *uint32, verbose bool) (*interop.Context, *neorpc.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) {
var ( var (
err error err error
ic *interop.Context ic *interop.Context
) )
if b == nil { if nextH == nil {
b, err = s.getFakeNextBlock(s.chain.BlockHeight() + 1) ic, err = s.chain.GetTestVM(t, tx, nil)
if err != 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 { } else {
ic, err = s.chain.GetTestHistoricVM(t, tx, b) ic, err = s.chain.GetTestHistoricVM(t, tx, *nextH)
if err != nil { if err != nil {
return nil, neorpc.NewInternalServerError(fmt.Sprintf("failed to create historic VM: %s", err)) 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` // witness invocation script in case of `verification` trigger (it pushes `verify`
// arguments on stack before verification). In case of contract verification // arguments on stack before verification). In case of contract verification
// contractScriptHash should be specified. // 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) { 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, b, verbose) ic, respErr := s.prepareInvocationContext(t, script, contractScriptHash, tx, nextH, verbose)
if respErr != nil { if respErr != nil {
return nil, respErr return nil, respErr
} }
@ -2111,16 +2091,12 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
var id uuid.UUID var id uuid.UUID
if sess != nil { 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. // the second attempt won't stop here.
if s.config.SessionBackedByMPT && b == nil { if s.config.SessionBackedByMPT && nextH == nil {
ic.Finalize() 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. // 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() id = uuid.New()
sessionID := id.String() sessionID := id.String()