// (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.
}
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)
-}
"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
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}
+++ /dev/null
-/*
-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
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")
}
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)
-}
"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")
OP_CHECKSIG Op = 0xac
OP_CHECKMULTISIG Op = 0xad
OP_TXSIGHASH Op = 0xae
- OP_BLOCKHASH Op = 0xaf
OP_CHECKOUTPUT Op = 0xc1
OP_ASSET Op = 0xc2
OP_ENTRYID Op = 0xca
OP_OUTPUTID Op = 0xcb
OP_NONCE Op = 0xcc
- OP_NEXTPROGRAM Op = 0xcd
- OP_BLOCKTIME Op = 0xce
)
type opInfo struct {
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},
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
"github.com/blockchain/errors"
)
-const initialRunLimit = 10000
-
type virtualMachine struct {
context *Context
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 {
}()
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,
}
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)
}
}
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
TraceOut = trace
vm := &virtualMachine{
program: prog,
- runLimit: int64(initialRunLimit),
+ runLimit: int64(10000),
dataStack: append([][]byte{}, c.args...),
}
err = vm.run()
}
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
// to a normal unit test.
MaxTimeMS: new(uint64),
}
- Verify(vctx)
+ Verify(vctx, 10000)
return true
}
}
}()
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 {
--- /dev/null
+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
+}
--- /dev/null
+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)
+ }
+ }
+}
--- /dev/null
+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...)
+}
--- /dev/null
+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}
+}