From 418805aed3b0a06cb1c89390a87a41652a8d3629 Mon Sep 17 00:00:00 2001 From: Svarog Date: Sun, 30 Dec 2018 18:16:41 +0200 Subject: [PATCH] [DEV-314] improved txscript coverage (#137) * [DEV-314] Added tests for DisasmPC and DisasmScript * [DEV-314] Re-wrote TestCheckErrorCondition to cover the whole method * [DEV-314] Fixed error message --- txscript/engine.go | 5 +- txscript/engine_test.go | 255 +++++++++++++++++++++++++++++++--------- txscript/main_test.go | 15 +++ 3 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 txscript/main_test.go diff --git a/txscript/engine.go b/txscript/engine.go index f7b57e854..d631e606e 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -177,6 +177,10 @@ func (vm *Engine) DisasmPC() (string, error) { // offset index. Index 0 is the signature script and 1 is the public key // script. func (vm *Engine) DisasmScript(idx int) (string, error) { + if idx < 0 { + str := fmt.Sprintf("script index %d < 0", idx) + return "", scriptError(ErrInvalidIndex, str) + } if idx >= len(vm.scripts) { str := fmt.Sprintf("script index %d >= total scripts %d", idx, len(vm.scripts)) @@ -203,7 +207,6 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error { if finalScript { if vm.dstack.Depth() > 1 { - str := fmt.Sprintf("stack contains %d unexpected items", vm.dstack.Depth()-1) return scriptError(ErrCleanStack, str) diff --git a/txscript/engine_test.go b/txscript/engine_test.go index c0cefd627..b423edd20 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -7,6 +7,7 @@ package txscript import ( "testing" + "bou.ke/monkey" "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/wire" ) @@ -77,71 +78,88 @@ func TestBadPC(t *testing.T) { } } -// TestCheckErrorCondition tests the execute early test in CheckErrorCondition() -// since most code paths are tested elsewhere. func TestCheckErrorCondition(t *testing.T) { t.Parallel() - // tx with almost empty scripts. - tx := &wire.MsgTx{ - Version: 1, - TxIn: []*wire.TxIn{{ - PreviousOutPoint: wire.OutPoint{ - Hash: daghash.Hash([32]byte{ - 0xc9, 0x97, 0xa5, 0xe5, - 0x6e, 0x10, 0x41, 0x02, - 0xfa, 0x20, 0x9c, 0x6a, - 0x85, 0x2d, 0xd9, 0x06, - 0x60, 0xa2, 0x0b, 0x2d, - 0x9c, 0x35, 0x24, 0x23, - 0xed, 0xce, 0x25, 0x85, - 0x7f, 0xcd, 0x37, 0x04, - }), - Index: 0, - }, - SignatureScript: nil, - Sequence: 4294967295, - }}, - TxOut: []*wire.TxOut{{ - Value: 1000000000, - PkScript: nil, - }}, - LockTime: 0, - } - pkScript := mustParseShortForm("NOP NOP NOP NOP NOP NOP NOP NOP NOP" + - " NOP TRUE") - - vm, err := NewEngine(pkScript, tx, 0, 0, nil) - if err != nil { - t.Errorf("failed to create script: %v", err) + tests := []struct { + script string + finalScript bool + stepCount int + source interface{} + replacement interface{} + expectedErr error + }{ + {"OP_1", true, 1, nil, nil, nil}, + {"NOP", true, 0, nil, nil, scriptError(ErrScriptUnfinished, "")}, + {"NOP", true, 1, nil, nil, scriptError(ErrEmptyStack, "")}, + {"OP_1 OP_1", true, 2, nil, nil, scriptError(ErrCleanStack, "")}, + {"OP_0", true, 1, nil, nil, scriptError(ErrEvalFalse, "")}, + {"OP_1", true, 1, (*stack).PopBool, + func(*stack) (bool, error) { return false, scriptError(ErrInvalidStackOperation, "") }, + scriptError(ErrInvalidStackOperation, "")}, } - for i := 0; i < len(pkScript)-1; i++ { - done, err := vm.Step() - if err != nil { - t.Fatalf("failed to step %dth time: %v", i, err) - } - if done { - t.Fatalf("finshed early on %dth time", i) - } + for i, test := range tests { + func() { + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: daghash.Hash([32]byte{ + 0xc9, 0x97, 0xa5, 0xe5, + 0x6e, 0x10, 0x41, 0x02, + 0xfa, 0x20, 0x9c, 0x6a, + 0x85, 0x2d, 0xd9, 0x06, + 0x60, 0xa2, 0x0b, 0x2d, + 0x9c, 0x35, 0x24, 0x23, + 0xed, 0xce, 0x25, 0x85, + 0x7f, 0xcd, 0x37, 0x04, + }), + Index: 0, + }, + SignatureScript: nil, + Sequence: 4294967295, + }}, + TxOut: []*wire.TxOut{{ + Value: 1000000000, + PkScript: nil, + }}, + LockTime: 0, + } + pkScript := mustParseShortForm(test.script) - err = vm.CheckErrorCondition(false) - if !IsErrorCode(err, ErrScriptUnfinished) { - t.Fatalf("got unexepected error %v on %dth iteration", - err, i) - } - } - done, err := vm.Step() - if err != nil { - t.Fatalf("final step failed %v", err) - } - if !done { - t.Fatalf("final step isn't done!") - } + vm, err := NewEngine(pkScript, tx, 0, 0, nil) + if err != nil { + t.Errorf("TestCheckErrorCondition: %d: failed to create script: %v", i, err) + } - err = vm.CheckErrorCondition(false) - if err != nil { - t.Errorf("unexpected error %v on final check", err) + for j := 0; j < test.stepCount; j++ { + _, err = vm.Step() + if err != nil { + t.Errorf("TestCheckErrorCondition: %d: failed to execute step No. %d: %v", i, j+1, err) + return + } + + if j != test.stepCount-1 { + err = vm.CheckErrorCondition(false) + if !IsErrorCode(err, ErrScriptUnfinished) { + t.Fatalf("TestCheckErrorCondition: %d: got unexepected error %v on %dth iteration", + i, err, j) + return + } + } + } + + if test.source != nil { + patch := monkey.Patch(test.source, test.replacement) + defer patch.Unpatch() + } + + err = vm.CheckErrorCondition(test.finalScript) + if e := tstCheckScriptError(err, test.expectedErr); e != nil { + t.Errorf("TestCheckErrorCondition: %d: %s", i, e) + } + }() } } @@ -375,3 +393,124 @@ func TestCheckSignatureEncoding(t *testing.T) { } } } + +func TestDisasmPC(t *testing.T) { + t.Parallel() + + // tx with almost empty scripts. + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: daghash.Hash([32]byte{ + 0xc9, 0x97, 0xa5, 0xe5, + 0x6e, 0x10, 0x41, 0x02, + 0xfa, 0x20, 0x9c, 0x6a, + 0x85, 0x2d, 0xd9, 0x06, + 0x60, 0xa2, 0x0b, 0x2d, + 0x9c, 0x35, 0x24, 0x23, + 0xed, 0xce, 0x25, 0x85, + 0x7f, 0xcd, 0x37, 0x04, + }), + Index: 0, + }, + SignatureScript: mustParseShortForm("OP_2"), + Sequence: 4294967295, + }}, + TxOut: []*wire.TxOut{{ + Value: 1000000000, + PkScript: nil, + }}, + LockTime: 0, + } + pkScript := mustParseShortForm("OP_DROP NOP TRUE") + + vm, err := NewEngine(pkScript, tx, 0, 0, nil) + if err != nil { + t.Fatalf("failed to create script: %v", err) + } + + tests := []struct { + expected string + expectedErr error + }{ + {"00:0000: OP_2", nil}, + {"01:0000: OP_DROP", nil}, + {"01:0001: OP_NOP", nil}, + {"01:0002: OP_1", nil}, + {"", scriptError(ErrInvalidProgramCounter, "")}, + } + + for i, test := range tests { + actual, err := vm.DisasmPC() + if e := tstCheckScriptError(err, test.expectedErr); e != nil { + t.Errorf("TestDisasmPC: %d: %s", i, e) + } + + if actual != test.expected { + t.Errorf("TestDisasmPC: %d: expected: '%s'. Got: '%s'", i, test.expected, actual) + } + + // ignore results from vm.Step() to keep going even when no opcodes left, to hit error case + _, _ = vm.Step() + } +} + +func TestDisasmScript(t *testing.T) { + t.Parallel() + + // tx with almost empty scripts. + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{{ + PreviousOutPoint: wire.OutPoint{ + Hash: daghash.Hash([32]byte{ + 0xc9, 0x97, 0xa5, 0xe5, + 0x6e, 0x10, 0x41, 0x02, + 0xfa, 0x20, 0x9c, 0x6a, + 0x85, 0x2d, 0xd9, 0x06, + 0x60, 0xa2, 0x0b, 0x2d, + 0x9c, 0x35, 0x24, 0x23, + 0xed, 0xce, 0x25, 0x85, + 0x7f, 0xcd, 0x37, 0x04, + }), + Index: 0, + }, + SignatureScript: mustParseShortForm("OP_2"), + Sequence: 4294967295, + }}, + TxOut: []*wire.TxOut{{ + Value: 1000000000, + PkScript: nil, + }}, + LockTime: 0, + } + pkScript := mustParseShortForm("OP_DROP NOP TRUE") + + vm, err := NewEngine(pkScript, tx, 0, 0, nil) + if err != nil { + t.Fatalf("failed to create script: %v", err) + } + + tests := []struct { + index int + expected string + expectedErr error + }{ + {-1, "", scriptError(ErrInvalidIndex, "")}, + {0, "00:0000: OP_2\n", nil}, + {1, "01:0000: OP_DROP\n01:0001: OP_NOP\n01:0002: OP_1\n", nil}, + {2, "", scriptError(ErrInvalidIndex, "")}, + } + + for _, test := range tests { + actual, err := vm.DisasmScript(test.index) + if e := tstCheckScriptError(err, test.expectedErr); e != nil { + t.Errorf("TestDisasmScript: %d: %s", test.index, e) + } + + if actual != test.expected { + t.Errorf("TestDisasmScript: %d: expected: '%s'. Got: '%s'", test.index, test.expected, actual) + } + } +} diff --git a/txscript/main_test.go b/txscript/main_test.go new file mode 100644 index 000000000..e7d6c39ef --- /dev/null +++ b/txscript/main_test.go @@ -0,0 +1,15 @@ +package txscript + +import ( + "os" + "testing" + + "github.com/btcsuite/btclog" +) + +func TestMain(m *testing.M) { + // set log level to trace, so that logClosures passed to log.Tracef are covered + log.SetLevel(btclog.LevelTrace) + + os.Exit(m.Run()) +}