mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-22 19:19:09 +00:00
Merge pull request #2543 from nspcc-dev/perf-new
Minor allocation improvements
This commit is contained in:
commit
8673d2a79c
9 changed files with 118 additions and 63 deletions
|
@ -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()))
|
||||
|
|
|
@ -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,10 +1107,8 @@ 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.SetPriceGetter(systemInterop.GetPrice)
|
||||
v.LoadToken = contract.LoadToken(systemInterop)
|
||||
v.GasLimit = tx.SystemFee
|
||||
|
||||
err := systemInterop.Exec()
|
||||
|
@ -1145,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)
|
||||
|
@ -1280,15 +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)
|
||||
v.SetPriceGetter(systemInterop.GetPrice)
|
||||
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
|
||||
|
@ -1299,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,
|
||||
|
@ -1365,6 +1366,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 +1378,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 +1391,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.
|
||||
}
|
||||
}
|
||||
|
@ -1451,7 +1455,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
|
||||
|
@ -2164,9 +2168,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)
|
||||
vm.LoadToken = contract.LoadToken(systemInterop)
|
||||
_ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready.
|
||||
return systemInterop
|
||||
}
|
||||
|
||||
|
@ -2199,9 +2201,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)
|
||||
vm.LoadToken = contract.LoadToken(systemInterop)
|
||||
_ = systemInterop.SpawnVM() // All the other code suppose that the VM is ready.
|
||||
return systemInterop, nil
|
||||
}
|
||||
|
||||
|
@ -2276,8 +2276,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
|
||||
|
@ -2373,7 +2371,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:
|
||||
|
|
|
@ -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,10 +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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
18
pkg/vm/vm.go
18
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
|
||||
|
|
Loading…
Reference in a new issue