diff --git a/core/generator/generator_test.go b/core/generator/generator_test.go index 58c6b411e4..11cdd790cb 100644 --- a/core/generator/generator_test.go +++ b/core/generator/generator_test.go @@ -83,7 +83,7 @@ func TestGetAndAddBlockSignatures(t *testing.T) { testutil.FatalErr(t, err) } - err = vm.VerifyBlockHeader(&tip.BlockHeader, block) + err = vm.Verify(bc.NewBlockVMContext(block, tip.ConsensusProgram, block.Witness)) if err != nil { testutil.FatalErr(t, err) } diff --git a/generated/rev/RevId.java b/generated/rev/RevId.java index dd5efae744..759808228f 100644 --- a/generated/rev/RevId.java +++ b/generated/rev/RevId.java @@ -1,4 +1,4 @@ public final class RevId { - public final String Id = "main/rev2841"; + public final String Id = "main/rev2842"; } diff --git a/generated/rev/revid.go b/generated/rev/revid.go index 5a5c6ca340..3601bf715c 100644 --- a/generated/rev/revid.go +++ b/generated/rev/revid.go @@ -1,3 +1,3 @@ package rev -const ID string = "main/rev2841" +const ID string = "main/rev2842" diff --git a/generated/rev/revid.js b/generated/rev/revid.js index 2cefbe03cf..af45fe93c9 100644 --- a/generated/rev/revid.js +++ b/generated/rev/revid.js @@ -1,2 +1,2 @@ -export const rev_id = "main/rev2841" +export const rev_id = "main/rev2842" diff --git a/generated/rev/revid.rb b/generated/rev/revid.rb index b9960e6084..7648ebd110 100644 --- a/generated/rev/revid.rb +++ b/generated/rev/revid.rb @@ -1,4 +1,4 @@ module Chain::Rev - ID = "main/rev2841".freeze + ID = "main/rev2842".freeze end diff --git a/protocol/bc/tx_test.go b/protocol/bc/tx_test.go index 92d9eaf680..2165f7cbb4 100644 --- a/protocol/bc/tx_test.go +++ b/protocol/bc/tx_test.go @@ -22,8 +22,8 @@ func TestTxHashes(t *testing.T) { if err != nil { t.Fatal(err) } - if len(hashes.VMContexts) != len(c.txdata.Inputs) { - t.Errorf("case %d: len(hashes.VMContexts) = %d, want %d", i, len(hashes.VMContexts), len(c.txdata.Inputs)) + if len(hashes.SpentOutputIDs) != len(c.txdata.Inputs) { + t.Errorf("case %d: len(hashes.SpentOutputIDs) = %d, want %d", i, len(hashes.SpentOutputIDs), len(c.txdata.Inputs)) } if c.hash != hashes.ID { t.Errorf("case %d: got txid %x, want %x", i, hashes.ID[:], c.hash[:]) diff --git a/protocol/bc/txhashes.go b/protocol/bc/txhashes.go index 73b9082836..3b19a37f02 100644 --- a/protocol/bc/txhashes.go +++ b/protocol/bc/txhashes.go @@ -11,8 +11,8 @@ type ( ID Hash ExpirationMS uint64 } - SpentOutputIDs []Hash // one per old-style Input. Non-spend inputs are blank hashes. - VMContexts []*VMContext // one per old-style Input + SpentOutputIDs []Hash // one per old-style Input. Non-spend inputs are blank hashes. + SigHashes []Hash // one per old-style Input. } // ResultInfo contains information about each result in a transaction header. @@ -24,17 +24,8 @@ type ( SourcePos uint64 // the position within the source entry of this output's value RefDataHash Hash // contents of the result entry's data field (which is a hash of the source refdata, when converting from old-style transactions) } - - VMContext struct { - TxRefDataHash Hash - RefDataHash Hash - TxSigHash Hash - OutputID *Hash - EntryID Hash - NonceID *Hash - } ) func (t TxHashes) SigHash(n uint32) Hash { - return t.VMContexts[n].TxSigHash + return t.SigHashes[n] } diff --git a/protocol/bc/txtransaction.go b/protocol/bc/txtransaction.go index 994adddfaa..903cbb6ec9 100644 --- a/protocol/bc/txtransaction.go +++ b/protocol/bc/txtransaction.go @@ -50,8 +50,8 @@ func ComputeTxHashes(oldTx *TxData) (hashes *TxHashes, err error) { } } - hashes.VMContexts = make([]*VMContext, len(oldTx.Inputs)) hashes.SpentOutputIDs = make([]Hash, len(oldTx.Inputs)) + hashes.SigHashes = make([]Hash, len(oldTx.Inputs)) for entryID, ent := range entries { switch ent := ent.(type) { @@ -73,14 +73,10 @@ func ComputeTxHashes(oldTx *TxData) (hashes *TxHashes, err error) { hashes.Issuances = append(hashes.Issuances, iss) case *Issuance: - vmc := newVMContext(entryID, hashes.ID, header.body.Data, ent.body.Data) - vmc.NonceID = &ent.body.Anchor - hashes.VMContexts[ent.Ordinal()] = vmc + hashes.SigHashes[ent.Ordinal()] = makeSigHash(entryID, hashes.ID) case *Spend: - vmc := newVMContext(entryID, hashes.ID, header.body.Data, ent.body.Data) - vmc.OutputID = &ent.body.SpentOutput - hashes.VMContexts[ent.Ordinal()] = vmc + hashes.SigHashes[ent.Ordinal()] = makeSigHash(entryID, hashes.ID) hashes.SpentOutputIDs[ent.Ordinal()] = ent.body.SpentOutput } } @@ -88,26 +84,11 @@ func ComputeTxHashes(oldTx *TxData) (hashes *TxHashes, err error) { return hashes, nil } -// populates the common fields of a VMContext for an Entry, regardless of whether -// that Entry is a Spend or an Issuance -func newVMContext(entryID, txid, txData, inpData Hash) *VMContext { - vmc := new(VMContext) - - // TxRefDataHash - vmc.TxRefDataHash = txData - - // RefDataHash (input-specific) - vmc.RefDataHash = inpData - - // EntryID - vmc.EntryID = entryID - - // TxSigHash +func makeSigHash(entryID, txID Hash) (hash Hash) { hasher := sha3pool.Get256() defer sha3pool.Put256(hasher) hasher.Write(entryID[:]) - hasher.Write(txid[:]) - hasher.Read(vmc.TxSigHash[:]) - - return vmc + hasher.Write(txID[:]) + hasher.Read(hash[:]) + return hash } diff --git a/protocol/bc/vmcontext.go b/protocol/bc/vmcontext.go new file mode 100644 index 0000000000..6fc56b9fe7 --- /dev/null +++ b/protocol/bc/vmcontext.go @@ -0,0 +1,89 @@ +package bc + +import ( + "bytes" + + "chain/protocol/vm" +) + +func NewBlockVMContext(block *Block, prog []byte, args [][]byte) *vm.Context { + blockHash := block.Hash().Bytes() + return &vm.Context{ + VMVersion: 1, + Code: prog, + Arguments: args, + + BlockHash: &blockHash, + BlockTimeMS: &block.TimestampMS, + NextConsensusProgram: &block.ConsensusProgram, + } +} + +func NewTxVMContext(tx *Tx, index uint32, prog Program, args [][]byte) *vm.Context { + var ( + txSigHash = tx.SigHash(index).Bytes() + numResults = uint64(len(tx.Results)) + assetID = tx.Inputs[index].AssetID() + assetIDBytes = assetID[:] + amount = tx.Inputs[index].Amount() + inputRefDataHash = hashData(tx.Inputs[index].ReferenceData).Bytes() + txRefDataHash = hashData(tx.ReferenceData).Bytes() + ) + + checkOutput := func(index uint64, refdatahash []byte, amount uint64, assetID []byte, vmVersion uint64, code []byte) (bool, error) { + if index >= uint64(len(tx.Outputs)) { + return false, vm.ErrBadValue + } + o := tx.Outputs[index] + if o.AssetVersion != 1 { + return false, nil + } + if o.Amount != uint64(amount) { + return false, nil + } + if o.VMVersion != uint64(vmVersion) { + return false, nil + } + if !bytes.Equal(o.ControlProgram, code) { + return false, nil + } + if !bytes.Equal(o.AssetID[:], assetID) { + return false, nil + } + if len(refdatahash) > 0 { + h := hashData(o.ReferenceData) + if !bytes.Equal(h[:], refdatahash) { + return false, nil + } + } + return true, nil + } + + result := &vm.Context{ + VMVersion: prog.VMVersion, + Code: prog.Code, + Arguments: args, + + TxVersion: &tx.Version, + + TxSigHash: &txSigHash, + NumResults: &numResults, + AssetID: &assetIDBytes, + Amount: &amount, + MinTimeMS: &tx.MinTime, + MaxTimeMS: &tx.MaxTime, + InputRefDataHash: &inputRefDataHash, + TxRefDataHash: &txRefDataHash, + InputIndex: &index, + CheckOutput: checkOutput, + } + switch inp := tx.Inputs[index].TypedInput.(type) { + case *IssuanceInput: + result.Nonce = &inp.Nonce + case *SpendInput: + spentOutputID := tx.TxHashes.SpentOutputIDs[index][:] + result.SpentOutputID = &spentOutputID + } + + return result +} diff --git a/protocol/validation/block.go b/protocol/validation/block.go index 0685ccfe93..4582be2309 100644 --- a/protocol/validation/block.go +++ b/protocol/validation/block.go @@ -34,7 +34,7 @@ var ( // then calls ValidateBlock. func ValidateBlockForAccept(ctx context.Context, snapshot *state.Snapshot, initialBlockHash bc.Hash, prevBlock, block *bc.Block, validateTx func(*bc.Tx) error) error { if prevBlock != nil { - err := vm.VerifyBlockHeader(&prevBlock.BlockHeader, block) + err := vm.Verify(bc.NewBlockVMContext(block, prevBlock.ConsensusProgram, block.Witness)) if err != nil { pkScriptStr, _ := vm.Disassemble(prevBlock.ConsensusProgram) witnessStrs := make([]string, 0, len(block.Witness)) diff --git a/protocol/validation/tx.go b/protocol/validation/tx.go index 79f65ae745..bd01428bc8 100644 --- a/protocol/validation/tx.go +++ b/protocol/validation/tx.go @@ -249,8 +249,20 @@ func CheckTxWellFormed(tx *bc.Tx) error { } } - for i := range tx.Inputs { - err := vm.VerifyTxInput(tx, uint32(i)) + for i, inp := range tx.Inputs { + var ( + prog bc.Program + args [][]byte + ) + switch inp := inp.TypedInput.(type) { + case *bc.IssuanceInput: + prog = bc.Program{VMVersion: inp.VMVersion, Code: inp.IssuanceProgram} + args = inp.Arguments + case *bc.SpendInput: + prog = bc.Program{VMVersion: inp.VMVersion, Code: inp.ControlProgram} + args = inp.Arguments + } + err := vm.Verify(bc.NewTxVMContext(tx, uint32(i), prog, args)) if err != nil { return badTxErrf(err, "validation failed in script execution, input %d", i) } diff --git a/protocol/vm/assemble_test.go b/protocol/vm/assemble_test.go index 8f3064d487..8ca0864b49 100644 --- a/protocol/vm/assemble_test.go +++ b/protocol/vm/assemble_test.go @@ -72,3 +72,11 @@ func TestDisassemble(t *testing.T) { } } } + +func mustDecodeHex(h string) []byte { + bits, err := hex.DecodeString(h) + if err != nil { + panic(err) + } + return bits +} diff --git a/protocol/vm/context.go b/protocol/vm/context.go new file mode 100644 index 0000000000..85460663e8 --- /dev/null +++ b/protocol/vm/context.go @@ -0,0 +1,43 @@ +package vm + +// Context contains the execution context for the virtual machine. +// +// Most fields are pointers and are not required to be present in all +// cases. A nil pointer means the value is absent in that context. If +// an opcode executes that requires an absent field to be present, it +// will return ErrContext. +// +// By convention, variables of this type have the name context, _not_ +// ctx (to avoid confusion with context.Context). +type Context struct { + VMVersion uint64 + Code []byte + Arguments [][]byte + + // TxVersion must be present when verifying transaction components + // (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. + + TxSigHash *[]byte + NumResults *uint64 + AssetID *[]byte + Amount *uint64 + MinTimeMS *uint64 + MaxTimeMS *uint64 + InputRefDataHash *[]byte + TxRefDataHash *[]byte + InputIndex *uint32 + Nonce *[]byte + SpentOutputID *[]byte + + CheckOutput func(index uint64, data []byte, amount uint64, assetID []byte, vmVersion uint64, code []byte) (bool, error) +} diff --git a/protocol/vm/control.go b/protocol/vm/control.go index 4a31cf1ee1..5bbb4be00a 100644 --- a/protocol/vm/control.go +++ b/protocol/vm/control.go @@ -61,14 +61,11 @@ func opCheckPredicate(vm *virtualMachine) error { } childVM := virtualMachine{ - mainprog: vm.mainprog, - program: predicate, - runLimit: limit, - depth: vm.depth + 1, - dataStack: append([][]byte{}, vm.dataStack[l-n:]...), - tx: vm.tx, - txContext: vm.txContext, - inputIndex: vm.inputIndex, + context: vm.context, + program: predicate, + runLimit: limit, + depth: vm.depth + 1, + dataStack: append([][]byte{}, vm.dataStack[l-n:]...), } vm.dataStack = vm.dataStack[:l-n] diff --git a/protocol/vm/crypto.go b/protocol/vm/crypto.go index 49986732a8..33e8b08cf5 100644 --- a/protocol/vm/crypto.go +++ b/protocol/vm/crypto.go @@ -127,25 +127,23 @@ func opCheckMultiSig(vm *virtualMachine) error { } func opTxSigHash(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } err := vm.applyCost(256) if err != nil { return err } - h := vm.txContext.TxSigHash - return vm.push(h[:], false) + if vm.context.TxSigHash == nil { + return ErrContext + } + return vm.push(*vm.context.TxSigHash, false) } func opBlockHash(vm *virtualMachine) error { - if vm.block == nil { - return ErrContext - } - h := vm.block.Hash() - err := vm.applyCost(4 * int64(len(h))) + err := vm.applyCost(1) if err != nil { return err } - return vm.push(h[:], false) + if vm.context.BlockHash == nil { + return ErrContext + } + return vm.push(*vm.context.BlockHash, false) } diff --git a/protocol/vm/crypto_test.go b/protocol/vm/crypto_test.go index 9cec671a68..f3dd255340 100644 --- a/protocol/vm/crypto_test.go +++ b/protocol/vm/crypto_test.go @@ -1,10 +1,11 @@ -package vm +package vm_test import ( "encoding/hex" "testing" "chain/protocol/bc" + . "chain/protocol/vm" "chain/testutil" ) @@ -67,11 +68,11 @@ func TestCheckSig(t *testing.T) { if err != nil { t.Fatalf("case %d: %s", i, err) } - vm := &virtualMachine{ - program: prog, - runLimit: 50000, + vm := &VirtualMachine{ + Program: prog, + RunLimit: 50000, } - err = vm.run() + _, err = vm.Run() if c.err { if err == nil { t.Errorf("case %d: expected error, got ok result", i) @@ -80,7 +81,7 @@ func TestCheckSig(t *testing.T) { if err != nil { t.Errorf("case %d: expected ok result, got error %s", i, err) } - } else if !vm.falseResult() { + } else if !vm.FalseResult() { t.Errorf("case %d: expected false VM result, got error %s", i, err) } } @@ -94,147 +95,147 @@ func TestCryptoOps(t *testing.T) { type testStruct struct { op Op - startVM *virtualMachine + startVM *VirtualMachine wantErr error - wantVM *virtualMachine + wantVM *VirtualMachine } cases := []testStruct{{ op: OP_SHA256, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{{1}}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{{1}}, }, - wantVM: &virtualMachine{ - runLimit: 49905, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49905, + DataStack: [][]byte{{ 75, 245, 18, 47, 52, 69, 84, 197, 59, 222, 46, 187, 140, 210, 183, 227, 209, 96, 10, 214, 49, 195, 133, 165, 215, 204, 226, 60, 119, 133, 69, 154, }}, }, }, { op: OP_SHA256, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{make([]byte, 65)}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{make([]byte, 65)}, }, - wantVM: &virtualMachine{ - runLimit: 49968, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49968, + DataStack: [][]byte{{ 152, 206, 66, 222, 239, 81, 212, 2, 105, 213, 66, 245, 49, 75, 239, 44, 116, 104, 212, 1, 173, 93, 133, 22, 139, 250, 180, 192, 16, 143, 117, 247, }}, }, }, { op: OP_SHA3, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{{1}}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{{1}}, }, - wantVM: &virtualMachine{ - runLimit: 49905, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49905, + DataStack: [][]byte{{ 39, 103, 241, 92, 138, 242, 242, 199, 34, 93, 82, 115, 253, 214, 131, 237, 199, 20, 17, 10, 152, 125, 16, 84, 105, 124, 52, 138, 237, 78, 108, 199, }}, }, }, { op: OP_SHA3, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{make([]byte, 65)}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{make([]byte, 65)}, }, - wantVM: &virtualMachine{ - runLimit: 49968, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49968, + DataStack: [][]byte{{ 65, 106, 167, 181, 192, 224, 101, 48, 102, 167, 198, 77, 189, 208, 0, 157, 190, 132, 56, 97, 81, 254, 3, 159, 217, 66, 250, 162, 219, 97, 114, 235, }}, }, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851" + "fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, }, - wantVM: &virtualMachine{ - deferredCost: -143, - runLimit: 48976, - dataStack: [][]byte{{1}}, + wantVM: &VirtualMachine{ + DeferredCost: -143, + RunLimit: 48976, + DataStack: [][]byte{{1}}, }, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851" + "fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("badda7a7a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, }, - wantVM: &virtualMachine{ - deferredCost: -144, - runLimit: 48976, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + DeferredCost: -144, + RunLimit: 48976, + DataStack: [][]byte{{}}, }, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851" + "fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("bad220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, }, - wantVM: &virtualMachine{ - deferredCost: -144, - runLimit: 48976, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + DeferredCost: -144, + RunLimit: 48976, + DataStack: [][]byte{{}}, }, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("badabdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851" + "fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, }, - wantVM: &virtualMachine{ - deferredCost: -144, - runLimit: 48976, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + DeferredCost: -144, + RunLimit: 48976, + DataStack: [][]byte{{}}, }, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{}, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), }, @@ -242,9 +243,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851" + "fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("badbad"), @@ -254,15 +255,15 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrBadValue, }, { op: OP_CHECKSIG, - startVM: &virtualMachine{ - runLimit: 0, + startVM: &VirtualMachine{ + RunLimit: 0, }, wantErr: ErrRunLimitExceeded, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -270,16 +271,16 @@ func TestCryptoOps(t *testing.T) { {1}, }, }, - wantVM: &virtualMachine{ - deferredCost: -161, - runLimit: 48976, - dataStack: [][]byte{{1}}, + wantVM: &VirtualMachine{ + DeferredCost: -161, + RunLimit: 48976, + DataStack: [][]byte{{1}}, }, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("badabdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -287,23 +288,23 @@ func TestCryptoOps(t *testing.T) { {1}, }, }, - wantVM: &virtualMachine{ - deferredCost: -162, - runLimit: 48976, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + DeferredCost: -162, + RunLimit: 48976, + DataStack: [][]byte{{}}, }, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{}, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ {1}, {1}, }, @@ -311,9 +312,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), {1}, {1}, @@ -322,9 +323,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), {1}, @@ -334,9 +335,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("badbad"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -347,9 +348,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrBadValue, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -360,9 +361,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrBadValue, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -373,9 +374,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrBadValue, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -386,9 +387,9 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrBadValue, }, { op: OP_CHECKMULTISIG, - startVM: &virtualMachine{ - runLimit: 0, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 0, + DataStack: [][]byte{ mustDecodeHex("af5abdf4bbb34f4a089efc298234f84fd909def662a8df03b4d7d40372728851fbd3bf59920af5a7c361a4851967714271d1727e3be417a60053c30969d8860c"), mustDecodeHex("916f0027a575074ce72a331777c3478d6513f786a591bd892da1a577bf2335f9"), mustDecodeHex("ab3220d065dc875c6a5b4ecc39809b5f24eb0a605e9eef5190457edbf1e3b866"), @@ -399,16 +400,14 @@ func TestCryptoOps(t *testing.T) { wantErr: ErrRunLimitExceeded, }, { op: OP_TXSIGHASH, - startVM: &virtualMachine{ - runLimit: 50000, - tx: tx, - txContext: txContext(&tx.TxData, 0), - }, - wantVM: &virtualMachine{ - runLimit: 49704, - tx: tx, - txContext: txContext(&tx.TxData, 0), - dataStack: [][]byte{{ + startVM: &VirtualMachine{ + RunLimit: 50000, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1}, nil), + }, + wantVM: &VirtualMachine{ + RunLimit: 49704, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1}, nil), + DataStack: [][]byte{{ 47, 0, 60, 221, 100, 66, 123, 94, 237, 214, 204, 181, 133, 71, 2, 11, 2, 222, 242, 45, 197, 153, 126, 157, @@ -417,83 +416,75 @@ func TestCryptoOps(t *testing.T) { }, }, { op: OP_TXSIGHASH, - startVM: &virtualMachine{ - runLimit: 0, - tx: tx, - txContext: txContext(&tx.TxData, 0), + startVM: &VirtualMachine{ + RunLimit: 0, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1}, nil), }, wantErr: ErrRunLimitExceeded, - }, { - op: OP_TXSIGHASH, - startVM: &virtualMachine{ - runLimit: 50000, - tx: nil, - }, - wantErr: ErrContext, }, { op: OP_BLOCKHASH, - startVM: &virtualMachine{ - runLimit: 50000, - block: &bc.Block{}, + startVM: &VirtualMachine{ + RunLimit: 50000, + Context: bc.NewBlockVMContext(&bc.Block{}, nil, nil), }, - wantVM: &virtualMachine{ - runLimit: 49832, - dataStack: [][]byte{{ + 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, }}, - block: &bc.Block{}, + Context: bc.NewBlockVMContext(&bc.Block{}, nil, nil), }, }, { op: OP_BLOCKHASH, - startVM: &virtualMachine{ - runLimit: 0, - block: &bc.Block{}, + startVM: &VirtualMachine{ + RunLimit: 0, + Context: bc.NewBlockVMContext(&bc.Block{}, nil, nil), }, wantErr: ErrRunLimitExceeded, - }, { - op: OP_BLOCKHASH, - startVM: &virtualMachine{ - runLimit: 50000, - block: nil, - }, - wantErr: ErrContext, }} hashOps := []Op{OP_SHA256, OP_SHA3} for _, op := range hashOps { cases = append(cases, testStruct{ op: op, - startVM: &virtualMachine{ - runLimit: 0, - dataStack: [][]byte{{1}}, + startVM: &VirtualMachine{ + RunLimit: 0, + DataStack: [][]byte{{1}}, }, wantErr: ErrRunLimitExceeded, }, testStruct{ op: op, - startVM: &virtualMachine{ - runLimit: 50000, - dataStack: [][]byte{}, + startVM: &VirtualMachine{ + RunLimit: 50000, + DataStack: [][]byte{}, }, wantErr: ErrDataStackUnderflow, }) } for i, c := range cases { - err := ops[c.op].fn(c.startVM) + t.Logf("case %d", i) + + gotVM, err := CallOp(c.op, c.startVM) if err != c.wantErr { - t.Errorf("case %d, op %s: got err = %v want %v", i, ops[c.op].name, err, c.wantErr) + t.Errorf("case %d, op %s: got err = %v want %v", i, OpName(c.op), err, c.wantErr) continue } if c.wantErr != nil { continue } - if !testutil.DeepEqual(c.startVM, c.wantVM) { - t.Errorf("case %d, op %s: unexpected vm result\n\tgot: %+v\n\twant: %+v\n", i, ops[c.op].name, c.startVM, c.wantVM) + // Hack: the context objects will otherwise compare unequal + // sometimes (because of the function pointer within?) and we + // don't care + c.wantVM.Context = gotVM.Context + + if !testutil.DeepEqual(gotVM, c.wantVM) { + t.Errorf("case %d, op %s: unexpected vm result\n\tgot: %+v\n\twant: %+v\n", i, OpName(c.op), gotVM, c.wantVM) } } } @@ -505,15 +496,3 @@ func mustDecodeHex(h string) []byte { } return bits } - -func txContext(txData *bc.TxData, inputIndex int) (c bc.VMContext) { - // special case: no inputs, just use zero value for vm context - if len(txData.Inputs) == 0 { - return - } - hashes, err := bc.ComputeTxHashes(txData) - if err != nil { - panic(err) - } - return *hashes.VMContexts[inputIndex] -} diff --git a/protocol/vm/export_test.go b/protocol/vm/export_test.go new file mode 100644 index 0000000000..1ba20704bb --- /dev/null +++ b/protocol/vm/export_test.go @@ -0,0 +1,72 @@ +package vm + +var InitialRunLimit = initialRunLimit + +type VirtualMachine struct { + Program []byte + RunLimit int64 + DataStack [][]byte + DeferredCost int64 + Context *Context + PC uint32 + NextPC uint32 + Data []byte + ExpansionReserved bool +} + +func VMtovm(in *VirtualMachine) *virtualMachine { + dataStack := make([][]byte, len(in.DataStack)) + copy(dataStack, in.DataStack) + return &virtualMachine{ + program: in.Program, + runLimit: in.RunLimit, + dataStack: dataStack, + deferredCost: in.DeferredCost, + context: in.Context, + pc: in.PC, + nextPC: in.NextPC, + data: in.Data, + expansionReserved: in.ExpansionReserved, + } +} + +func VMfromvm(in *virtualMachine) *VirtualMachine { + return &VirtualMachine{ + Program: in.program, + RunLimit: in.runLimit, + DataStack: in.dataStack, + DeferredCost: in.deferredCost, + Context: in.context, + PC: in.pc, + NextPC: in.nextPC, + Data: in.data, + ExpansionReserved: in.expansionReserved, + } +} + +func (vm *VirtualMachine) Run() (*VirtualMachine, error) { + realVM := VMtovm(vm) + err := realVM.run() + return VMfromvm(realVM), err +} + +func (vm *VirtualMachine) Step() (*VirtualMachine, error) { + realVM := VMtovm(vm) + err := realVM.step() + return VMfromvm(realVM), err +} + +func (vm *VirtualMachine) FalseResult() bool { + realVM := VMtovm(vm) + return realVM.falseResult() +} + +func OpName(op Op) string { + return ops[op].name +} + +func CallOp(op Op, vm *VirtualMachine) (*VirtualMachine, error) { + realVM := VMtovm(vm) + err := ops[op].fn(realVM) + return VMfromvm(realVM), err +} diff --git a/protocol/vm/introspection.go b/protocol/vm/introspection.go index 715c17ba55..70828960bf 100644 --- a/protocol/vm/introspection.go +++ b/protocol/vm/introspection.go @@ -1,26 +1,14 @@ package vm -import ( - "bytes" - "fmt" - "math" - - "golang.org/x/crypto/sha3" - - "chain/protocol/bc" -) +import "math" func opCheckOutput(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - err := vm.applyCost(16) if err != nil { return err } - prog, err := vm.pop(true) + code, err := vm.pop(true) if err != nil { return err } @@ -50,207 +38,163 @@ func opCheckOutput(vm *virtualMachine) error { if err != nil { return err } - if index < 0 || int64(len(vm.tx.Outputs)) <= index { + if index < 0 { return ErrBadValue } - o := vm.tx.Outputs[index] - - if o.AssetVersion != 1 { - return vm.pushBool(false, true) - } - if o.Amount != uint64(amount) { - return vm.pushBool(false, true) - } - if o.VMVersion != uint64(vmVersion) { - return vm.pushBool(false, true) - } - if !bytes.Equal(o.ControlProgram, prog) { - return vm.pushBool(false, true) - } - if !bytes.Equal(o.AssetID[:], assetID) { - return vm.pushBool(false, true) + if vm.context.CheckOutput == nil { + return ErrContext } - if len(refdatahash) > 0 { - h := sha3.Sum256(o.ReferenceData) - if !bytes.Equal(h[:], refdatahash) { - return vm.pushBool(false, true) - } + + ok, err := vm.context.CheckOutput(uint64(index), refdatahash, uint64(amount), assetID, uint64(vmVersion), code) + if err != nil { + return err } - return vm.pushBool(true, true) + return vm.pushBool(ok, true) } func opAsset(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - err := vm.applyCost(1) if err != nil { return err } - assetID := vm.tx.Inputs[vm.inputIndex].AssetID() - return vm.push(assetID[:], true) -} - -func opAmount(vm *virtualMachine) error { - if vm.tx == nil { + if vm.context.AssetID == nil { return ErrContext } + return vm.push(*vm.context.AssetID, true) +} +func opAmount(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - amount := vm.tx.Inputs[vm.inputIndex].Amount() - return vm.pushInt64(int64(amount), true) -} - -func opProgram(vm *virtualMachine) error { - if vm.tx == nil { + if vm.context.Amount == nil { return ErrContext } + return vm.pushInt64(int64(*vm.context.Amount), true) +} +func opProgram(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - return vm.push(vm.mainprog, true) + return vm.push(vm.context.Code, true) } func opMinTime(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - err := vm.applyCost(1) if err != nil { return err } - return vm.pushInt64(int64(vm.tx.MinTime), true) -} - -func opMaxTime(vm *virtualMachine) error { - if vm.tx == nil { + if vm.context.MinTimeMS == nil { return ErrContext } + return vm.pushInt64(int64(*vm.context.MinTimeMS), true) +} +func opMaxTime(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - maxTime := vm.tx.MaxTime - if maxTime == 0 || maxTime > math.MaxInt64 { - maxTime = uint64(math.MaxInt64) + if vm.context.MaxTimeMS == nil { + return ErrContext + } + maxTimeMS := *vm.context.MaxTimeMS + if maxTimeMS == 0 || maxTimeMS > math.MaxInt64 { + maxTimeMS = uint64(math.MaxInt64) } - return vm.pushInt64(int64(maxTime), true) + return vm.pushInt64(int64(maxTimeMS), true) } func opRefDataHash(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - err := vm.applyCost(1) if err != nil { return err } - h := sha3.Sum256(vm.tx.Inputs[vm.inputIndex].ReferenceData) - return vm.push(h[:], true) -} - -func opTxRefDataHash(vm *virtualMachine) error { - if vm.tx == nil { + if vm.context.InputRefDataHash == nil { return ErrContext } + return vm.push(*vm.context.InputRefDataHash, true) +} +func opTxRefDataHash(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - h := sha3.Sum256(vm.tx.ReferenceData) - return vm.push(h[:], true) -} - -func opIndex(vm *virtualMachine) error { - if vm.tx == nil { + if vm.context.TxRefDataHash == nil { return ErrContext } + return vm.push(*vm.context.TxRefDataHash, true) +} +func opIndex(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - return vm.pushInt64(int64(vm.inputIndex), true) -} - -func opOutputID(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - - outid := vm.txContext.OutputID - if outid == nil { + if vm.context.InputIndex == nil { return ErrContext } + return vm.pushInt64(int64(*vm.context.InputIndex), true) +} +func opOutputID(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - return vm.push(outid[:], true) -} - -func opNonce(vm *virtualMachine) error { - if vm.tx == nil { - return ErrContext - } - - txin := vm.tx.Inputs[vm.inputIndex] - ii, ok := txin.TypedInput.(*bc.IssuanceInput) - if !ok { + if vm.context.SpentOutputID == nil { return ErrContext } + return vm.push(*vm.context.SpentOutputID, true) +} +func opNonce(vm *virtualMachine) error { err := vm.applyCost(1) if err != nil { return err } - return vm.push(ii.Nonce, true) + if vm.context.Nonce == nil { + return ErrContext + } + return vm.push(*vm.context.Nonce, true) } func opNextProgram(vm *virtualMachine) error { - if vm.block == nil { - return ErrContext - } err := vm.applyCost(1) if err != nil { return err } - return vm.push(vm.block.ConsensusProgram, true) -} -func opBlockTime(vm *virtualMachine) error { - if vm.block == nil { + 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.block.TimestampMS > math.MaxInt64 { - return fmt.Errorf("block timestamp out of range") + + if vm.context.BlockTimeMS == nil { + return ErrContext } - return vm.pushInt64(int64(vm.block.TimestampMS), true) + return vm.pushInt64(int64(*vm.context.BlockTimeMS), true) } diff --git a/protocol/vm/introspection_test.go b/protocol/vm/introspection_test.go index 9c22043090..7354fb3bf1 100644 --- a/protocol/vm/introspection_test.go +++ b/protocol/vm/introspection_test.go @@ -1,9 +1,13 @@ -package vm +package vm_test import ( "testing" + "github.com/davecgh/go-spew/spew" + + "chain/errors" "chain/protocol/bc" + . "chain/protocol/vm" "chain/testutil" ) @@ -19,12 +23,11 @@ func TestNextProgram(t *testing.T) { if err != nil { t.Fatal(err) } - vm := &virtualMachine{ - runLimit: 50000, - block: block, - program: prog, + vm := &VirtualMachine{ + RunLimit: 50000, + Context: bc.NewBlockVMContext(block, prog, nil), } - err = vm.run() + _, err = vm.Run() if err != nil { t.Errorf("got error %s, expected none", err) } @@ -33,13 +36,12 @@ func TestNextProgram(t *testing.T) { if err != nil { t.Fatal(err) } - vm = &virtualMachine{ - runLimit: 50000, - block: block, - program: prog, + vm = &VirtualMachine{ + RunLimit: 50000, + Context: bc.NewBlockVMContext(block, prog, nil), } - err = vm.run() - if err == nil && vm.falseResult() { + _, err = vm.Run() + if err == nil && vm.FalseResult() { err = ErrFalseVMResult } switch err { @@ -62,12 +64,12 @@ func TestBlockTime(t *testing.T) { if err != nil { t.Fatal(err) } - vm := &virtualMachine{ - runLimit: 50000, - block: block, - program: prog, + vm := &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewBlockVMContext(block, prog, nil), } - err = vm.run() + _, err = vm.Run() if err != nil { t.Errorf("got error %s, expected none", err) } @@ -76,13 +78,13 @@ func TestBlockTime(t *testing.T) { if err != nil { t.Fatal(err) } - vm = &virtualMachine{ - runLimit: 50000, - block: block, - program: prog, + vm = &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewBlockVMContext(block, prog, nil), } - err = vm.run() - if err == nil && vm.falseResult() { + _, err = vm.Run() + if err == nil && vm.FalseResult() { err = ErrFalseVMResult } switch err { @@ -108,57 +110,57 @@ func TestOutputIDAndNonceOp(t *testing.T) { if err != nil { t.Fatal(err) } - vm := &virtualMachine{ - runLimit: 50000, - tx: tx, - txContext: txContext(&tx.TxData, 0), - inputIndex: 0, - program: []byte{uint8(OP_OUTPUTID)}, + prog := []byte{uint8(OP_OUTPUTID)} + vm := &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1, Code: prog}, nil), } - err = vm.step() + gotVM, err := vm.Step() if err != nil { t.Fatal(err) } expectedStack := [][]byte{outputID[:]} - if !testutil.DeepEqual(vm.dataStack, expectedStack) { - t.Errorf("expected stack %v, got %v", expectedStack, vm.dataStack) + if !testutil.DeepEqual(gotVM.DataStack, expectedStack) { + t.Errorf("expected stack %v, got %v; vm is:\n%s", expectedStack, gotVM.DataStack, spew.Sdump(vm)) } - vm = &virtualMachine{ - runLimit: 50000, - tx: tx, - inputIndex: 1, - program: []byte{uint8(OP_OUTPUTID)}, + prog = []byte{uint8(OP_OUTPUTID)} + vm = &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewTxVMContext(tx, 1, bc.Program{VMVersion: 1, Code: prog}, nil), } - err = vm.step() + _, err = vm.Step() if err != ErrContext { t.Errorf("expected ErrContext, got %v", err) } - vm = &virtualMachine{ - runLimit: 50000, - tx: tx, - inputIndex: 0, - program: []byte{uint8(OP_NONCE)}, + prog = []byte{uint8(OP_NONCE)} + vm = &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1, Code: prog}, nil), } - err = vm.step() + _, err = vm.Step() if err != ErrContext { t.Errorf("expected ErrContext, got %v", err) } - vm = &virtualMachine{ - runLimit: 50000, - tx: tx, - inputIndex: 1, - program: []byte{uint8(OP_NONCE)}, + + prog = []byte{uint8(OP_NONCE)} + vm = &VirtualMachine{ + RunLimit: 50000, + Program: prog, + Context: bc.NewTxVMContext(tx, 1, bc.Program{VMVersion: 1, Code: prog}, nil), } - err = vm.step() + gotVM, err = vm.Step() if err != nil { t.Fatal(err) } expectedStack = [][]byte{nonce} - if !testutil.DeepEqual(vm.dataStack, expectedStack) { - t.Errorf("expected stack %v, got %v", expectedStack, vm.dataStack) + if !testutil.DeepEqual(gotVM.DataStack, expectedStack) { + t.Errorf("expected stack %v, got %v", expectedStack, gotVM.DataStack) } } @@ -180,17 +182,18 @@ func TestIntrospectionOps(t *testing.T) { MaxTime: 20, }) + context0 := bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1}, nil) + type testStruct struct { op Op - startVM *virtualMachine + startVM *VirtualMachine wantErr error - wantVM *virtualMachine + wantVM *VirtualMachine } cases := []testStruct{{ op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {4}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -198,18 +201,18 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 50101, - deferredCost: -117, - tx: tx, - dataStack: [][]byte{{1}}, + wantVM: &VirtualMachine{ + RunLimit: 50101, + DeferredCost: -117, + DataStack: [][]byte{{1}}, + Context: context0, }, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {3}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -217,18 +220,18 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 50102, - deferredCost: -118, - tx: tx, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + RunLimit: 50102, + DeferredCost: -118, + DataStack: [][]byte{{}}, + Context: context0, }, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {0}, []byte{}, {1}, @@ -236,82 +239,69 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("missingprog"), }, + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 50070, - deferredCost: -86, - tx: tx, - dataStack: [][]byte{{}}, + wantVM: &VirtualMachine{ + RunLimit: 50070, + DeferredCost: -86, + DataStack: [][]byte{{}}, + Context: context0, }, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - dataStack: [][]byte{ - {0}, - mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), - {7}, - append([]byte{2}, make([]byte, 31)...), - {1}, - []byte("controlprog"), - }, - }, - wantErr: ErrContext, - }, { - op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ []byte("controlprog"), }, + Context: context0, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ append([]byte{2}, make([]byte, 31)...), {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {7}, append([]byte{2}, make([]byte, 31)...), {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, append([]byte{2}, make([]byte, 31)...), {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrDataStackUnderflow, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {4}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -319,13 +309,13 @@ func TestIntrospectionOps(t *testing.T) { Int64Bytes(-1), []byte("controlprog"), }, + Context: context0, }, wantErr: ErrBadValue, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {4}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), Int64Bytes(-1), @@ -333,13 +323,13 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrBadValue, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ Int64Bytes(-1), mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -347,13 +337,13 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrBadValue, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + DataStack: [][]byte{ {5}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -361,14 +351,14 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrBadValue, }, { op: OP_CHECKOUTPUT, - startVM: &virtualMachine{ - runLimit: 0, - tx: tx, - dataStack: [][]byte{ + startVM: &VirtualMachine{ + RunLimit: 0, + DataStack: [][]byte{ {4}, mustDecodeHex("1f2a05f881ed9fa0c9068a84823677409f863891a2196eb55dbfbb677a566374"), {7}, @@ -376,117 +366,116 @@ func TestIntrospectionOps(t *testing.T) { {1}, []byte("controlprog"), }, + Context: context0, }, wantErr: ErrRunLimitExceeded, }, { op: OP_ASSET, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49959, - deferredCost: 40, - dataStack: [][]byte{append([]byte{1}, make([]byte, 31)...)}, - tx: tx, + wantVM: &VirtualMachine{ + RunLimit: 49959, + DeferredCost: 40, + DataStack: [][]byte{append([]byte{1}, make([]byte, 31)...)}, + Context: context0, }, }, { op: OP_AMOUNT, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49990, - deferredCost: 9, - dataStack: [][]byte{{5}}, - tx: tx, + wantVM: &VirtualMachine{ + RunLimit: 49990, + DeferredCost: 9, + DataStack: [][]byte{{5}}, + Context: context0, }, }, { op: OP_PROGRAM, - startVM: &virtualMachine{ - mainprog: []byte("spendprog"), - tx: tx, + startVM: &VirtualMachine{ + Program: []byte("spendprog"), + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1, Code: []byte("spendprog")}, nil), }, - wantVM: &virtualMachine{ - runLimit: 49982, - deferredCost: 17, - dataStack: [][]byte{[]byte("spendprog")}, - tx: tx, + wantVM: &VirtualMachine{ + RunLimit: 49982, + DeferredCost: 17, + DataStack: [][]byte{[]byte("spendprog")}, + Context: bc.NewTxVMContext(tx, 0, bc.Program{VMVersion: 1, Code: []byte("spendprog")}, nil), }, }, { op: OP_PROGRAM, - startVM: &virtualMachine{ - mainprog: []byte("issueprog"), - runLimit: 50000, - tx: tx, - inputIndex: 1, - }, - wantVM: &virtualMachine{ - runLimit: 49982, - deferredCost: 17, - dataStack: [][]byte{[]byte("issueprog")}, - tx: tx, - inputIndex: 1, + startVM: &VirtualMachine{ + Program: []byte("issueprog"), + RunLimit: 50000, + Context: bc.NewTxVMContext(tx, 1, bc.Program{VMVersion: 1, Code: []byte("issueprog")}, nil), + }, + wantVM: &VirtualMachine{ + RunLimit: 49982, + DeferredCost: 17, + DataStack: [][]byte{[]byte("issueprog")}, + Context: bc.NewTxVMContext(tx, 1, bc.Program{VMVersion: 1, Code: []byte("issueprog")}, nil), }, }, { op: OP_MINTIME, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49991, - deferredCost: 8, - tx: tx, - dataStack: [][]byte{[]byte{}}, + wantVM: &VirtualMachine{ + RunLimit: 49991, + DeferredCost: 8, + DataStack: [][]byte{[]byte{}}, + Context: context0, }, }, { op: OP_MAXTIME, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49990, - deferredCost: 9, - dataStack: [][]byte{{20}}, - tx: tx, + wantVM: &VirtualMachine{ + RunLimit: 49990, + DeferredCost: 9, + DataStack: [][]byte{{20}}, + Context: context0, }, }, { op: OP_TXREFDATAHASH, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49959, - deferredCost: 40, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49959, + DeferredCost: 40, + DataStack: [][]byte{{ 62, 81, 144, 242, 105, 30, 109, 69, 28, 80, 237, 249, 169, 166, 106, 122, 103, 121, 199, 135, 103, 100, 82, 129, 13, 191, 79, 110, 64, 83, 104, 44, }}, - tx: tx, + Context: context0, }, }, { op: OP_REFDATAHASH, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49959, - deferredCost: 40, - dataStack: [][]byte{{ + wantVM: &VirtualMachine{ + RunLimit: 49959, + DeferredCost: 40, + DataStack: [][]byte{{ 68, 190, 94, 20, 206, 33, 111, 75, 44, 53, 165, 235, 11, 53, 208, 120, 189, 165, 92, 240, 91, 93, 54, 238, 14, 122, 1, 251, 198, 239, 98, 183, }}, - tx: tx, + Context: context0, }, }, { op: OP_INDEX, - startVM: &virtualMachine{ - tx: tx, + startVM: &VirtualMachine{ + Context: context0, }, - wantVM: &virtualMachine{ - runLimit: 49991, - deferredCost: 8, - tx: tx, - dataStack: [][]byte{[]byte{}}, + wantVM: &VirtualMachine{ + RunLimit: 49991, + DeferredCost: 8, + DataStack: [][]byte{[]byte{}}, + Context: context0, }, }} @@ -499,51 +488,42 @@ func TestIntrospectionOps(t *testing.T) { for _, op := range txops { cases = append(cases, testStruct{ op: op, - startVM: &virtualMachine{ - runLimit: 0, - tx: tx, - txContext: bc.VMContext{ - OutputID: &bc.Hash{}, - }, + startVM: &VirtualMachine{ + RunLimit: 0, + Context: context0, }, wantErr: ErrRunLimitExceeded, - }, testStruct{ - op: op, - startVM: &virtualMachine{ - tx: nil, - }, - wantErr: ErrContext, }) } for i, c := range cases { + t.Logf("case %d", i) prog := []byte{byte(c.op)} vm := c.startVM if c.wantErr != ErrRunLimitExceeded { - vm.runLimit = 50000 - } - if vm.mainprog == nil { - vm.mainprog = prog + vm.RunLimit = 50000 } - vm.program = prog - err := vm.run() - switch err { + vm.Program = prog + gotVM, err := vm.Run() + switch errors.Root(err) { case c.wantErr: // ok case nil: - t.Errorf("case %d, op %s: got no error, want %v", i, ops[c.op].name, c.wantErr) + t.Errorf("case %d, op %s: got no error, want %v", i, OpName(c.op), c.wantErr) default: - t.Errorf("case %d, op %s: got err = %v want %v", i, ops[c.op].name, err, c.wantErr) + t.Errorf("case %d, op %s: got err = %v want %v", i, OpName(c.op), err, c.wantErr) } if c.wantErr != nil { continue } - c.wantVM.mainprog = vm.mainprog - c.wantVM.program = prog - c.wantVM.pc = 1 - c.wantVM.nextPC = 1 - if !testutil.DeepEqual(vm, c.wantVM) { - t.Errorf("case %d, op %s: unexpected vm result\n\tgot: %+v\n\twant: %+v\n", i, ops[c.op].name, c.startVM, c.wantVM) + + c.wantVM.Program = prog + c.wantVM.PC = 1 + c.wantVM.NextPC = 1 + c.wantVM.Context = gotVM.Context + + if !testutil.DeepEqual(gotVM, c.wantVM) { + t.Errorf("case %d, op %s: unexpected vm result\n\tgot: %+v\n\twant: %+v\nstartVM is:\n%s", i, OpName(c.op), gotVM, c.wantVM, spew.Sdump(c.startVM)) } } } diff --git a/protocol/vm/vm.go b/protocol/vm/vm.go index 6614a60e02..4d1789dbe4 100644 --- a/protocol/vm/vm.go +++ b/protocol/vm/vm.go @@ -6,16 +6,15 @@ import ( "io" "strings" - // TODO(bobg): very little of this package depends on bc, consider trying to remove the dependency "chain/errors" - "chain/protocol/bc" ) const initialRunLimit = 10000 type virtualMachine struct { + context *Context + program []byte // the program currently executing - mainprog []byte // the outermost program, returned by OP_PROGRAM pc, nextPC uint32 runLimit int64 deferredCost int64 @@ -32,12 +31,6 @@ type virtualMachine struct { // In each of these stacks, stack[len(stack)-1] is the top element. dataStack [][]byte altStack [][]byte - - tx *bc.Tx - txContext bc.VMContext - inputIndex uint32 - - block *bc.Block } // ErrFalseVMResult is one of the ways for a transaction to fail validation @@ -47,94 +40,38 @@ var ErrFalseVMResult = errors.New("false VM result") // execution. var TraceOut io.Writer -func VerifyTxInput(tx *bc.Tx, inputIndex uint32) (err error) { +func Verify(context *Context) (err error) { defer func() { if panErr := recover(); panErr != nil { err = ErrUnexpected } }() - return verifyTxInput(tx, inputIndex) -} -func verifyTxInput(tx *bc.Tx, inputIndex uint32) error { - if inputIndex < 0 || inputIndex >= uint32(len(tx.Inputs)) { - return ErrBadValue + if context.VMVersion != 1 { + return ErrUnsupportedVM } - txinput := tx.Inputs[inputIndex] - - expansionReserved := tx.Version == 1 - - f := func(vmversion uint64, prog []byte, args [][]byte) error { - if vmversion != 1 { - return ErrUnsupportedVM - } - - vm := virtualMachine{ - tx: tx, - txContext: *tx.VMContexts[inputIndex], - inputIndex: inputIndex, - - expansionReserved: expansionReserved, - - mainprog: prog, - program: prog, - runLimit: initialRunLimit, - } - for _, arg := range args { - err := vm.push(arg, false) - if err != nil { - return err - } - } - err := vm.run() - if err == nil && vm.falseResult() { - err = ErrFalseVMResult - } - return wrapErr(err, &vm, args) + vm := &virtualMachine{ + expansionReserved: context.TxVersion != nil && *context.TxVersion == 1, + program: context.Code, + runLimit: initialRunLimit, + context: context, } - switch inp := txinput.TypedInput.(type) { - case *bc.IssuanceInput: - return f(inp.VMVersion, inp.IssuanceProgram, inp.Arguments) - case *bc.SpendInput: - return f(inp.VMVersion, inp.ControlProgram, inp.Arguments) - } - return errors.WithDetailf(ErrUnsupportedTx, "transaction input %d has unknown type %T", inputIndex, txinput.TypedInput) -} - -func VerifyBlockHeader(prev *bc.BlockHeader, block *bc.Block) (err error) { - defer func() { - if panErr := recover(); panErr != nil { - err = ErrUnexpected - } - }() - return verifyBlockHeader(prev, block) -} - -func verifyBlockHeader(prev *bc.BlockHeader, block *bc.Block) error { - vm := virtualMachine{ - block: block, - - expansionReserved: true, - - mainprog: prev.ConsensusProgram, - program: prev.ConsensusProgram, - runLimit: initialRunLimit, - } - - for _, arg := range block.Witness { - err := vm.push(arg, false) + args := context.Arguments + for i, arg := range args { + err = vm.push(arg, false) if err != nil { - return err + return errors.Wrapf(err, "pushing initial argument %d", i) } } - err := vm.run() + err = vm.run() if err == nil && vm.falseResult() { err = ErrFalseVMResult } - return wrapErr(err, &vm, block.Witness) + + return wrapErr(err, vm, args) } // falseResult returns true iff the stack is empty or the top diff --git a/protocol/vm/vm_test.go b/protocol/vm/vm_test.go index 32dd1bfab6..4ed3a4d3db 100644 --- a/protocol/vm/vm_test.go +++ b/protocol/vm/vm_test.go @@ -1,4 +1,4 @@ -package vm +package vm_test import ( "bytes" @@ -10,6 +10,7 @@ import ( "chain/errors" "chain/protocol/bc" + . "chain/protocol/vm" "chain/testutil" ) @@ -152,13 +153,13 @@ func doOKNotOK(t *testing.T, expectOK bool) { fmt.Printf("* case %d, prog [%s] [%x]\n", i, progSrc, prog) trace := new(tracebuf) TraceOut = trace - vm := &virtualMachine{ - program: prog, - runLimit: initialRunLimit, - dataStack: append([][]byte{}, c.args...), + vm := &VirtualMachine{ + Program: prog, + RunLimit: int64(InitialRunLimit), + DataStack: append([][]byte{}, c.args...), } - err = vm.run() - if err == nil && vm.falseResult() { + gotVM, err := vm.Run() + if err == nil && gotVM.FalseResult() { err = ErrFalseVMResult } if expectOK && err != nil { @@ -233,7 +234,7 @@ func TestVerifyTxInput(t *testing.T) { Inputs: []*bc.TxInput{c.input}, }) - gotErr := VerifyTxInput(tx, 0) + gotErr := verifyTx(tx, 0) if errors.Root(gotErr) != c.wantErr { t.Errorf("VerifyTxInput(%d) err = %v want %v", i, gotErr, c.wantErr) @@ -257,7 +258,7 @@ func TestVerifyBlockHeader(t *testing.T) { }, } - gotErr := VerifyBlockHeader(&prevBlock.BlockHeader, block) + gotErr := verifyBlock(&prevBlock.BlockHeader, block) if gotErr != nil { t.Errorf("unexpected error: %v", gotErr) } @@ -270,7 +271,7 @@ func TestVerifyBlockHeader(t *testing.T) { }, } - gotErr = VerifyBlockHeader(&prevBlock.BlockHeader, block) + gotErr = verifyBlock(&prevBlock.BlockHeader, block) if errors.Root(gotErr) != ErrRunLimitExceeded { t.Error("expected block to exceed run limit") } @@ -278,17 +279,17 @@ func TestVerifyBlockHeader(t *testing.T) { func TestRun(t *testing.T) { cases := []struct { - vm *virtualMachine + vm *VirtualMachine wantErr error }{{ - vm: &virtualMachine{runLimit: 50000, program: []byte{byte(OP_TRUE)}}, + vm: &VirtualMachine{RunLimit: 50000, Program: []byte{byte(OP_TRUE)}}, }, { - vm: &virtualMachine{runLimit: 50000, program: []byte{byte(OP_ADD)}}, + vm: &VirtualMachine{RunLimit: 50000, Program: []byte{byte(OP_ADD)}}, wantErr: ErrDataStackUnderflow, }} for i, c := range cases { - gotErr := c.vm.run() + _, gotErr := c.vm.Run() if gotErr != c.wantErr { t.Errorf("run test %d: got err = %v want %v", i, gotErr, c.wantErr) @@ -302,119 +303,125 @@ func TestRun(t *testing.T) { } func TestStep(t *testing.T) { + tx := bc.NewTx(bc.TxData{ + Version: 1, + Inputs: []*bc.TxInput{ + bc.NewSpendInput(nil, bc.Hash{}, bc.AssetID{}, 1, 0, nil, bc.Hash{}, nil), + }, + }) cases := []struct { - startVM *virtualMachine - wantVM *virtualMachine + startVM *VirtualMachine + wantVM *VirtualMachine wantErr error }{{ - startVM: &virtualMachine{ - program: []byte{byte(OP_TRUE)}, - runLimit: 50000, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE)}, + RunLimit: 50000, }, - wantVM: &virtualMachine{ - program: []byte{byte(OP_TRUE)}, - runLimit: 49990, - dataStack: [][]byte{{1}}, - pc: 1, - nextPC: 1, - data: []byte{1}, + wantVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE)}, + RunLimit: 49990, + DataStack: [][]byte{{1}}, + PC: 1, + NextPC: 1, + Data: []byte{1}, }, }, { - startVM: &virtualMachine{ - program: []byte{byte(OP_TRUE), byte(OP_JUMP), byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 49990, - dataStack: [][]byte{}, - pc: 1, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE), byte(OP_JUMP), byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 49990, + DataStack: [][]byte{}, + PC: 1, }, - wantVM: &virtualMachine{ - program: []byte{byte(OP_TRUE), byte(OP_JUMP), byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 49989, - dataStack: [][]byte{}, - data: []byte{byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, - pc: 255, - nextPC: 255, - deferredCost: 0, + wantVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE), byte(OP_JUMP), byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 49989, + DataStack: [][]byte{}, + Data: []byte{byte(0xff), byte(0x00), byte(0x00), byte(0x00)}, + PC: 255, + NextPC: 255, + DeferredCost: 0, }, }, { - startVM: &virtualMachine{ - program: []byte{byte(OP_TRUE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 49995, - dataStack: [][]byte{{1}}, - pc: 1, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 49995, + DataStack: [][]byte{{1}}, + PC: 1, }, - wantVM: &virtualMachine{ - program: []byte{byte(OP_TRUE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 50003, - dataStack: [][]byte{}, - pc: 0, - nextPC: 0, - data: []byte{byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - deferredCost: -9, + wantVM: &VirtualMachine{ + Program: []byte{byte(OP_TRUE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 50003, + DataStack: [][]byte{}, + PC: 0, + NextPC: 0, + Data: []byte{byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + DeferredCost: -9, }, }, { - startVM: &virtualMachine{ - program: []byte{byte(OP_FALSE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 49995, - dataStack: [][]byte{{}}, - pc: 1, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_FALSE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 49995, + DataStack: [][]byte{{}}, + PC: 1, }, - wantVM: &virtualMachine{ - program: []byte{byte(OP_FALSE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - runLimit: 50002, - dataStack: [][]byte{}, - pc: 6, - nextPC: 6, - data: []byte{byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, - deferredCost: -8, + wantVM: &VirtualMachine{ + Program: []byte{byte(OP_FALSE), byte(OP_JUMPIF), byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + RunLimit: 50002, + DataStack: [][]byte{}, + PC: 6, + NextPC: 6, + Data: []byte{byte(0x00), byte(0x00), byte(0x00), byte(0x00)}, + DeferredCost: -8, }, }, { - startVM: &virtualMachine{ - program: []byte{255}, - runLimit: 50000, - dataStack: [][]byte{}, + startVM: &VirtualMachine{ + Program: []byte{255}, + RunLimit: 50000, + DataStack: [][]byte{}, }, - wantVM: &virtualMachine{ - program: []byte{255}, - runLimit: 49999, - pc: 1, - nextPC: 1, - dataStack: [][]byte{}, + wantVM: &VirtualMachine{ + Program: []byte{255}, + RunLimit: 49999, + PC: 1, + NextPC: 1, + DataStack: [][]byte{}, }, }, { - startVM: &virtualMachine{ - program: []byte{byte(OP_ADD)}, - runLimit: 50000, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_ADD)}, + RunLimit: 50000, }, wantErr: ErrDataStackUnderflow, }, { - startVM: &virtualMachine{ - program: []byte{byte(OP_INDEX)}, - runLimit: 1, - tx: &bc.Tx{}, + startVM: &VirtualMachine{ + Program: []byte{byte(OP_INDEX)}, + RunLimit: 1, + Context: bc.NewTxVMContext(tx, 0, bc.Program{}, nil), }, wantErr: ErrRunLimitExceeded, }, { - startVM: &virtualMachine{ - program: []byte{255}, - runLimit: 100, - expansionReserved: true, + startVM: &VirtualMachine{ + Program: []byte{255}, + RunLimit: 100, + ExpansionReserved: true, }, wantErr: ErrDisallowedOpcode, }, { - startVM: &virtualMachine{ - program: []byte{255}, - runLimit: 100, + startVM: &VirtualMachine{ + Program: []byte{255}, + RunLimit: 100, }, - wantVM: &virtualMachine{ - program: []byte{255}, - runLimit: 99, - pc: 1, - nextPC: 1, + wantVM: &VirtualMachine{ + Program: []byte{255}, + RunLimit: 99, + PC: 1, + NextPC: 1, }, }} for i, c := range cases { - gotErr := c.startVM.step() + gotVM, gotErr := c.startVM.Step() if gotErr != c.wantErr { t.Errorf("step test %d: got err = %v want %v", i, gotErr, c.wantErr) @@ -425,8 +432,8 @@ func TestStep(t *testing.T) { continue } - if !testutil.DeepEqual(c.startVM, c.wantVM) { - t.Errorf("step test %d:\n\tgot vm: %+v\n\twant vm: %+v", i, c.startVM, c.wantVM) + if !testutil.DeepEqual(gotVM, c.wantVM) { + t.Errorf("step test %d:\n\tgot vm: %+v\n\twant vm: %+v", i, gotVM, c.wantVM) } } } @@ -467,7 +474,7 @@ func TestVerifyTxInputQuickCheck(t *testing.T) { tx := bc.NewTx(bc.TxData{ Inputs: []*bc.TxInput{bc.NewSpendInput(witnesses, bc.Hash{}, bc.AssetID{}, 10, 0, program, bc.Hash{}, nil)}, }) - verifyTxInput(tx, 0) + verifyTx(tx, 0) return true } if err := quick.Check(f, nil); err != nil { @@ -497,10 +504,33 @@ func TestVerifyBlockHeaderQuickCheck(t *testing.T) { Witness: witnesses, }, }} - verifyBlockHeader(prev, block) + verifyBlock(prev, block) return true } if err := quick.Check(f, nil); err != nil { t.Error(err) } } + +func verifyTx(tx *bc.Tx, index uint32) error { + var ( + prog []byte + args [][]byte + vmver uint64 + ) + switch inp := tx.Inputs[index].TypedInput.(type) { + case *bc.IssuanceInput: + prog = inp.IssuanceProgram + args = inp.Arguments + vmver = inp.VMVersion + case *bc.SpendInput: + prog = inp.ControlProgram + args = inp.Arguments + vmver = inp.VMVersion + } + return Verify(bc.NewTxVMContext(tx, index, bc.Program{VMVersion: vmver, Code: prog}, args)) +} + +func verifyBlock(prev *bc.BlockHeader, block *bc.Block) error { + return Verify(bc.NewBlockVMContext(block, prev.ConsensusProgram, block.Witness)) +}