contract: avoid going to the DB for entry scripts

This optimizes out DB access for non-deployed contracts under the assumption
that deployed ones are always loaded via `LoadScriptWithHash` (and if they're
not --- it's a bug anyway with the new hashing model) which actually is a very
popular case (every entry script does that).
This commit is contained in:
Roman Khimov 2020-11-26 23:02:00 +03:00
parent 49f6b33eae
commit d93aa745bb
4 changed files with 22 additions and 7 deletions

View file

@ -46,10 +46,13 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
if strings.HasPrefix(name, "_") { if strings.HasPrefix(name, "_") {
return errors.New("invalid method name (starts with '_')") return errors.New("invalid method name (starts with '_')")
} }
curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) ctx := ic.VM.Context()
if err == nil { if ctx != nil && ctx.IsDeployed() {
if !curr.Manifest.CanCall(u, &cs.Manifest, name) { curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
return errors.New("disallowed method call") if err == nil {
if !curr.Manifest.CanCall(u, &cs.Manifest, name) {
return errors.New("disallowed method call")
}
} }
} }
return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty, nil) return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty, nil)

View file

@ -581,7 +581,7 @@ func TestContractCall(t *testing.T) {
runInvalid := func(args ...interface{}) func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
loadScript(ic, currScript, 42) loadScriptWithHashAndFlags(ic, currScript, h, smartcontract.All, 42)
for i := range args { for i := range args {
ic.VM.Estack().PushVal(args[i]) ic.VM.Estack().PushVal(args[i])
} }

View file

@ -42,6 +42,9 @@ type Context struct {
// Caller's contract script hash. // Caller's contract script hash.
callingScriptHash util.Uint160 callingScriptHash util.Uint160
// Set to true when running deployed contracts.
isDeployed bool
// Call flags this context was created with. // Call flags this context was created with.
callFlag smartcontract.CallFlag callFlag smartcontract.CallFlag
@ -256,6 +259,11 @@ func (c *Context) String() string {
return "execution context" return "execution context"
} }
// IsDeployed returns whether this context contains deployed contract.
func (c *Context) IsDeployed() bool {
return c.isDeployed
}
// getContextScriptHash returns script hash of the invocation stack element // getContextScriptHash returns script hash of the invocation stack element
// number n. // number n.
func (v *VM) getContextScriptHash(n int) util.Uint160 { func (v *VM) getContextScriptHash(n int) util.Uint160 {

View file

@ -284,12 +284,16 @@ func (v *VM) LoadScriptWithFlags(b []byte, f smartcontract.CallFlag) {
} }
// LoadScriptWithHash if similar to the LoadScriptWithFlags method, but it also loads // LoadScriptWithHash if similar to the LoadScriptWithFlags method, but it also loads
// given script hash directly into the Context to avoid its recalculations. It's // given script hash directly into the Context to avoid its recalculations and to make
// up to user of this function to make sure the script and hash match each other. // is possible to override it for deployed contracts with special hashes (the function
// assumes that it is used for deployed contracts setting context's parameters
// accordingly). It's up to user of this function to make sure the script and hash match
// each other.
func (v *VM) LoadScriptWithHash(b []byte, hash util.Uint160, f smartcontract.CallFlag) { func (v *VM) LoadScriptWithHash(b []byte, hash util.Uint160, f smartcontract.CallFlag) {
shash := v.GetCurrentScriptHash() shash := v.GetCurrentScriptHash()
v.LoadScriptWithFlags(b, f) v.LoadScriptWithFlags(b, f)
ctx := v.Context() ctx := v.Context()
ctx.isDeployed = true
ctx.scriptHash = hash ctx.scriptHash = hash
ctx.callingScriptHash = shash ctx.callingScriptHash = shash
} }