diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index c48d993ec..57600137e 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -63,6 +63,15 @@ const ( // SyscallHandler is a type for syscall handler. type SyscallHandler = func(*VM, uint32) error +// OnExecHook is a type for a callback that is invoked +// for each executed instruction +type OnExecHook = func(scriptHash util.Uint160, offset int, opcode opcode.Opcode) + +// A struct that contains all VM hooks +type hooks struct { + onExec OnExecHook +} + // VM represents the virtual machine. type VM struct { state vmstate.State @@ -90,6 +99,10 @@ type VM struct { // invTree is a top-level invocation tree (if enabled). invTree *invocations.Tree + + // All registered hooks. + // Each hook should never be nil. + hooks hooks } var ( @@ -99,6 +112,10 @@ var ( bigTwo = big.NewInt(2) ) +var defaultHooks = hooks{ + onExec: func(scriptHash util.Uint160, offset int, opcode opcode.Opcode) {}, +} + // New returns a new VM object ready to load AVM bytecode scripts. func New() *VM { return NewWithTrigger(trigger.Application) @@ -109,6 +126,7 @@ func NewWithTrigger(t trigger.Type) *VM { vm := &VM{ state: vmstate.None, trigger: t, + hooks: defaultHooks, } vm.istack = make([]*Context, 0, 8) // Most of invocations use one-two contracts, but they're likely to have internal calls. @@ -116,6 +134,16 @@ func NewWithTrigger(t trigger.Type) *VM { return vm } +// SetOnExecHook sets the value of OnExecHook which +// will be invoked for each executed instruction. +// This function panics if the VM has been started. +func (v *VM) SetOnExecHook(hook OnExecHook) { + if v.state != vmstate.None { + panic("Cannot set onExec hook of a started VM") + } + v.hooks.onExec = hook +} + // SetPriceGetter registers the given PriceGetterFunc in v. // f accepts vm's Context, current instruction and instruction parameter. func (v *VM) SetPriceGetter(f func(opcode.Opcode, []byte) int64) { @@ -472,7 +500,9 @@ func (v *VM) Step() error { // step executes one instruction in the given context. func (v *VM) step(ctx *Context) error { + instruction_offset := v.Context().nextip op, param, err := ctx.Next() + v.hooks.onExec(v.GetCurrentScriptHash(), instruction_offset, op) if err != nil { v.state = vmstate.Fault return newError(ctx.ip, op, err)