OSDN Git Service

vm is now charge gas
authorColt <colt@ColtdeMBP.lan>
Tue, 15 Aug 2017 06:27:16 +0000 (14:27 +0800)
committerColt <colt@ColtdeMBP.lan>
Tue, 15 Aug 2017 06:27:16 +0000 (14:27 +0800)
14 files changed:
protocol/vm/context.go
protocol/vm/crypto.go
protocol/vm/crypto_test.go
protocol/vm/doc.go [deleted file]
protocol/vm/errors.go
protocol/vm/introspection.go
protocol/vm/introspection_test.go
protocol/vm/ops.go
protocol/vm/vm.go
protocol/vm/vm_test.go
testutil/deepequal.go [new file with mode: 0644]
testutil/deepequal_test.go [new file with mode: 0644]
testutil/expect.go [new file with mode: 0644]
testutil/keys.go [new file with mode: 0644]

index c56663e..7aca163 100644 (file)
@@ -20,12 +20,6 @@ type Context struct {
        // (such as spends and issuances).
        TxVersion *uint64
 
-       // These fields must be present when verifying block headers.
-
-       BlockHash            *[]byte
-       BlockTimeMS          *uint64
-       NextConsensusProgram *[]byte
-
        // Fields below this point are required by particular opcodes when
        // verifying transaction components.
 
index 9825a7c..da27bdc 100644 (file)
@@ -136,14 +136,3 @@ func opTxSigHash(vm *virtualMachine) error {
        }
        return vm.push(vm.context.TxSigHash(), false)
 }
-
-func opBlockHash(vm *virtualMachine) error {
-       err := vm.applyCost(1)
-       if err != nil {
-               return err
-       }
-       if vm.context.BlockHash == nil {
-               return ErrContext
-       }
-       return vm.push(*vm.context.BlockHash, false)
-}
index cccdde9..1f3fe5d 100644 (file)
@@ -6,10 +6,6 @@ import (
        "github.com/blockchain/testutil"
 )
 
-var emptyBlockVMContext = &Context{
-       BlockHash: &[]uint8{0xf0, 0x85, 0x4f, 0x88, 0xb4, 0x89, 0x0, 0x99, 0x2f, 0xec, 0x40, 0x43, 0xf9, 0x65, 0xfa, 0x2, 0x9d, 0xeb, 0x8a, 0xd6, 0x93, 0xcf, 0x37, 0x11, 0xfe, 0x83, 0x9, 0xb3, 0x90, 0x6a, 0x5a, 0x86},
-}
-
 func TestCheckSig(t *testing.T) {
        cases := []struct {
                prog    string
@@ -399,29 +395,6 @@ func TestCryptoOps(t *testing.T) {
                        context:  &Context{},
                },
                wantErr: ErrRunLimitExceeded,
-       }, {
-               op: OP_BLOCKHASH,
-               startVM: &virtualMachine{
-                       runLimit: 50000,
-                       context:  emptyBlockVMContext,
-               },
-               wantVM: &virtualMachine{
-                       runLimit: 49959,
-                       dataStack: [][]byte{{
-                               240, 133, 79, 136, 180, 137, 0, 153,
-                               47, 236, 64, 67, 249, 101, 250, 2,
-                               157, 235, 138, 214, 147, 207, 55, 17,
-                               254, 131, 9, 179, 144, 106, 90, 134,
-                       }},
-                       context: emptyBlockVMContext,
-               },
-       }, {
-               op: OP_BLOCKHASH,
-               startVM: &virtualMachine{
-                       runLimit: 0,
-                       context:  emptyBlockVMContext,
-               },
-               wantErr: ErrRunLimitExceeded,
        }}
 
        hashOps := []Op{OP_SHA256, OP_SHA3}
diff --git a/protocol/vm/doc.go b/protocol/vm/doc.go
deleted file mode 100644 (file)
index 78b3940..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-Package vm implements the VM described in Chain Protocol 1.
-
-The VM is for verifying transaction inputs and blocks. Accordingly
-there are two main entrypoints: VerifyTxInput and VerifyBlockHeader,
-both in vm.go. Each constructs a disposable VM object to perform its
-computation.
-
-For VerifyTxInput, the program to execute comes from the input
-commitment: either the prevout's control program, if it's a spend
-input; or the issuance program, if it's an issuance. For
-VerifyBlockHeader, the program to execute is the previous block's
-consensus program.  In all cases, the VM's data stack is first
-populated with witness data from the current object (transaction input
-or block).
-
-The program is interpreted byte-by-byte by the main loop in
-virtualMachine.run(). Most bytes are opcodes in one of the following categories:
-  - bitwise
-  - control
-  - crypto
-  - introspection
-  - numeric
-  - pushdata
-  - splice
-  - stack
-Each category has a corresponding .go file implementing those opcodes.
-
-Each instruction incurs some cost when executed. These costs are
-deducted from (and in some cases refunded to) a predefined run
-limit. Costs are tallied in two conceptual phases: "before" the
-instruction runs and "after." In practice, "before" charges are
-applied on the fly in the body of each opcode's implementation, and
-"after" charges are deferred until the instruction finishes, at which
-point the VM main loop applies the deferred charges. As such,
-functions that have associated costs (chiefly stack pushing and
-popping) include a "deferred" flag as an argument.
-*/
-package vm
index d70c03b..54def8e 100644 (file)
@@ -9,6 +9,7 @@ var (
        ErrDataStackUnderflow = errors.New("data stack underflow")
        ErrDisallowedOpcode   = errors.New("disallowed opcode")
        ErrDivZero            = errors.New("division by zero")
+       ErrFalseVMResult      = errors.New("false VM result")
        ErrLongProgram        = errors.New("program size exceeds maxint32")
        ErrRange              = errors.New("range error")
        ErrReturn             = errors.New("RETURN executed")
index d1a525c..55e6ef9 100644 (file)
@@ -183,27 +183,3 @@ func opNonce(vm *virtualMachine) error {
        }
        return vm.push(*vm.context.AnchorID, true)
 }
-
-func opNextProgram(vm *virtualMachine) error {
-       err := vm.applyCost(1)
-       if err != nil {
-               return err
-       }
-
-       if vm.context.NextConsensusProgram == nil {
-               return ErrContext
-       }
-       return vm.push(*vm.context.NextConsensusProgram, true)
-}
-
-func opBlockTime(vm *virtualMachine) error {
-       err := vm.applyCost(1)
-       if err != nil {
-               return err
-       }
-
-       if vm.context.BlockTimeMS == nil {
-               return ErrContext
-       }
-       return vm.pushInt64(int64(*vm.context.BlockTimeMS), true)
-}
index b041905..21b8c8b 100644 (file)
@@ -9,91 +9,6 @@ import (
        "github.com/blockchain/testutil"
 )
 
-func TestNextProgram(t *testing.T) {
-       context := &Context{
-               NextConsensusProgram: &[]byte{1, 2, 3},
-       }
-
-       prog, err := Assemble("NEXTPROGRAM 0x010203 EQUAL")
-       if err != nil {
-               t.Fatal(err)
-       }
-       vm := &virtualMachine{
-               runLimit: 50000,
-               program:  prog,
-               context:  context,
-       }
-       err = vm.run()
-       if err != nil {
-               t.Errorf("got error %s, expected none", err)
-       }
-
-       prog, err = Assemble("NEXTPROGRAM 0x0102 EQUAL")
-       if err != nil {
-               t.Fatal(err)
-       }
-       vm = &virtualMachine{
-               runLimit: 50000,
-               program:  prog,
-               context:  context,
-       }
-       err = vm.run()
-       if err == nil && vm.falseResult() {
-               err = ErrFalseVMResult
-       }
-       switch err {
-       case nil:
-               t.Error("got ok result, expected failure")
-       case ErrFalseVMResult:
-               // ok
-       default:
-               t.Errorf("got error %s, expected ErrFalseVMResult", err)
-       }
-}
-
-func TestBlockTime(t *testing.T) {
-       var blockTimeMS uint64 = 3263826
-
-       prog, err := Assemble("BLOCKTIME 3263826 NUMEQUAL")
-       if err != nil {
-               t.Fatal(err)
-       }
-       vm := &virtualMachine{
-               runLimit: 50000,
-               program:  prog,
-               context:  &Context{BlockTimeMS: &blockTimeMS},
-       }
-       err = vm.run()
-       if err != nil {
-               t.Errorf("got error %s, expected none", err)
-       }
-       if vm.falseResult() {
-               t.Error("result is false, want success")
-       }
-
-       prog, err = Assemble("BLOCKTIME 3263827 NUMEQUAL")
-       if err != nil {
-               t.Fatal(err)
-       }
-       vm = &virtualMachine{
-               runLimit: 50000,
-               program:  prog,
-               context:  &Context{BlockTimeMS: &blockTimeMS},
-       }
-       err = vm.run()
-       if err == nil && vm.falseResult() {
-               err = ErrFalseVMResult
-       }
-       switch err {
-       case nil:
-               t.Error("got ok result, expected failure")
-       case ErrFalseVMResult:
-               // ok
-       default:
-               t.Errorf("got error %s, expected ErrFalseVMResult", err)
-       }
-}
-
 func TestOutputIDAndNonceOp(t *testing.T) {
        // arbitrary
        outputID := mustDecodeHex("0a60f9b12950c84c221012a808ef7782823b7e16b71fe2ba01811cda96a217df")
index a351a53..44f75af 100644 (file)
@@ -199,7 +199,6 @@ const (
        OP_CHECKSIG      Op = 0xac
        OP_CHECKMULTISIG Op = 0xad
        OP_TXSIGHASH     Op = 0xae
-       OP_BLOCKHASH     Op = 0xaf
 
        OP_CHECKOUTPUT Op = 0xc1
        OP_ASSET       Op = 0xc2
@@ -213,8 +212,6 @@ const (
        OP_ENTRYID     Op = 0xca
        OP_OUTPUTID    Op = 0xcb
        OP_NONCE       Op = 0xcc
-       OP_NEXTPROGRAM Op = 0xcd
-       OP_BLOCKTIME   Op = 0xce
 )
 
 type opInfo struct {
@@ -311,7 +308,6 @@ var (
                OP_CHECKSIG:      {OP_CHECKSIG, "CHECKSIG", opCheckSig},
                OP_CHECKMULTISIG: {OP_CHECKMULTISIG, "CHECKMULTISIG", opCheckMultiSig},
                OP_TXSIGHASH:     {OP_TXSIGHASH, "TXSIGHASH", opTxSigHash},
-               OP_BLOCKHASH:     {OP_BLOCKHASH, "BLOCKHASH", opBlockHash},
 
                OP_CHECKOUTPUT: {OP_CHECKOUTPUT, "CHECKOUTPUT", opCheckOutput},
                OP_ASSET:       {OP_ASSET, "ASSET", opAsset},
@@ -325,8 +321,6 @@ var (
                OP_ENTRYID:     {OP_ENTRYID, "ENTRYID", opEntryID},
                OP_OUTPUTID:    {OP_OUTPUTID, "OUTPUTID", opOutputID},
                OP_NONCE:       {OP_NONCE, "NONCE", opNonce},
-               OP_NEXTPROGRAM: {OP_NEXTPROGRAM, "NEXTPROGRAM", opNextProgram},
-               OP_BLOCKTIME:   {OP_BLOCKTIME, "BLOCKTIME", opBlockTime},
        }
 
        opsByName map[string]opInfo
index 3c8d057..8ef5011 100644 (file)
@@ -9,8 +9,6 @@ import (
        "github.com/blockchain/errors"
 )
 
-const initialRunLimit = 10000
-
 type virtualMachine struct {
        context *Context
 
@@ -33,14 +31,11 @@ type virtualMachine struct {
        altStack  [][]byte
 }
 
-// ErrFalseVMResult is one of the ways for a transaction to fail validation
-var ErrFalseVMResult = errors.New("false VM result")
-
 // TraceOut - if non-nil - will receive trace output during
 // execution.
 var TraceOut io.Writer
 
-func Verify(context *Context) (err error) {
+func Verify(context *Context, gasLimit int64) (gasLeft int64, err error) {
        defer func() {
                if r := recover(); r != nil {
                        if rErr, ok := r.(error); ok {
@@ -52,13 +47,13 @@ func Verify(context *Context) (err error) {
        }()
 
        if context.VMVersion != 1 {
-               return ErrUnsupportedVM
+               return gasLimit, ErrUnsupportedVM
        }
 
        vm := &virtualMachine{
                expansionReserved: context.TxVersion != nil && *context.TxVersion == 1,
                program:           context.Code,
-               runLimit:          initialRunLimit,
+               runLimit:          gasLimit,
                context:           context,
        }
 
@@ -66,7 +61,7 @@ func Verify(context *Context) (err error) {
        for i, arg := range args {
                err = vm.push(arg, false)
                if err != nil {
-                       return errors.Wrapf(err, "pushing initial argument %d", i)
+                       return vm.runLimit, errors.Wrapf(err, "pushing initial argument %d", i)
                }
        }
 
@@ -75,7 +70,7 @@ func Verify(context *Context) (err error) {
                err = ErrFalseVMResult
        }
 
-       return wrapErr(err, vm, args)
+       return vm.runLimit, wrapErr(err, vm, args)
 }
 
 // falseResult returns true iff the stack is empty or the top
index 26de7c9..cc38e47 100644 (file)
@@ -153,7 +153,7 @@ func doOKNotOK(t *testing.T, expectOK bool) {
                TraceOut = trace
                vm := &virtualMachine{
                        program:   prog,
-                       runLimit:  int64(initialRunLimit),
+                       runLimit:  int64(10000),
                        dataStack: append([][]byte{}, c.args...),
                }
                err = vm.run()
@@ -197,35 +197,13 @@ func TestVerifyTxInput(t *testing.T) {
        }
 
        for _, c := range cases {
-               gotErr := Verify(c.vctx)
+               _, gotErr := Verify(c.vctx, 10000)
                if errors.Root(gotErr) != c.wantErr {
                        t.Errorf("VerifyTxInput(%+v) err = %v want %v", c.vctx, gotErr, c.wantErr)
                }
        }
 }
 
-func TestVerifyBlockHeader(t *testing.T) {
-       consensusProg := []byte{byte(OP_ADD), byte(OP_5), byte(OP_NUMEQUAL)}
-       context := &Context{
-               VMVersion: 1,
-               Code:      consensusProg,
-               Arguments: [][]byte{{2}, {3}},
-       }
-       gotErr := Verify(context)
-       if gotErr != nil {
-               t.Errorf("unexpected error: %v", gotErr)
-       }
-
-       context = &Context{
-               VMVersion: 1,
-               Arguments: [][]byte{make([]byte, 50000)},
-       }
-       gotErr = Verify(context)
-       if errors.Root(gotErr) != ErrRunLimitExceeded {
-               t.Error("expected block to exceed run limit")
-       }
-}
-
 func TestRun(t *testing.T) {
        cases := []struct {
                vm      *virtualMachine
@@ -427,7 +405,7 @@ func TestVerifyTxInputQuickCheck(t *testing.T) {
                        // to a normal unit test.
                        MaxTimeMS: new(uint64),
                }
-               Verify(vctx)
+               Verify(vctx, 10000)
 
                return true
        }
@@ -449,14 +427,11 @@ func TestVerifyBlockHeaderQuickCheck(t *testing.T) {
                        }
                }()
                context := &Context{
-                       VMVersion:            1,
-                       Code:                 program,
-                       Arguments:            witnesses,
-                       BlockHash:            new([]byte),
-                       BlockTimeMS:          new(uint64),
-                       NextConsensusProgram: &[]byte{},
+                       VMVersion: 1,
+                       Code:      program,
+                       Arguments: witnesses,
                }
-               Verify(context)
+               Verify(context, 10000)
                return true
        }
        if err := quick.Check(f, nil); err != nil {
diff --git a/testutil/deepequal.go b/testutil/deepequal.go
new file mode 100644 (file)
index 0000000..538500b
--- /dev/null
@@ -0,0 +1,155 @@
+package testutil
+
+import (
+       "reflect"
+       "unsafe"
+)
+
+type visit struct {
+       a1, a2 unsafe.Pointer
+       typ    reflect.Type
+}
+
+// DeepEqual is similar to reflect.DeepEqual, but treats nil as equal
+// to empty maps and slices. Some of the implementation is cribbed
+// from Go's reflect package.
+func DeepEqual(x, y interface{}) bool {
+       vx := reflect.ValueOf(x)
+       vy := reflect.ValueOf(y)
+       return deepValueEqual(vx, vy, make(map[visit]bool))
+}
+
+func deepValueEqual(x, y reflect.Value, visited map[visit]bool) bool {
+       if isEmpty(x) && isEmpty(y) {
+               return true
+       }
+       if !x.IsValid() {
+               return !y.IsValid()
+       }
+       if !y.IsValid() {
+               return false
+       }
+
+       tx := x.Type()
+       ty := y.Type()
+       if tx != ty {
+               return false
+       }
+
+       switch tx.Kind() {
+       case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
+               if x.CanAddr() && y.CanAddr() {
+                       a1 := unsafe.Pointer(x.UnsafeAddr())
+                       a2 := unsafe.Pointer(y.UnsafeAddr())
+                       if uintptr(a1) > uintptr(a2) {
+                               // Canonicalize order to reduce number of entries in visited.
+                               // Assumes non-moving garbage collector.
+                               a1, a2 = a2, a1
+                       }
+                       v := visit{a1, a2, tx}
+                       if visited[v] {
+                               return true
+                       }
+                       visited[v] = true
+               }
+       }
+
+       switch tx.Kind() {
+       case reflect.Bool:
+               return x.Bool() == y.Bool()
+
+       case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+               return x.Int() == y.Int()
+
+       case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+               return x.Uint() == y.Uint()
+
+       case reflect.Float32, reflect.Float64:
+               return x.Float() == y.Float()
+
+       case reflect.Complex64, reflect.Complex128:
+               return x.Complex() == y.Complex()
+
+       case reflect.String:
+               return x.String() == y.String()
+
+       case reflect.Array:
+               for i := 0; i < tx.Len(); i++ {
+                       if !deepValueEqual(x.Index(i), y.Index(i), visited) {
+                               return false
+                       }
+               }
+               return true
+
+       case reflect.Slice:
+               ttx := tx.Elem()
+               tty := ty.Elem()
+               if ttx != tty {
+                       return false
+               }
+               if x.Len() != y.Len() {
+                       return false
+               }
+               for i := 0; i < x.Len(); i++ {
+                       if !deepValueEqual(x.Index(i), y.Index(i), visited) {
+                               return false
+                       }
+               }
+               return true
+
+       case reflect.Interface:
+               if x.IsNil() {
+                       return y.IsNil()
+               }
+               if y.IsNil() {
+                       return false
+               }
+               return deepValueEqual(x.Elem(), y.Elem(), visited)
+
+       case reflect.Ptr:
+               if x.Pointer() == y.Pointer() {
+                       return true
+               }
+               return deepValueEqual(x.Elem(), y.Elem(), visited)
+
+       case reflect.Struct:
+               for i := 0; i < tx.NumField(); i++ {
+                       if !deepValueEqual(x.Field(i), y.Field(i), visited) {
+                               return false
+                       }
+               }
+               return true
+
+       case reflect.Map:
+               if x.Pointer() == y.Pointer() {
+                       return true
+               }
+               if x.Len() != y.Len() {
+                       return false
+               }
+               for _, k := range x.MapKeys() {
+                       if !deepValueEqual(x.MapIndex(k), y.MapIndex(k), visited) {
+                               return false
+                       }
+               }
+               return true
+
+       case reflect.Func:
+               return x.IsNil() && y.IsNil()
+       }
+       return false
+}
+
+func isEmpty(v reflect.Value) bool {
+       if !v.IsValid() {
+               return true
+       }
+       switch v.Type().Kind() {
+       case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr:
+               return v.IsNil()
+
+       case reflect.Slice, reflect.Map:
+               return v.IsNil() || v.Len() == 0
+       }
+       return false
+}
diff --git a/testutil/deepequal_test.go b/testutil/deepequal_test.go
new file mode 100644 (file)
index 0000000..0544e0e
--- /dev/null
@@ -0,0 +1,40 @@
+package testutil
+
+import "testing"
+
+func TestDeepEqual(t *testing.T) {
+       type s struct {
+               a int
+               b string
+       }
+
+       cases := []struct {
+               a, b interface{}
+               want bool
+       }{
+               {1, 1, true},
+               {1, 2, false},
+               {nil, nil, true},
+               {nil, []byte{}, true},
+               {nil, []byte{1}, false},
+               {[]byte{1}, []byte{1}, true},
+               {[]byte{1}, []byte{2}, false},
+               {[]byte{1}, []byte{1, 2}, false},
+               {[]byte{1}, []string{"1"}, false},
+               {[3]byte{}, [4]byte{}, false},
+               {[3]byte{1}, [3]byte{1, 0, 0}, true},
+               {s{}, s{}, true},
+               {s{a: 1}, s{}, false},
+               {s{b: "foo"}, s{}, false},
+               {"foo", "foo", true},
+               {"foo", "bar", false},
+               {"foo", nil, false},
+       }
+
+       for i, c := range cases {
+               got := DeepEqual(c.a, c.b)
+               if got != c.want {
+                       t.Errorf("case %d: got %v want %v", i, got, c.want)
+               }
+       }
+}
diff --git a/testutil/expect.go b/testutil/expect.go
new file mode 100644 (file)
index 0000000..791622f
--- /dev/null
@@ -0,0 +1,27 @@
+package testutil
+
+import (
+       "fmt"
+       "os"
+       "path/filepath"
+       "strings"
+       "testing"
+
+       "chain/errors"
+)
+
+var wd, _ = os.Getwd()
+
+func FatalErr(t testing.TB, err error) {
+       args := []interface{}{err}
+       for _, frame := range errors.Stack(err) {
+               file := frame.File
+               if rel, err := filepath.Rel(wd, file); err == nil && !strings.HasPrefix(rel, "../") {
+                       file = rel
+               }
+               funcname := frame.Func[strings.IndexByte(frame.Func, '.')+1:]
+               s := fmt.Sprintf("\n%s:%d: %s", file, frame.Line, funcname)
+               args = append(args, s)
+       }
+       t.Fatal(args...)
+}
diff --git a/testutil/keys.go b/testutil/keys.go
new file mode 100644 (file)
index 0000000..2fc389f
--- /dev/null
@@ -0,0 +1,32 @@
+package testutil
+
+import (
+       "chain/crypto/ed25519"
+       "chain/crypto/ed25519/chainkd"
+)
+
+var (
+       TestXPub chainkd.XPub
+       TestXPrv chainkd.XPrv
+       TestPub  ed25519.PublicKey
+       TestPubs []ed25519.PublicKey
+)
+
+type zeroReader struct{}
+
+func (z zeroReader) Read(buf []byte) (int, error) {
+       for i := range buf {
+               buf[i] = 0
+       }
+       return len(buf), nil
+}
+
+func init() {
+       var err error
+       TestXPrv, TestXPub, err = chainkd.NewXKeys(zeroReader{})
+       if err != nil {
+               panic(err)
+       }
+       TestPub = TestXPub.PublicKey()
+       TestPubs = []ed25519.PublicKey{TestPub}
+}