package vm import ( "encoding/hex" "fmt" "io" "strings" "github.com/bytom/vapor/errors" ) type virtualMachine struct { context *Context program []byte // the program currently executing pc, nextPC uint32 runLimit int64 deferredCost int64 expansionReserved bool // Stores the data parsed out of an opcode. Used as input to // data-pushing opcodes. data []byte // CHECKPREDICATE spawns a child vm with depth+1 depth int // In each of these stacks, stack[len(stack)-1] is the top element. dataStack [][]byte altStack [][]byte } // TraceOut - if non-nil - will receive trace output during // execution. var TraceOut io.Writer // Verify program by running VM func Verify(context *Context, gasLimit int64) (gasLeft int64, err error) { defer func() { if r := recover(); r != nil { if rErr, ok := r.(error); ok { err = errors.Sub(ErrUnexpected, rErr) } else { err = errors.Wrap(ErrUnexpected, r) } } }() if context.VMVersion != 1 { return gasLimit, ErrUnsupportedVM } vm := &virtualMachine{ expansionReserved: context.TxVersion != nil && *context.TxVersion == 1, program: context.Code, runLimit: gasLimit, context: context, } args := context.Arguments for i, arg := range args { err = vm.push(arg, false) if err != nil { return vm.runLimit, errors.Wrapf(err, "pushing initial argument %d", i) } } err = vm.run() if err == nil && vm.falseResult() { err = ErrFalseVMResult } return vm.runLimit, wrapErr(err, vm, args) } // falseResult returns true iff the stack is empty or the top // item is false func (vm *virtualMachine) falseResult() bool { return len(vm.dataStack) == 0 || !AsBool(vm.dataStack[len(vm.dataStack)-1]) } func (vm *virtualMachine) run() error { for vm.pc = 0; vm.pc < uint32(len(vm.program)); { // handle vm.pc updates in step err := vm.step() if err != nil { return err } } return nil } func (vm *virtualMachine) step() error { inst, err := ParseOp(vm.program, vm.pc) if err != nil { return err } vm.nextPC = vm.pc + inst.Len if TraceOut != nil { opname := inst.Op.String() fmt.Fprintf(TraceOut, "vm %d pc %d limit %d %s", vm.depth, vm.pc, vm.runLimit, opname) if len(inst.Data) > 0 { fmt.Fprintf(TraceOut, " %x", inst.Data) } fmt.Fprint(TraceOut, "\n") } if isExpansion[inst.Op] { if vm.expansionReserved { return ErrDisallowedOpcode } vm.pc = vm.nextPC return vm.applyCost(1) } vm.deferredCost = 0 vm.data = inst.Data err = ops[inst.Op].fn(vm) if err != nil { return err } err = vm.applyCost(vm.deferredCost) if err != nil { return err } vm.pc = vm.nextPC if TraceOut != nil { for i := len(vm.dataStack) - 1; i >= 0; i-- { fmt.Fprintf(TraceOut, " stack %d: %x\n", len(vm.dataStack)-1-i, vm.dataStack[i]) } } return nil } func (vm *virtualMachine) push(data []byte, deferred bool) error { cost := 8 + int64(len(data)) if deferred { vm.deferCost(cost) } else { err := vm.applyCost(cost) if err != nil { return err } } vm.dataStack = append(vm.dataStack, data) return nil } func (vm *virtualMachine) pushBool(b bool, deferred bool) error { return vm.push(BoolBytes(b), deferred) } func (vm *virtualMachine) pushInt64(n int64, deferred bool) error { return vm.push(Int64Bytes(n), deferred) } func (vm *virtualMachine) pop(deferred bool) ([]byte, error) { if len(vm.dataStack) == 0 { return nil, ErrDataStackUnderflow } res := vm.dataStack[len(vm.dataStack)-1] vm.dataStack = vm.dataStack[:len(vm.dataStack)-1] cost := 8 + int64(len(res)) if deferred { vm.deferCost(-cost) } else { vm.runLimit += cost } return res, nil } func (vm *virtualMachine) popInt64(deferred bool) (int64, error) { bytes, err := vm.pop(deferred) if err != nil { return 0, err } n, err := AsInt64(bytes) return n, err } func (vm *virtualMachine) top() ([]byte, error) { if len(vm.dataStack) == 0 { return nil, ErrDataStackUnderflow } return vm.dataStack[len(vm.dataStack)-1], nil } // positive cost decreases runlimit, negative cost increases it func (vm *virtualMachine) applyCost(n int64) error { if n > vm.runLimit { vm.runLimit = 0 return ErrRunLimitExceeded } vm.runLimit -= n return nil } func (vm *virtualMachine) deferCost(n int64) { vm.deferredCost += n } func stackCost(stack [][]byte) int64 { result := int64(8 * len(stack)) for _, item := range stack { result += int64(len(item)) } return result } func wrapErr(err error, vm *virtualMachine, args [][]byte) error { if err == nil { return nil } dis, errDis := Disassemble(vm.program) if errDis != nil { dis = "???" } dataArgs := make([]string, 0, len(args)) for _, a := range args { dataArgs = append(dataArgs, hex.EncodeToString(a)) } return errors.Wrap(err, fmt.Sprintf("%s [prog %x = %s; args %s]", err.Error(), vm.program, dis, strings.Join(dataArgs, " "))) }