9 "github.com/blockchain/errors"
12 const initialRunLimit = 10000
14 type virtualMachine struct {
17 program []byte // the program currently executing
22 expansionReserved bool
24 // Stores the data parsed out of an opcode. Used as input to
25 // data-pushing opcodes.
28 // CHECKPREDICATE spawns a child vm with depth+1
31 // In each of these stacks, stack[len(stack)-1] is the top element.
36 // ErrFalseVMResult is one of the ways for a transaction to fail validation
37 var ErrFalseVMResult = errors.New("false VM result")
39 // TraceOut - if non-nil - will receive trace output during
41 var TraceOut io.Writer
43 func Verify(context *Context) (err error) {
45 if r := recover(); r != nil {
46 if rErr, ok := r.(error); ok {
47 err = errors.Sub(ErrUnexpected, rErr)
49 err = errors.Wrap(ErrUnexpected, r)
54 if context.VMVersion != 1 {
55 return ErrUnsupportedVM
58 vm := &virtualMachine{
59 expansionReserved: context.TxVersion != nil && *context.TxVersion == 1,
60 program: context.Code,
61 runLimit: initialRunLimit,
65 args := context.Arguments
66 for i, arg := range args {
67 err = vm.push(arg, false)
69 return errors.Wrapf(err, "pushing initial argument %d", i)
74 if err == nil && vm.falseResult() {
75 err = ErrFalseVMResult
78 return wrapErr(err, vm, args)
81 // falseResult returns true iff the stack is empty or the top
83 func (vm *virtualMachine) falseResult() bool {
84 return len(vm.dataStack) == 0 || !AsBool(vm.dataStack[len(vm.dataStack)-1])
87 func (vm *virtualMachine) run() error {
88 for vm.pc = 0; vm.pc < uint32(len(vm.program)); { // handle vm.pc updates in step
97 func (vm *virtualMachine) step() error {
98 inst, err := ParseOp(vm.program, vm.pc)
103 vm.nextPC = vm.pc + inst.Len
106 opname := inst.Op.String()
107 fmt.Fprintf(TraceOut, "vm %d pc %d limit %d %s", vm.depth, vm.pc, vm.runLimit, opname)
108 if len(inst.Data) > 0 {
109 fmt.Fprintf(TraceOut, " %x", inst.Data)
111 fmt.Fprint(TraceOut, "\n")
114 if isExpansion[inst.Op] {
115 if vm.expansionReserved {
116 return ErrDisallowedOpcode
119 return vm.applyCost(1)
124 err = ops[inst.Op].fn(vm)
128 err = vm.applyCost(vm.deferredCost)
135 for i := len(vm.dataStack) - 1; i >= 0; i-- {
136 fmt.Fprintf(TraceOut, " stack %d: %x\n", len(vm.dataStack)-1-i, vm.dataStack[i])
143 func (vm *virtualMachine) push(data []byte, deferred bool) error {
144 cost := 8 + int64(len(data))
148 err := vm.applyCost(cost)
153 vm.dataStack = append(vm.dataStack, data)
157 func (vm *virtualMachine) pushBool(b bool, deferred bool) error {
158 return vm.push(BoolBytes(b), deferred)
161 func (vm *virtualMachine) pushInt64(n int64, deferred bool) error {
162 return vm.push(Int64Bytes(n), deferred)
165 func (vm *virtualMachine) pop(deferred bool) ([]byte, error) {
166 if len(vm.dataStack) == 0 {
167 return nil, ErrDataStackUnderflow
169 res := vm.dataStack[len(vm.dataStack)-1]
170 vm.dataStack = vm.dataStack[:len(vm.dataStack)-1]
172 cost := 8 + int64(len(res))
182 func (vm *virtualMachine) popInt64(deferred bool) (int64, error) {
183 bytes, err := vm.pop(deferred)
187 n, err := AsInt64(bytes)
191 func (vm *virtualMachine) top() ([]byte, error) {
192 if len(vm.dataStack) == 0 {
193 return nil, ErrDataStackUnderflow
195 return vm.dataStack[len(vm.dataStack)-1], nil
198 // positive cost decreases runlimit, negative cost increases it
199 func (vm *virtualMachine) applyCost(n int64) error {
201 return ErrRunLimitExceeded
207 func (vm *virtualMachine) deferCost(n int64) {
211 func stackCost(stack [][]byte) int64 {
212 result := int64(8 * len(stack))
213 for _, item := range stack {
214 result += int64(len(item))
225 func (e Error) Error() string {
226 dis, err := Disassemble(e.Prog)
231 args := make([]string, 0, len(e.Args))
232 for _, a := range e.Args {
233 args = append(args, hex.EncodeToString(a))
236 return fmt.Sprintf("%s [prog %x = %s; args %s]", e.Err.Error(), e.Prog, dis, strings.Join(args, " "))
239 func wrapErr(err error, vm *virtualMachine, args [][]byte) error {