From 1e5825c4af8b6b441e2b8aa43173e116f72e8ff8 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 3 Jun 2022 23:39:46 +0300 Subject: [PATCH 1/6] core: don't allocate another int for notification handler It'll be serialized anyway. --- pkg/core/blockchain.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 1f881156b..c50e29584 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1365,6 +1365,7 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti if !isNEP11 { nep17xfer = &state.NEP17Transfer{ Asset: id, + Amount: *amount, From: from, To: to, Block: b.Index, @@ -1376,6 +1377,7 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti nep11xfer := &state.NEP11Transfer{ NEP17Transfer: state.NEP17Transfer{ Asset: id, + Amount: *amount, From: from, To: to, Block: b.Index, @@ -1388,13 +1390,14 @@ func (bc *Blockchain) processTokenTransfer(cache *dao.Simple, transCache map[uti nep17xfer = &nep11xfer.NEP17Transfer } if !from.Equals(util.Uint160{}) { - _ = nep17xfer.Amount.Neg(amount) // We already have the Int. - if appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, b.Timestamp, isNEP11) != nil { + _ = nep17xfer.Amount.Neg(&nep17xfer.Amount) + err := appendTokenTransfer(cache, transCache, from, transfer, id, b.Index, b.Timestamp, isNEP11) + _ = nep17xfer.Amount.Neg(&nep17xfer.Amount) + if err != nil { return } } if !to.Equals(util.Uint160{}) { - _ = nep17xfer.Amount.Set(amount) // We already have the Int. _ = appendTokenTransfer(cache, transCache, to, transfer, id, b.Index, b.Timestamp, isNEP11) // Nothing useful we can do. } } From 799394192b9a9bf7e93f4511d1423b57e30929b1 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 3 Jun 2022 23:49:51 +0300 Subject: [PATCH 2/6] state: create buffer/io writer once per TokenTransferLog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta TokenTransferLog_Append-8 93.0µs ±170% 46.8µs ±152% ~ (p=0.053 n=10+9) name old alloc/op new alloc/op delta TokenTransferLog_Append-8 53.8kB ± 4% 38.6kB ±39% -28.26% (p=0.004 n=8+10) name old allocs/op new allocs/op delta TokenTransferLog_Append-8 384 ± 0% 128 ± 0% -66.67% (p=0.000 n=10+10) --- pkg/core/blockchain.go | 2 +- pkg/core/state/tokens.go | 28 +++++++++++++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index c50e29584..504bebd1e 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1454,7 +1454,7 @@ func appendTokenTransfer(cache *dao.Simple, transCache map[util.Uint160]transfer *nextBatch++ *currTimestamp = bTimestamp // Put makes a copy of it anyway. - log.Raw = log.Raw[:0] + log.Reset() } transCache[addr] = transferData return nil diff --git a/pkg/core/state/tokens.go b/pkg/core/state/tokens.go index c81d4e32d..81739ccdf 100644 --- a/pkg/core/state/tokens.go +++ b/pkg/core/state/tokens.go @@ -16,6 +16,8 @@ const TokenTransferBatchSize = 128 // TokenTransferLog is a serialized log of token transfers. type TokenTransferLog struct { Raw []byte + buf *bytes.Buffer + iow *io.BinWriter } // NEP17Transfer represents a single NEP-17 Transfer event. @@ -111,18 +113,30 @@ func (lg *TokenTransferLog) Append(tr io.Serializable) error { lg.Raw = append(lg.Raw, 0) } - b := bytes.NewBuffer(lg.Raw) - w := io.NewBinWriterFromIO(b) - - tr.EncodeBinary(w) - if w.Err != nil { - return w.Err + if lg.buf == nil { + lg.buf = bytes.NewBuffer(lg.Raw) } - lg.Raw = b.Bytes() + if lg.iow == nil { + lg.iow = io.NewBinWriterFromIO(lg.buf) + } + + tr.EncodeBinary(lg.iow) + if lg.iow.Err != nil { + return lg.iow.Err + } + lg.Raw = lg.buf.Bytes() lg.Raw[0]++ return nil } +// Reset resets the state of the log, clearing all entries, but keeping existing +// buffer for future writes. +func (lg *TokenTransferLog) Reset() { + lg.Raw = lg.Raw[:0] + lg.buf = nil + lg.iow = nil +} + // ForEachNEP11 iterates over a transfer log returning on the first error. func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) { if lg == nil || len(lg.Raw) == 0 { From 638b04b29ad8a2a82e6d7a4171cc6b2ee93699f6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 6 Jun 2022 21:53:03 +0300 Subject: [PATCH 3/6] interop: wrap contract.LoadToken in context.LoadToken Creating a closure in runtime is a relatively costly thing, but it can easily be avoided. --- pkg/compiler/interop_test.go | 2 +- pkg/core/blockchain.go | 6 +---- pkg/core/interop/context.go | 10 ++++++++ pkg/core/interop/contract/call.go | 36 +++++++++++++-------------- pkg/core/interop/crypto/ecdsa_test.go | 2 +- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index 120e29709..d737a3058 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -202,7 +202,7 @@ func TestAppCall(t *testing.T) { fc := fakechain.NewFakeChain() ic := interop.NewContext(trigger.Application, fc, dao.NewSimple(storage.NewMemoryStore(), false, false), - interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, zaptest.NewLogger(t)) + interop.DefaultBaseExecFee, native.DefaultStoragePrice, contractGetter, nil, nil, nil, nil, zaptest.NewLogger(t)) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 504bebd1e..94b55283a 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1110,7 +1110,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error v := systemInterop.SpawnVM() v.LoadScriptWithFlags(tx.Script, callflag.All) v.SetPriceGetter(systemInterop.GetPrice) - v.LoadToken = contract.LoadToken(systemInterop) v.GasLimit = tx.SystemFee err := systemInterop.Exec() @@ -2169,7 +2168,6 @@ func (bc *Blockchain) GetTestVM(t trigger.Type, tx *transaction.Transaction, b * systemInterop := bc.newInteropContext(t, bc.dao, b, tx) vm := systemInterop.SpawnVM() vm.SetPriceGetter(systemInterop.GetPrice) - vm.LoadToken = contract.LoadToken(systemInterop) return systemInterop } @@ -2204,7 +2202,6 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact systemInterop := bc.newInteropContext(t, dTrie, b, tx) vm := systemInterop.SpawnVM() vm.SetPriceGetter(systemInterop.GetPrice) - vm.LoadToken = contract.LoadToken(systemInterop) return systemInterop, nil } @@ -2280,7 +2277,6 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa vm := interopCtx.SpawnVM() vm.SetPriceGetter(interopCtx.GetPrice) - vm.LoadToken = contract.LoadToken(interopCtx) vm.GasLimit = gas if err := bc.InitVerificationContext(interopCtx, hash, witness); err != nil { return 0, err @@ -2376,7 +2372,7 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d *dao.Simple, blo // changes that were not yet persisted to Blockchain's dao. baseStorageFee = bc.contracts.Policy.GetStoragePriceInternal(d) } - ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, bc.contracts.Management.GetContract, bc.contracts.Contracts, block, tx, bc.log) + ic := interop.NewContext(trigger, bc, d, baseExecFee, baseStorageFee, bc.contracts.Management.GetContract, bc.contracts.Contracts, contract.LoadToken, block, tx, bc.log) ic.Functions = systemInterops switch { case tx != nil: diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0c7419284..0d78ce900 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -64,6 +64,7 @@ type Context struct { getContract func(*dao.Simple, util.Uint160) (*state.Contract, error) baseExecFee int64 baseStorageFee int64 + loadToken func(ic *Context, id int32) error GetRandomCounter uint32 signers []transaction.Signer } @@ -71,6 +72,7 @@ type Context struct { // NewContext returns new interop context. func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, baseStorageFee int64, getContract func(*dao.Simple, util.Uint160) (*state.Contract, error), natives []Contract, + loadTokenFunc func(ic *Context, id int32) error, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { dao := d.GetPrivate() cfg := bc.GetConfig() @@ -88,6 +90,7 @@ func NewContext(trigger trigger.Type, bc Ledger, d *dao.Simple, baseExecFee, bas getContract: getContract, baseExecFee: baseExecFee, baseStorageFee: baseStorageFee, + loadToken: loadTokenFunc, } } @@ -298,6 +301,12 @@ func (ic *Context) BaseStorageFee() int64 { return ic.baseStorageFee } +// LoadToken wraps externally provided load-token loading function providing it with context, +// this function can then be easily used by VM. +func (ic *Context) LoadToken(id int32) error { + return ic.loadToken(ic, id) +} + // SyscallHandler handles syscall with id. func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { f := ic.GetFunction(id) @@ -317,6 +326,7 @@ func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { // SpawnVM spawns a new VM with the specified gas limit and set context.VM field. func (ic *Context) SpawnVM() *vm.VM { v := vm.NewWithTrigger(ic.Trigger) + v.LoadToken = ic.LoadToken v.GasLimit = -1 v.SyscallHandler = ic.SyscallHandler ic.VM = v diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index a322bcc7c..26a23aaa1 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -22,26 +22,24 @@ type policyChecker interface { } // LoadToken calls method specified by the token id. -func LoadToken(ic *interop.Context) func(id int32) error { - return func(id int32) error { - ctx := ic.VM.Context() - if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) { - return errors.New("invalid call flags") - } - tok := ctx.NEF.Tokens[id] - if int(tok.ParamCount) > ctx.Estack().Len() { - return errors.New("stack is too small") - } - args := make([]stackitem.Item, tok.ParamCount) - for i := range args { - args[i] = ic.VM.Estack().Pop().Item() - } - cs, err := ic.GetContract(tok.Hash) - if err != nil { - return fmt.Errorf("token contract %s not found: %w", tok.Hash.StringLE(), err) - } - return callInternal(ic, cs, tok.Method, tok.CallFlag, tok.HasReturn, args, false) +func LoadToken(ic *interop.Context, id int32) error { + ctx := ic.VM.Context() + if !ctx.GetCallFlags().Has(callflag.ReadStates | callflag.AllowCall) { + return errors.New("invalid call flags") } + tok := ctx.NEF.Tokens[id] + if int(tok.ParamCount) > ctx.Estack().Len() { + return errors.New("stack is too small") + } + args := make([]stackitem.Item, tok.ParamCount) + for i := range args { + args[i] = ic.VM.Estack().Pop().Item() + } + cs, err := ic.GetContract(tok.Hash) + if err != nil { + return fmt.Errorf("token contract %s not found: %w", tok.Hash.StringLE(), err) + } + return callInternal(ic, cs, tok.Method, tok.CallFlag, tok.HasReturn, args, false) } // Call calls a contract with flags. diff --git a/pkg/core/interop/crypto/ecdsa_test.go b/pkg/core/interop/crypto/ecdsa_test.go index 9cfb370e1..fc6f70bfd 100644 --- a/pkg/core/interop/crypto/ecdsa_test.go +++ b/pkg/core/interop/crypto/ecdsa_test.go @@ -73,7 +73,7 @@ func initCheckMultisigVMNoArgs(container *transaction.Transaction) *vm.VM { trigger.Verification, fakechain.NewFakeChain(), dao.NewSimple(storage.NewMemoryStore(), false, false), - interop.DefaultBaseExecFee, native.DefaultStoragePrice, nil, nil, nil, + interop.DefaultBaseExecFee, native.DefaultStoragePrice, nil, nil, nil, nil, container, nil) ic.Container = container From bdc6624c9d2b7eea4f48a6dce59d8af5a812a6a4 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 6 Jun 2022 22:00:16 +0300 Subject: [PATCH 4/6] interop: unify VM price getter setting --- pkg/core/blockchain.go | 9 ++------- pkg/core/interop/context.go | 1 + 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 94b55283a..6d895d8f3 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1109,7 +1109,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(tx.Script, callflag.All) - v.SetPriceGetter(systemInterop.GetPrice) v.GasLimit = tx.SystemFee err := systemInterop.Exec() @@ -1283,7 +1282,6 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.S systemInterop := bc.newInteropContext(trig, cache, block, nil) v := systemInterop.SpawnVM() v.LoadScriptWithFlags(script, callflag.All) - v.SetPriceGetter(systemInterop.GetPrice) if err := systemInterop.Exec(); err != nil { return nil, fmt.Errorf("VM has failed: %w", err) } else if _, err := systemInterop.DAO.Persist(); err != nil { @@ -2166,8 +2164,7 @@ 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 { systemInterop := bc.newInteropContext(t, bc.dao, b, tx) - vm := systemInterop.SpawnVM() - vm.SetPriceGetter(systemInterop.GetPrice) + _ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready. return systemInterop } @@ -2200,8 +2197,7 @@ func (bc *Blockchain) GetTestHistoricVM(t trigger.Type, tx *transaction.Transact return nil, fmt.Errorf("failed to initialize native cache backed by historic DAO: %w", err) } systemInterop := bc.newInteropContext(t, dTrie, b, tx) - vm := systemInterop.SpawnVM() - vm.SetPriceGetter(systemInterop.GetPrice) + _ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready. return systemInterop, nil } @@ -2276,7 +2272,6 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa } vm := interopCtx.SpawnVM() - vm.SetPriceGetter(interopCtx.GetPrice) vm.GasLimit = gas if err := bc.InitVerificationContext(interopCtx, hash, witness); err != nil { return 0, err diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0d78ce900..3421afb21 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -329,6 +329,7 @@ func (ic *Context) SpawnVM() *vm.VM { v.LoadToken = ic.LoadToken v.GasLimit = -1 v.SyscallHandler = ic.SyscallHandler + v.SetPriceGetter(ic.GetPrice) ic.VM = v return v } From 92c94f265c3d69c4c1f1812546ce02406df6097f Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 6 Jun 2022 22:48:10 +0300 Subject: [PATCH 5/6] interop/vm: make VM reusable and use on VM for all in-block execs Avoid allocating again and again. Increases TPS by about 3%. --- pkg/core/blockchain.go | 20 ++++++++++++-------- pkg/core/interop/context.go | 12 +++++++++++- pkg/vm/vm.go | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 6d895d8f3..694d99b33 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1095,7 +1095,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error close(aerdone) }() _ = cache.GetItemCtx() // Prime serialization context cache (it'll be reused by upper layer DAOs). - aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) + aer, v, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist, nil) if err != nil { // Release goroutines, don't care about errors, we already have one. close(aerchan) @@ -1107,7 +1107,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error for _, tx := range block.Transactions { systemInterop := bc.newInteropContext(trigger.Application, cache, block, tx) - v := systemInterop.SpawnVM() + systemInterop.ReuseVM(v) v.LoadScriptWithFlags(tx.Script, callflag.All) v.GasLimit = tx.SystemFee @@ -1143,7 +1143,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error aerchan <- aer } - aer, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) + aer, _, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist, v) if err != nil { // Release goroutines, don't care about errors, we already have one. close(aerchan) @@ -1278,14 +1278,18 @@ func (bc *Blockchain) IsExtensibleAllowed(u util.Uint160) bool { return n < len(us) } -func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type) (*state.AppExecResult, error) { +func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.Simple, trig trigger.Type, v *vm.VM) (*state.AppExecResult, *vm.VM, error) { systemInterop := bc.newInteropContext(trig, cache, block, nil) - v := systemInterop.SpawnVM() + if v == nil { + v = systemInterop.SpawnVM() + } else { + systemInterop.ReuseVM(v) + } v.LoadScriptWithFlags(script, callflag.All) if err := systemInterop.Exec(); err != nil { - return nil, fmt.Errorf("VM has failed: %w", err) + return nil, v, fmt.Errorf("VM has failed: %w", err) } else if _, err := systemInterop.DAO.Persist(); err != nil { - return nil, fmt.Errorf("can't save changes: %w", err) + return nil, v, fmt.Errorf("can't save changes: %w", err) } return &state.AppExecResult{ Container: block.Hash(), // application logs can be retrieved by block hash @@ -1296,7 +1300,7 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.S Stack: v.Estack().ToArray(), Events: systemInterop.Notifications, }, - }, nil + }, v, nil } func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Simple, diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 3421afb21..f3b805132 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -326,12 +326,22 @@ func (ic *Context) SyscallHandler(_ *vm.VM, id uint32) error { // SpawnVM spawns a new VM with the specified gas limit and set context.VM field. func (ic *Context) SpawnVM() *vm.VM { v := vm.NewWithTrigger(ic.Trigger) + ic.initVM(v) + return v +} + +func (ic *Context) initVM(v *vm.VM) { v.LoadToken = ic.LoadToken v.GasLimit = -1 v.SyscallHandler = ic.SyscallHandler v.SetPriceGetter(ic.GetPrice) ic.VM = v - return v +} + +// ReuseVM resets given VM and allows to reuse it in the current context. +func (ic *Context) ReuseVM(v *vm.VM) { + v.Reset(ic.Trigger) + ic.initVM(v) } // RegisterCancelFunc adds the given function to the list of functions to be called after the VM diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index d7b744aed..95e03a2c1 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -122,6 +122,24 @@ func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) { v.getPrice = f } +// Reset allows to reuse existing VM for subsequent executions making them somewhat +// more efficient. It reuses invocation and evaluation stacks as well as VM structure +// itself. +func (v *VM) Reset(t trigger.Type) { + v.state = NoneState + v.getPrice = nil + v.istack.elems = v.istack.elems[:0] + v.estack.elems = v.estack.elems[:0] + v.uncaughtException = nil + v.refs = 0 + v.gasConsumed = 0 + v.GasLimit = 0 + v.SyscallHandler = nil + v.LoadToken = nil + v.trigger = t + v.invTree = nil +} + // GasConsumed returns the amount of GAS consumed during execution. func (v *VM) GasConsumed() int64 { return v.gasConsumed From 19ad31dc527f1c8fbd152a68f8f2b0cf4ccd7fbc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 7 Jun 2022 10:29:13 +0300 Subject: [PATCH 6/6] vm: optimize IsSignatureContract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We use it a lot in (*Blockchain).IsTxStillRelevant(). name old time/op new time/op delta IsSignatureContract-8 19.1ns ± 5% 1.2ns ± 4% -93.81% (p=0.000 n=10+10) name old alloc/op new alloc/op delta IsSignatureContract-8 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta IsSignatureContract-8 0.00 0.00 ~ (all equal) --- pkg/vm/bench_test.go | 9 +++++++++ pkg/vm/contract_checks.go | 17 +++++++---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/pkg/vm/bench_test.go b/pkg/vm/bench_test.go index c0403ea8b..d1ad21f24 100644 --- a/pkg/vm/bench_test.go +++ b/pkg/vm/bench_test.go @@ -48,3 +48,12 @@ func BenchmarkScriptPushPop(t *testing.B) { }) } } + +func BenchmarkIsSignatureContract(t *testing.B) { + b64script := "DCED2eixa9myLTNF1tTN4xvhw+HRYVMuPQzOy5Xs4utYM25BVuezJw==" + script, err := base64.StdEncoding.DecodeString(b64script) + require.NoError(t, err) + for n := 0; n < t.N; n++ { + _ = IsSignatureContract(script) + } +} diff --git a/pkg/vm/contract_checks.go b/pkg/vm/contract_checks.go index 1336765f4..51bb68040 100644 --- a/pkg/vm/contract_checks.go +++ b/pkg/vm/contract_checks.go @@ -118,17 +118,14 @@ func ParseSignatureContract(script []byte) ([]byte, bool) { return nil, false } - ctx := NewContext(script) - instr, param, err := ctx.Next() - if err != nil || instr != opcode.PUSHDATA1 || len(param) != 33 { - return nil, false + // We don't use Context for this simple case, it's more efficient this way. + if script[0] == byte(opcode.PUSHDATA1) && // PUSHDATA1 + script[1] == 33 && // with a public key parameter + script[35] == byte(opcode.SYSCALL) && // and a CheckSig SYSCALL. + binary.LittleEndian.Uint32(script[36:]) == verifyInteropID { + return script[2:35], true } - pub := param - instr, param, err = ctx.Next() - if err != nil || instr != opcode.SYSCALL || binary.LittleEndian.Uint32(param) != verifyInteropID { - return nil, false - } - return pub, true + return nil, false } // IsStandardContract checks whether the passed script is a signature or