diff --git a/bscript/interpreter/debug.go b/bscript/interpreter/debug.go new file mode 100644 index 00000000..5bfada83 --- /dev/null +++ b/bscript/interpreter/debug.go @@ -0,0 +1,86 @@ +package interpreter + +// Debugger implement to enable debugging. +// If enabled, copies of state are provided to each of the functions on +// call. +// +// Each function is called during its stage of a threads lifecycle. +// A high level overview of this lifecycle is: +// +// BeforeExecute +// for step +// BeforeStep +// BeforeExecuteOpcode +// for each stack push +// BeforeStackPush +// AfterStackPush +// end for +// for each stack pop +// BeforeStackPop +// AfterStackPop +// end for +// AfterExecuteOpcode +// if end of script +// BeforeScriptChange +// AfterScriptChange +// end if +// if bip16 and end of final script +// BeforeStackPush +// AfterStackPush +// end if +// AfterStep +// end for +// AfterExecute +// if success +// AfterSuccess +// end if +// if error +// AfterError +// end if +type Debugger interface { + BeforeExecute(*State) + AfterExecute(*State) + BeforeStep(*State) + AfterStep(*State) + BeforeExecuteOpcode(*State) + AfterExecuteOpcode(*State) + BeforeScriptChange(*State) + AfterScriptChange(*State) + AfterSuccess(*State) + AfterError(*State, error) + + BeforeStackPush(*State, []byte) + AfterStackPush(*State, []byte) + BeforeStackPop(*State) + AfterStackPop(*State, []byte) +} + +type nopDebugger struct{} + +func (n *nopDebugger) BeforeExecute(*State) {} + +func (n *nopDebugger) AfterExecute(*State) {} + +func (n *nopDebugger) BeforeStep(*State) {} + +func (n *nopDebugger) AfterStep(*State) {} + +func (n *nopDebugger) BeforeExecuteOpcode(*State) {} + +func (n *nopDebugger) AfterExecuteOpcode(*State) {} + +func (n *nopDebugger) BeforeScriptChange(*State) {} + +func (n *nopDebugger) AfterScriptChange(*State) {} + +func (n *nopDebugger) BeforeStackPush(*State, []byte) {} + +func (n *nopDebugger) AfterStackPush(*State, []byte) {} + +func (n *nopDebugger) BeforeStackPop(*State) {} + +func (n *nopDebugger) AfterStackPop(*State, []byte) {} + +func (n *nopDebugger) AfterSuccess(*State) {} + +func (n *nopDebugger) AfterError(*State, error) {} diff --git a/bscript/interpreter/debug/debugger.go b/bscript/interpreter/debug/debugger.go new file mode 100644 index 00000000..49dcc7f1 --- /dev/null +++ b/bscript/interpreter/debug/debugger.go @@ -0,0 +1,315 @@ +package debug + +import "github.com/libsv/go-bt/v2/bscript/interpreter" + +type ( + // ThreadStateFunc debug handler for a threads state. + ThreadStateFunc func(state *interpreter.State) + + // StackFunc debug handler for stack operations. + StackFunc func(state *interpreter.State, data []byte) + + // ExecutionErrorFunc debug handler for execution failure. + ExecutionErrorFunc func(state *interpreter.State, err error) +) + +type debugOpts struct { + rewind bool +} + +// DefaultDebugger exposes attachment points via the way of functions, which +// are to be appended to via a series of function calls. +type DefaultDebugger interface { + AttachBeforeExecute(ThreadStateFunc) + AttachAfterExecute(ThreadStateFunc) + AttachBeforeStep(ThreadStateFunc) + AttachAfterStep(ThreadStateFunc) + AttachBeforeExecuteOpcode(ThreadStateFunc) + AttachAfterExecuteOpcode(ThreadStateFunc) + AttachBeforeScriptChange(ThreadStateFunc) + AttachAfterScriptChange(ThreadStateFunc) + AttachAfterSuccess(ThreadStateFunc) + AttachAfterError(ExecutionErrorFunc) + + AttachBeforeStackPush(StackFunc) + AttachAfterStackPush(StackFunc) + AttachBeforeStackPop(ThreadStateFunc) + AttachAfterStackPop(StackFunc) + + interpreter.Debugger +} + +type debugger struct { + beforeExecuteFns []ThreadStateFunc + afterExecuteFns []ThreadStateFunc + + beforeStepFns []ThreadStateFunc + afterStepFns []ThreadStateFunc + + beforeExecuteOpcodeFns []ThreadStateFunc + afterExecuteOpcodeFns []ThreadStateFunc + + beforeScriptChangeFns []ThreadStateFunc + afterScriptChangeFns []ThreadStateFunc + + afterSuccessFns []ThreadStateFunc + afterErrorFns []ExecutionErrorFunc + + beforeStackPushFns []StackFunc + afterStackPushFns []StackFunc + + beforeStackPopFns []ThreadStateFunc + afterStackPopFns []StackFunc +} + +// NewDebugger returns an empty debugger which is to be configured with the `Attach` +// functions. +// +// Example usage: +// debugger := debug.NewDebugger() +// debugger.AttachBeforeExecuteOpcode(func (state *interpreter.State) { +// fmt.Println(state.DataStack) +// }) +// debugger.AttachAfterStackPush(func (state *interpreter.State, data []byte) { +// fmt.Println(hex.EncodeToString(data)) +// }) +// engine.Execute(interpreter.WithDebugger(debugger)) +func NewDebugger(oo ...DebuggerOptionFunc) DefaultDebugger { + opts := &debugOpts{} + for _, o := range oo { + o(opts) + } + + return &debugger{ + beforeExecuteFns: make([]ThreadStateFunc, 0), + afterExecuteFns: make([]ThreadStateFunc, 0), + + beforeStepFns: make([]ThreadStateFunc, 0), + afterStepFns: make([]ThreadStateFunc, 0), + + beforeExecuteOpcodeFns: make([]ThreadStateFunc, 0), + afterExecuteOpcodeFns: make([]ThreadStateFunc, 0), + + afterSuccessFns: make([]ThreadStateFunc, 0), + afterErrorFns: make([]ExecutionErrorFunc, 0), + + beforeStackPushFns: make([]StackFunc, 0), + afterStackPushFns: make([]StackFunc, 0), + + beforeStackPopFns: make([]ThreadStateFunc, 0), + afterStackPopFns: make([]StackFunc, 0), + } +} + +// AttachBeforeExecute attach the provided function to be executed before +// interpreter execution. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeExecute(fn ThreadStateFunc) { + d.beforeExecuteFns = append(d.beforeExecuteFns, fn) +} + +// AttachAfterExecute attach the provided function to be executed after +// all scripts have completed execution, but before the final stack value +// is checked. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterExecute(fn ThreadStateFunc) { + d.afterExecuteFns = append(d.afterExecuteFns, fn) +} + +// AttachBeforeStep attach the provided function to be executed before a thread +// begins a step. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeStep(fn ThreadStateFunc) { + d.beforeStepFns = append(d.beforeStepFns, fn) +} + +// AttachAfterStep attach the provided function to be executed after a thread +// finishes a step. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterStep(fn ThreadStateFunc) { + d.afterStepFns = append(d.afterStepFns, fn) +} + +// AttachBeforeExecuteOpcode attach the provided function to be executed before +// an opcodes execution. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeExecuteOpcode(fn ThreadStateFunc) { + d.beforeExecuteOpcodeFns = append(d.beforeExecuteOpcodeFns, fn) +} + +// AttachAfterExecuteOpcode attach the provided function to be executed after +// an opcodes execution. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterExecuteOpcode(fn ThreadStateFunc) { + d.afterExecuteOpcodeFns = append(d.afterExecuteOpcodeFns, fn) +} + +// AttachBeforeScriptChange attach the provided function to be executed after +// a scripts execution has finished, just before the thread shifts to the +// next. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeScriptChange(fn ThreadStateFunc) { + d.beforeScriptChangeFns = append(d.beforeScriptChangeFns, fn) +} + +// AttachAfterScriptChange attach the provided function to be executed after +// a scripts execution has finished, just after the thread shifts to the +// next. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterScriptChange(fn ThreadStateFunc) { + d.afterScriptChangeFns = append(d.afterScriptChangeFns, fn) +} + +// AttachAfterSuccess attach the provided function to be executed on +// successful execution. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterSuccess(fn ThreadStateFunc) { + d.afterSuccessFns = append(d.afterSuccessFns, fn) +} + +// AttachAfterError attach the provided function to be executed on execution +// error. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterError(fn ExecutionErrorFunc) { + d.afterErrorFns = append(d.afterErrorFns, fn) +} + +// AttachBeforeStackPush attach the provided function to be executed just before +// a push to a stack. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeStackPush(fn StackFunc) { + d.beforeStackPushFns = append(d.beforeStackPushFns, fn) +} + +// AttachAfterStackPush attach the provided function to be executed immediately +// after a push to a stack. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterStackPush(fn StackFunc) { + d.afterStackPushFns = append(d.afterStackPushFns, fn) +} + +// AttachBeforeStackPop attach the provided function to be executed just before +// a pop to a stack. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachBeforeStackPop(fn ThreadStateFunc) { + d.beforeStackPopFns = append(d.beforeStackPopFns, fn) +} + +// AttachAfterStackPop attach the provided function to be executed immediately +// after a pop from a stack. +// If this is called multiple times, provided funcs are executed on a +// FIFO basis. +func (d *debugger) AttachAfterStackPop(fn StackFunc) { + d.afterStackPopFns = append(d.afterStackPopFns, fn) +} + +// BeforeExecute execute all before execute attachments. +func (d *debugger) BeforeExecute(state *interpreter.State) { + for _, fn := range d.beforeExecuteFns { + fn(state) + } +} + +// AfterExecute execute all after execute attachments. +func (d *debugger) AfterExecute(state *interpreter.State) { + for _, fn := range d.afterExecuteFns { + fn(state) + } +} + +// BeforeStep execute all before step attachments. +func (d *debugger) BeforeStep(state *interpreter.State) { + for _, fn := range d.beforeStepFns { + fn(state) + } +} + +// AfterStep execute all after step attachments. +func (d *debugger) AfterStep(state *interpreter.State) { + for _, fn := range d.afterStepFns { + fn(state) + } +} + +// BeforeExecuteOpcode execute all before execute opcode attachments. +func (d *debugger) BeforeExecuteOpcode(state *interpreter.State) { + for _, fn := range d.beforeExecuteOpcodeFns { + fn(state) + } +} + +// AfterExecuteOpcode execute all after execute opcode attachments. +func (d *debugger) AfterExecuteOpcode(state *interpreter.State) { + for _, fn := range d.afterExecuteOpcodeFns { + fn(state) + } +} + +// BeforeScriptChange execute all before script change attachments. +func (d *debugger) BeforeScriptChange(state *interpreter.State) { + for _, fn := range d.beforeScriptChangeFns { + fn(state) + } +} + +// AfterScriptChange execute all after script change attachments. +func (d *debugger) AfterScriptChange(state *interpreter.State) { + for _, fn := range d.afterScriptChangeFns { + fn(state) + } +} + +// AfterSuccess execute all after success attachments. +func (d *debugger) AfterSuccess(state *interpreter.State) { + for _, fn := range d.afterSuccessFns { + fn(state) + } +} + +// AfterError execute all after error attachments. +func (d *debugger) AfterError(state *interpreter.State, err error) { + for _, fn := range d.afterErrorFns { + fn(state, err) + } +} + +// BeforeStackPush execute all before stack push attachments. +func (d *debugger) BeforeStackPush(state *interpreter.State, data []byte) { + for _, fn := range d.beforeStackPushFns { + fn(state, data) + } +} + +// AfterStackPush execute all after stack push attachments. +func (d *debugger) AfterStackPush(state *interpreter.State, data []byte) { + for _, fn := range d.afterStackPushFns { + fn(state, data) + } +} + +// BeforeStackPop execute all before stack pop attachments. +func (d *debugger) BeforeStackPop(state *interpreter.State) { + for _, fn := range d.beforeStackPopFns { + fn(state) + } +} + +// AfterStackPop execute all after stack pop attachments. +func (d *debugger) AfterStackPop(state *interpreter.State, data []byte) { + for _, fn := range d.afterStackPopFns { + fn(state, data) + } +} diff --git a/bscript/interpreter/debug/debugger_test.go b/bscript/interpreter/debug/debugger_test.go new file mode 100644 index 00000000..a44bf3e5 --- /dev/null +++ b/bscript/interpreter/debug/debugger_test.go @@ -0,0 +1,1258 @@ +package debug_test + +import ( + "encoding/hex" + "testing" + + "github.com/libsv/go-bt/v2/bscript" + "github.com/libsv/go-bt/v2/bscript/interpreter" + "github.com/libsv/go-bt/v2/bscript/interpreter/debug" + "github.com/stretchr/testify/assert" +) + +func TestDebugger_BeforeExecute(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStack []string + expOpcode string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStack: []string{}, + expOpcode: "OP_4", + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStack: []string{}, + expOpcode: "OP_0", + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStack: []string{}, + expOpcode: "OP_4", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + var timesCalled int + debugger := debug.NewDebugger() + debugger.AttachBeforeExecute(func(state *interpreter.State) { + timesCalled++ + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + assert.Equal(t, test.expStack, stack) + assert.Equal(t, test.expOpcode, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, 1, timesCalled) + }) + } +} + +func TestDebugger_BeforeStep(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06", "02", "03"}, + {"04", "06", "06"}, + {"04"}, + {"04", "02"}, + {"04", "02", "02"}, + {"04", "04"}, + }, + expOpcodes: []string{ + "OP_4", "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {}, + {""}, + {"", ""}, + {"", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + }, + expOpcodes: []string{"OP_0", "OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07", "02", "03"}, + {"04", "07", "06"}, + }, + expOpcodes: []string{"OP_4", "OP_7", "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachBeforeStep(func(state *interpreter.State) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + }) + } +} + +func TestDebugger_AfterStep(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06", "02", "03"}, + {"04", "06", "06"}, + {"04"}, + {"04", "02"}, + {"04", "02", "02"}, + {"04", "04"}, + {"01"}, + }, + expOpcodes: []string{ + "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"", ""}, + {"", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"01"}, + }, + expOpcodes: []string{"OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07", "02", "03"}, + {"04", "07", "06"}, + }, + expOpcodes: []string{"OP_7", "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachAfterStep(func(state *interpreter.State) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + }) + } +} + +func TestDebugger_BeforeExecuteOpcode(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06", "02", "03"}, + {"04", "06", "06"}, + {"04"}, + {"04", "02"}, + {"04", "02", "02"}, + {"04", "04"}, + }, + expOpcodes: []string{ + "OP_4", "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {}, + {""}, + {"", ""}, + {"", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + }, + expOpcodes: []string{"OP_0", "OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07", "02", "03"}, + {"04", "07", "06"}, + }, + expOpcodes: []string{"OP_4", "OP_7", "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachBeforeExecuteOpcode(func(state *interpreter.State) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + }) + } +} + +func TestDebugger_AfterExecuteOpcode(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06", "02", "03"}, + {"04", "06", "06"}, + {"04"}, + {"04", "02"}, + {"04", "02", "02"}, + {"04", "04"}, + {"01"}, + }, + expOpcodes: []string{ + "OP_4", "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"", ""}, + {"", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"01"}, + }, + expOpcodes: []string{"OP_0", "OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07", "02", "03"}, + {"04", "07", "06"}, + }, + expOpcodes: []string{"OP_4", "OP_7", "OP_2", "OP_3", "OP_MUL"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachAfterExecuteOpcode(func(state *interpreter.State) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + }) + } +} + +func TestDebugger_BeforeScriptChange(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + exptimesCalled int + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04", "06"}, + {"01"}, + }, + expOpcodes: []string{"OP_6", "OP_EQUAL"}, + exptimesCalled: 2, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"01"}, + }, + expOpcodes: []string{"OP_0", "OP_EQUAL"}, + exptimesCalled: 2, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04", "07"}, + }, + expOpcodes: []string{"OP_7"}, + exptimesCalled: 1, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + + var timesCalled int + debugger.AttachBeforeScriptChange(func(state *interpreter.State) { + timesCalled++ + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + assert.Equal(t, test.exptimesCalled, timesCalled) + }) + } +} + +func TestDebugger_AfterScriptChange(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + exptimesCalled int + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04", "06"}, + {"01"}, + }, + expOpcodes: []string{"OP_2", "OP_EQUAL"}, + exptimesCalled: 2, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"01"}, + }, + expOpcodes: []string{"OP_DUP", "OP_EQUAL"}, + exptimesCalled: 2, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04", "07"}, + }, + expOpcodes: []string{"OP_2"}, + exptimesCalled: 1, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + + var timesCalled int + debugger.AttachAfterScriptChange(func(state *interpreter.State) { + timesCalled++ + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + assert.Equal(t, test.exptimesCalled, timesCalled) + }) + } +} + +func TestDebugger_AfterExecution(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStack []string + expOpcode string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStack: []string{"01"}, + expOpcode: "OP_EQUAL", + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStack: []string{"01"}, + expOpcode: "OP_EQUAL", + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStack: []string{"04"}, + expOpcode: "OP_EQUALVERIFY", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + stack := make([]string, 0) + var opcode string + + debugger := debug.NewDebugger() + debugger.AttachAfterExecute(func(state *interpreter.State) { + for _, d := range state.DataStack { + stack = append(stack, hex.EncodeToString(d)) + } + opcode = state.Opcode().Name() + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStack, stack) + assert.Equal(t, test.expOpcode, opcode) + }) + } +} + +func TestDebugger_AfterError(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStack []string + expOpcode string + expCalled bool + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStack: []string{"04"}, + expOpcode: "OP_EQUALVERIFY", + expCalled: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + stack := make([]string, 0) + var opcode string + var called bool + + debugger := debug.NewDebugger() + debugger.AttachAfterError(func(state *interpreter.State, err error) { + called = true + for _, d := range state.DataStack { + stack = append(stack, hex.EncodeToString(d)) + } + opcode = state.Opcode().Name() + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expCalled, called) + if called { + assert.Equal(t, test.expStack, stack) + assert.Equal(t, test.expOpcode, opcode) + } + }) + } +} + +func TestDebugger_AfterSuccess(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStack []string + expOpcode string + expCalled bool + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStack: []string{}, + expOpcode: "OP_EQUAL", + expCalled: true, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStack: []string{}, + expOpcode: "OP_EQUAL", + expCalled: true, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + stack := make([]string, 0) + var opcode string + var called bool + + debugger := debug.NewDebugger() + debugger.AttachAfterSuccess(func(state *interpreter.State) { + called = true + for _, d := range state.DataStack { + stack = append(stack, hex.EncodeToString(d)) + } + opcode = state.Opcode().Name() + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expCalled, called) + if called { + assert.Equal(t, test.expStack, stack) + assert.Equal(t, test.expOpcode, opcode) + } + }) + } +} + +func TestDebugger_BeforeStackPush(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + entries []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + expPushData []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06"}, + {"04"}, + {"04"}, + {"04", "02"}, + {"04"}, + {}, + }, + expPushData: []string{"04", "06", "02", "03", "06", "01", "02", "02", "04", "01"}, + expOpcodes: []string{ + "OP_4", "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {}, + {""}, + {""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {}, + }, + expPushData: []string{"", "", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "01"}, + expOpcodes: []string{"OP_0", "OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {}, + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07"}, + {"04"}, + }, + expPushData: []string{"04", "07", "02", "03", "06", ""}, + expOpcodes: []string{"OP_4", "OP_7", "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + entries: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachBeforeStackPush(func(state *interpreter.State, data []byte) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + history.entries = append(history.entries, hex.EncodeToString(data)) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + assert.Equal(t, test.expPushData, history.entries) + }) + } +} + +func TestDebugger_AfterStackPush(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + entries []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + expPushData []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04"}, + {"04", "06"}, + {"04", "06", "02"}, + {"04", "06", "02", "03"}, + {"04", "06", "06"}, + {"04", "01"}, + {"04", "02"}, + {"04", "02", "02"}, + {"04", "04"}, + {"01"}, + }, + expPushData: []string{"04", "06", "02", "03", "06", "01", "02", "02", "04", "01"}, + expOpcodes: []string{ + "OP_4", "OP_6", + "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY", + "OP_2", "OP_2", "OP_ADD", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"", ""}, + {"", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"01"}, + }, + expPushData: []string{"", "", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "01"}, + expOpcodes: []string{"OP_0", "OP_DUP", "OP_HASH160", "OP_SWAP", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04"}, + {"04", "07"}, + {"04", "07", "02"}, + {"04", "07", "02", "03"}, + {"04", "07", "06"}, + {"04", ""}, + }, + expPushData: []string{"04", "07", "02", "03", "06", ""}, + expOpcodes: []string{"OP_4", "OP_7", "OP_2", "OP_3", "OP_MUL", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + entries: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachAfterStackPush(func(state *interpreter.State, data []byte) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + history.entries = append(history.entries, hex.EncodeToString(data)) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + assert.Equal(t, test.expPushData, history.entries) + }) + } +} + +func TestDebugger_BeforeStackPop(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04", "06", "02", "03"}, + {"04", "06", "02"}, + {"04", "06", "06"}, + {"04", "06"}, + {"04", "01"}, + {"04", "02", "02"}, + {"04", "02"}, + {"04", "04"}, + {"04"}, + {"01"}, + }, + expOpcodes: []string{ + "OP_MUL", "OP_MUL", "OP_EQUALVERIFY", "OP_EQUALVERIFY", "OP_EQUALVERIFY", + "OP_ADD", "OP_ADD", "OP_EQUAL", "OP_EQUAL", "OP_EQUAL", + }, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {"", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", ""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"01"}, + }, + expOpcodes: []string{"OP_HASH160", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL", "OP_EQUAL", "OP_EQUAL"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04", "07", "02", "03"}, + {"04", "07", "02"}, + {"04", "07", "06"}, + {"04", "07"}, + {"04", ""}, + }, + expOpcodes: []string{"OP_MUL", "OP_MUL", "OP_EQUALVERIFY", "OP_EQUALVERIFY", "OP_EQUALVERIFY"}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachBeforeStackPop(func(state *interpreter.State) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + }) + } +} + +func TestDebugger_AfterStackPop(t *testing.T) { + t.Parallel() + + type stateHistory struct { + dstack [][]string + astack [][]string + opcodes []string + entries []string + } + + tests := map[string]struct { + lockingScriptHex string + unlockingScriptHex string + expStackHistory [][]string + expOpcodes []string + expPopData []string + }{ + "simple script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5456", + expStackHistory: [][]string{ + {"04", "06", "02"}, + {"04", "06"}, + {"04", "06"}, + {"04"}, + {"04"}, + {"04", "02"}, + {"04"}, + {"04"}, + {}, + {}, + }, + expOpcodes: []string{ + "OP_MUL", "OP_MUL", "OP_EQUALVERIFY", "OP_EQUALVERIFY", "OP_EQUALVERIFY", + "OP_ADD", "OP_ADD", "OP_EQUAL", "OP_EQUAL", "OP_EQUAL", + }, + expPopData: []string{"03", "02", "06", "06", "01", "02", "02", "04", "04", "01"}, + }, + "complex script": { + lockingScriptHex: "76a97ca8a687", + unlockingScriptHex: "00", + expStackHistory: [][]string{ + {""}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {"b472a266d0bd89c13706a4132ccfb16f7c3b9fcb"}, + {}, + {}, + }, + expOpcodes: []string{"OP_HASH160", "OP_SHA256", "OP_RIPEMD160", "OP_EQUAL", "OP_EQUAL", "OP_EQUAL"}, + expPopData: []string{"", "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "b472a266d0bd89c13706a4132ccfb16f7c3b9fcb", "01"}, + }, + "error script": { + lockingScriptHex: "5253958852529387", + unlockingScriptHex: "5457", + expStackHistory: [][]string{ + {"04", "07", "02"}, + {"04", "07"}, + {"04", "07"}, + {"04"}, + {"04"}, + }, + expOpcodes: []string{"OP_MUL", "OP_MUL", "OP_EQUALVERIFY", "OP_EQUALVERIFY", "OP_EQUALVERIFY"}, + expPopData: []string{"03", "02", "06", "07", ""}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.lockingScriptHex) + assert.NoError(t, err) + + uls, err := bscript.NewFromHexString(test.unlockingScriptHex) + assert.NoError(t, err) + + history := &stateHistory{ + dstack: make([][]string, 0), + astack: make([][]string, 0), + opcodes: make([]string, 0), + entries: make([]string, 0), + } + + debugger := debug.NewDebugger() + debugger.AttachAfterStackPop(func(state *interpreter.State, data []byte) { + stack := make([]string, len(state.DataStack)) + for i, d := range state.DataStack { + stack[i] = hex.EncodeToString(d) + } + history.dstack = append(history.dstack, stack) + history.opcodes = append(history.opcodes, state.Opcode().Name()) + history.entries = append(history.entries, hex.EncodeToString(data)) + }) + + interpreter.NewEngine().Execute( + interpreter.WithScripts(ls, uls), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ) + + assert.Equal(t, test.expStackHistory, history.dstack) + assert.Equal(t, test.expOpcodes, history.opcodes) + assert.Equal(t, test.expPopData, history.entries) + }) + } +} diff --git a/bscript/interpreter/debug/example_test.go b/bscript/interpreter/debug/example_test.go new file mode 100644 index 00000000..31d0c280 --- /dev/null +++ b/bscript/interpreter/debug/example_test.go @@ -0,0 +1,52 @@ +package debug_test + +import ( + "encoding/hex" + "fmt" + "strings" + + "github.com/libsv/go-bt/v2/bscript" + "github.com/libsv/go-bt/v2/bscript/interpreter" + "github.com/libsv/go-bt/v2/bscript/interpreter/debug" +) + +func ExampleDebugger_AfterStep() { + lockingScript, err := bscript.NewFromASM("777f726c64 OP_SWAP OP_CAT OP_SHA256 8376118fc0230e6054e782fb31ae52ebcfd551342d8d026c209997e0127b6f74 OP_EQUAL") + if err != nil { + fmt.Println(err) + return + } + + unlockingScript, err := bscript.NewFromASM(hex.EncodeToString([]byte("hello"))) + if err != nil { + fmt.Println(err) + return + } + + debugger := debug.NewDebugger() + debugger.AttachAfterStep(func(state *interpreter.State) { + frames := make([]string, len(state.DataStack)) + for i, frame := range state.DataStack { + frames[i] = hex.EncodeToString(frame) + } + fmt.Println(strings.Join(frames, "|")) + }) + + if err := interpreter.NewEngine().Execute( + interpreter.WithScripts(lockingScript, unlockingScript), + interpreter.WithAfterGenesis(), + interpreter.WithDebugger(debugger), + ); err != nil { + fmt.Println(err) + } + + // Output: + // 68656c6c6f + // 68656c6c6f|777f726c64 + // 777f726c64|68656c6c6f + // 777f726c6468656c6c6f + // 8a0e597fd66749ca1a2f098f4ef706422c63a96dceef4abfd74517b10cd12f63 + // 8a0e597fd66749ca1a2f098f4ef706422c63a96dceef4abfd74517b10cd12f63|8376118fc0230e6054e782fb31ae52ebcfd551342d8d026c209997e0127b6f74 + // + // false stack entry at end of script execution +} diff --git a/bscript/interpreter/debug/options.go b/bscript/interpreter/debug/options.go new file mode 100644 index 00000000..b9809a53 --- /dev/null +++ b/bscript/interpreter/debug/options.go @@ -0,0 +1,12 @@ +package debug + +// DebuggerOptionFunc for setting debugger options. +type DebuggerOptionFunc func(o *debugOpts) + +// WithRewind configure the debugger to enable rewind functionality. When +// enabled, the debugger will save each stack frame from BeforeStep to memory. +func WithRewind() DebuggerOptionFunc { + return func(o *debugOpts) { + o.rewind = true + } +} diff --git a/bscript/interpreter/engine.go b/bscript/interpreter/engine.go index 0aa26210..7f6aea03 100644 --- a/bscript/interpreter/engine.go +++ b/bscript/interpreter/engine.go @@ -25,7 +25,7 @@ func NewEngine() Engine { // // Execute with tx example: // if err := engine.Execute( -// interpreter.WithTx(tx, inputIdx previousOutput), +// interpreter.WithTx(tx, inputIdx, previousOutput), // interpreter.WithAfterGenesis(), // interpreter.WithForkID(), // ); err != nil { @@ -47,10 +47,15 @@ func (e *engine) Execute(oo ...ExecutionOptionFunc) error { o(opts) } - th, err := createThread(opts) + t, err := createThread(opts) if err != nil { return err } - return th.execute() + if err := t.execute(); err != nil { + t.afterError(err) + return err + } + + return nil } diff --git a/bscript/interpreter/engine_test.go b/bscript/interpreter/engine_test.go index ead5f4da..40fb1944 100644 --- a/bscript/interpreter/engine_test.go +++ b/bscript/interpreter/engine_test.go @@ -60,9 +60,9 @@ func TestBadPC(t *testing.T) { cfg: &beforeGenesisConfig{}, } err := vm.apply(&execOpts{ - PreviousTxOut: txOut, - Tx: tx, - InputIdx: 0, + previousTxOut: txOut, + tx: tx, + inputIdx: 0, }) if err != nil { t.Errorf("Failed to create script: %v", err) @@ -117,9 +117,9 @@ func TestCheckErrorCondition(t *testing.T) { } err = vm.apply(&execOpts{ - PreviousTxOut: txOut, - InputIdx: 0, - Tx: tx, + previousTxOut: txOut, + inputIdx: 0, + tx: tx, }) if err != nil { t.Errorf("failed to configure thread %w", err) @@ -148,7 +148,7 @@ func TestValidateParams(t *testing.T) { }{ "valid tx/previous out checksig script": { params: execOpts{ - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -160,7 +160,7 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { cbLockingScript, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) @@ -170,7 +170,7 @@ func TestValidateParams(t *testing.T) { }, "valid tx/previous out non-checksig script": { params: execOpts{ - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "52529387", 0) assert.NoError(t, err) @@ -182,7 +182,7 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { cbLockingScript, err := bscript.NewFromASM("OP_2 OP_2 OP_ADD OP_EQUAL") assert.NoError(t, err) @@ -192,12 +192,12 @@ func TestValidateParams(t *testing.T) { }, "valid locking/unlocking script non-checksig": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("52529387") assert.NoError(t, err) return script }(), - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("54") assert.NoError(t, err) return script @@ -206,17 +206,17 @@ func TestValidateParams(t *testing.T) { }, "valid locking/unlocking script with check-sig": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) return script }(), - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("483045022100a4d9da733aeb29f9ba94dcaa578e71662cf29dd9742ce4b022c098211f4fdb06022041d24db4eda239fa15a12cf91229f6c352adab3c1c10091fc2aa517fe0f487c5412102454c535854802e5eaeaf5cbecd20e0aa508486063b71194dfde34744f19f1a5d") assert.NoError(t, err) return script }(), - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -228,7 +228,7 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) @@ -238,12 +238,12 @@ func TestValidateParams(t *testing.T) { }, "no locking script provided errors": { params: execOpts{ - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("483045022100a4d9da733aeb29f9ba94dcaa578e71662cf29dd9742ce4b022c098211f4fdb06022041d24db4eda239fa15a12cf91229f6c352adab3c1c10091fc2aa517fe0f487c5412102454c535854802e5eaeaf5cbecd20e0aa508486063b71194dfde34744f19f1a5d") assert.NoError(t, err) return script }(), - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -260,12 +260,12 @@ func TestValidateParams(t *testing.T) { }, "no unlocking script provided errors": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) return script }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) @@ -276,12 +276,12 @@ func TestValidateParams(t *testing.T) { }, "invalid locking/unlocking script with checksig": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) return script }(), - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("483045022100a4d9da733aeb29f9ba94dcaa578e71662cf29dd9742ce4b022c098211f4fdb06022041d24db4eda239fa15a12cf91229f6c352adab3c1c10091fc2aa517fe0f487c5412102454c535854802e5eaeaf5cbecd20e0aa508486063b71194dfde34744f19f1a5d") assert.NoError(t, err) return script @@ -291,17 +291,17 @@ func TestValidateParams(t *testing.T) { }, "provided locking script that differs from previoustxout's errors": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("52529387") assert.NoError(t, err) return script }(), - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("483045022100a4d9da733aeb29f9ba94dcaa578e71662cf29dd9742ce4b022c098211f4fdb06022041d24db4eda239fa15a12cf91229f6c352adab3c1c10091fc2aa517fe0f487c5412102454c535854802e5eaeaf5cbecd20e0aa508486063b71194dfde34744f19f1a5d") assert.NoError(t, err) return script }(), - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -313,7 +313,7 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) @@ -324,17 +324,17 @@ func TestValidateParams(t *testing.T) { }, "provided unlocking scropt that differs from tx input's errors": { params: execOpts{ - LockingScript: func() *bscript.Script { + lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) return script }(), - UnlockingScript: func() *bscript.Script { + unlockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("84") assert.NoError(t, err) return script }(), - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -346,7 +346,7 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) @@ -357,7 +357,7 @@ func TestValidateParams(t *testing.T) { }, "invalid input index errors": { params: execOpts{ - Tx: func() *bt.Tx { + tx: func() *bt.Tx { tx := bt.NewTx() err := tx.From("ae81577c1a2434929a1224cf19aa63e167d88029965e2ca6de24defff014d031", 0, "76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac", 0) assert.NoError(t, err) @@ -369,13 +369,13 @@ func TestValidateParams(t *testing.T) { return tx }(), - PreviousTxOut: func() *bt.Output { + previousTxOut: func() *bt.Output { cbLockingScript, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") assert.NoError(t, err) return &bt.Output{LockingScript: cbLockingScript, Satoshis: 0} }(), - InputIdx: 5, + inputIdx: 5, }, expErr: errors.New("transaction input index 5 is negative or >= 1"), }, @@ -436,10 +436,10 @@ func TestInvalidFlagCombinations(t *testing.T) { cfg: &beforeGenesisConfig{}, } err := vm.apply(&execOpts{ - Tx: tx, - InputIdx: 0, - PreviousTxOut: txOut, - Flags: test, + tx: tx, + inputIdx: 0, + previousTxOut: txOut, + flags: test, }) if !errs.IsErrorCode(err, errs.ErrInvalidFlags) { t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+ @@ -825,3 +825,106 @@ func TestCheckHashTypeEncoding(t *testing.T) { } } } + +func TestEngine_WithState(t *testing.T) { + tests := map[string]struct { + ls string + uls string + state *State + }{ + "start midway": { + ls: "5253958852529387", + uls: "5456", + state: &State{ + ScriptIdx: 1, + OpcodeIdx: 1, + DataStack: func() [][]byte { + return [][]byte{{4}, {6}, {2}} + }(), + AltStack: [][]byte{}, + CondStack: []int{}, + ElseStack: [][]byte{}, + Flags: scriptflag.UTXOAfterGenesis | scriptflag.EnableSighashForkID, + LastCodeSeperatorIdx: 0, + NumOps: 3, + SavedFirstStack: [][]byte{}, + Scripts: func() []ParsedScript { + ls, err := bscript.NewFromHexString("5253958852529387") + assert.NoError(t, err) + uls, err := bscript.NewFromHexString("5456") + assert.NoError(t, err) + + var parser DefaultOpcodeParser + pls, err := parser.Parse(ls) + assert.NoError(t, err) + + puls, err := parser.Parse(uls) + assert.NoError(t, err) + + return []ParsedScript{puls, pls} + }(), + Genesis: struct { + AfterGenesis bool + EarlyReturn bool + }{ + AfterGenesis: true, + }, + }, + }, + "start at operation": { + ls: "5253958852529387", + uls: "5456", + state: &State{ + ScriptIdx: 1, + OpcodeIdx: 6, + DataStack: func() [][]byte { + return [][]byte{{4}, {2}, {2}} + }(), + AltStack: [][]byte{}, + CondStack: []int{}, + ElseStack: [][]byte{}, + Flags: scriptflag.UTXOAfterGenesis | scriptflag.EnableSighashForkID, + LastCodeSeperatorIdx: 0, + NumOps: 8, + SavedFirstStack: [][]byte{}, + Scripts: func() []ParsedScript { + ls, err := bscript.NewFromHexString("5253958852529387") + assert.NoError(t, err) + uls, err := bscript.NewFromHexString("5456") + assert.NoError(t, err) + + var parser DefaultOpcodeParser + pls, err := parser.Parse(ls) + assert.NoError(t, err) + + puls, err := parser.Parse(uls) + assert.NoError(t, err) + + return []ParsedScript{puls, pls} + }(), + Genesis: struct { + AfterGenesis bool + EarlyReturn bool + }{ + AfterGenesis: true, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ls, err := bscript.NewFromHexString(test.ls) + assert.NoError(t, err) + uls, err := bscript.NewFromHexString(test.uls) + assert.NoError(t, err) + + assert.NoError(t, NewEngine().Execute( + WithScripts(ls, uls), + WithForkID(), + WithAfterGenesis(), + WithState(test.state), + )) + }) + } +} diff --git a/bscript/interpreter/opcodeparser.go b/bscript/interpreter/opcodeparser.go index cc5ce083..02124a61 100644 --- a/bscript/interpreter/opcodeparser.go +++ b/bscript/interpreter/opcodeparser.go @@ -15,27 +15,37 @@ type OpcodeParser interface { } // ParsedScript is a slice of ParsedOp -type ParsedScript []ParsedOp +type ParsedScript []ParsedOpcode // DefaultOpcodeParser is a standard parser which can be used from zero value. type DefaultOpcodeParser struct { ErrorOnCheckSig bool } -// ParsedOp is a parsed opcode -type ParsedOp struct { - Op opcode +// ParsedOpcode is a parsed opcode. +type ParsedOpcode struct { + op opcode Data []byte } -// Name returns the human readable name for the current opcode -func (o *ParsedOp) Name() string { - return o.Op.name +// Name returns the human readable name for the current opcode. +func (o ParsedOpcode) Name() string { + return o.op.name } -// IsDisabled returns true if the op is disabled -func (o *ParsedOp) IsDisabled() bool { - switch o.Op.val { +// Value returns the byte value of the opcode. +func (o ParsedOpcode) Value() byte { + return o.op.val +} + +// Length returns the data length of the opcode. +func (o ParsedOpcode) Length() int { + return o.op.length +} + +// IsDisabled returns true if the op is disabled. +func (o *ParsedOpcode) IsDisabled() bool { + switch o.op.val { case bscript.Op2MUL, bscript.Op2DIV: return true default: @@ -43,9 +53,9 @@ func (o *ParsedOp) IsDisabled() bool { } } -// RequiresTx returns true if the op is checksig -func (o *ParsedOp) RequiresTx() bool { - switch o.Op.val { +// RequiresTx returns true if the op is checksig. +func (o *ParsedOpcode) RequiresTx() bool { + switch o.op.val { case bscript.OpCHECKSIG, bscript.OpCHECKSIGVERIFY, bscript.OpCHECKMULTISIG, bscript.OpCHECKMULTISIGVERIFY, bscript.OpCHECKSEQUENCEVERIFY: return true @@ -54,9 +64,9 @@ func (o *ParsedOp) RequiresTx() bool { } } -// AlwaysIllegal returns true if the op is always illegal -func (o *ParsedOp) AlwaysIllegal() bool { - switch o.Op.val { +// AlwaysIllegal returns true if the op is always illegal. +func (o *ParsedOpcode) AlwaysIllegal() bool { + switch o.op.val { case bscript.OpVERIF, bscript.OpVERNOTIF: return true default: @@ -64,9 +74,9 @@ func (o *ParsedOp) AlwaysIllegal() bool { } } -// IsConditional returns true if the op is a conditional -func (o *ParsedOp) IsConditional() bool { - switch o.Op.val { +// IsConditional returns true if the op is a conditional. +func (o *ParsedOpcode) IsConditional() bool { + switch o.op.val { case bscript.OpIF, bscript.OpNOTIF, bscript.OpELSE, bscript.OpENDIF, bscript.OpVERIF, bscript.OpVERNOTIF: return true default: @@ -74,48 +84,48 @@ func (o *ParsedOp) IsConditional() bool { } } -// EnforceMinimumDataPush checks that the op is pushing only the needed amount of data. +// enforceMinimumDataPush checks that the op is pushing only the needed amount of data. // Errs if not the case. -func (o *ParsedOp) EnforceMinimumDataPush() error { +func (o *ParsedOpcode) enforceMinimumDataPush() error { dataLen := len(o.Data) - if dataLen == 0 && o.Op.val != bscript.Op0 { + if dataLen == 0 && o.op.val != bscript.Op0 { return errs.NewError( errs.ErrMinimalData, "zero length data push is encoded with opcode %s instead of OP_0", - o.Op.name, + o.op.name, ) } - if dataLen == 1 && (1 <= o.Data[0] && o.Data[0] <= 16) && o.Op.val != bscript.Op1+o.Data[0]-1 { + if dataLen == 1 && (1 <= o.Data[0] && o.Data[0] <= 16) && o.op.val != bscript.Op1+o.Data[0]-1 { return errs.NewError( errs.ErrMinimalData, - "data push of the value %d encoded with opcode %s instead of OP_%d", o.Data[0], o.Op.name, o.Data[0], + "data push of the value %d encoded with opcode %s instead of OP_%d", o.Data[0], o.op.name, o.Data[0], ) } - if dataLen == 1 && o.Data[0] == 0x81 && o.Op.val != bscript.Op1NEGATE { + if dataLen == 1 && o.Data[0] == 0x81 && o.op.val != bscript.Op1NEGATE { return errs.NewError( errs.ErrMinimalData, - "data push of the value -1 encoded with opcode %s instead of OP_1NEGATE", o.Op.name, + "data push of the value -1 encoded with opcode %s instead of OP_1NEGATE", o.op.name, ) } if dataLen <= 75 { - if int(o.Op.val) != dataLen { + if int(o.op.val) != dataLen { return errs.NewError( errs.ErrMinimalData, - "data push of %d bytes encoded with opcode %s instead of OP_DATA_%d", dataLen, o.Op.name, dataLen, + "data push of %d bytes encoded with opcode %s instead of OP_DATA_%d", dataLen, o.op.name, dataLen, ) } } else if dataLen <= 255 { - if o.Op.val != bscript.OpPUSHDATA1 { + if o.op.val != bscript.OpPUSHDATA1 { return errs.NewError( errs.ErrMinimalData, - "data push of %d bytes encoded with opcode %s instead of OP_PUSHDATA1", dataLen, o.Op.name, + "data push of %d bytes encoded with opcode %s instead of OP_PUSHDATA1", dataLen, o.op.name, ) } } else if dataLen <= 65535 { - if o.Op.val != bscript.OpPUSHDATA2 { + if o.op.val != bscript.OpPUSHDATA2 { return errs.NewError( errs.ErrMinimalData, - "data push of %d bytes encoded with opcode %s instead of OP_PUSHDATA2", dataLen, o.Op.name, + "data push of %d bytes encoded with opcode %s instead of OP_PUSHDATA2", dataLen, o.op.name, ) } } @@ -125,35 +135,35 @@ func (o *ParsedOp) EnforceMinimumDataPush() error { // Parse takes a *bscript.Script and returns a []interpreter.ParsedOp func (p *DefaultOpcodeParser) Parse(s *bscript.Script) (ParsedScript, error) { script := *s - parsedOps := make([]ParsedOp, 0, len(script)) + parsedOps := make([]ParsedOpcode, 0, len(script)) for i := 0; i < len(script); { instruction := script[i] - parsedOp := ParsedOp{Op: opcodeArray[instruction]} + parsedOp := ParsedOpcode{op: opcodeArray[instruction]} if p.ErrorOnCheckSig && parsedOp.RequiresTx() { return nil, errs.NewError(errs.ErrInvalidParams, "tx and previous output must be supplied for checksig") } switch { - case parsedOp.Op.length == 1: + case parsedOp.op.length == 1: i++ - case parsedOp.Op.length > 1: - if len(script[i:]) < parsedOp.Op.length { + case parsedOp.op.length > 1: + if len(script[i:]) < parsedOp.op.length { return nil, errs.NewError(errs.ErrMalformedPush, "opcode %s required %d bytes, script has %d remaining", - parsedOp.Name(), parsedOp.Op.length, len(script[i:])) + parsedOp.Name(), parsedOp.op.length, len(script[i:])) } - parsedOp.Data = script[i+1 : i+parsedOp.Op.length] - i += parsedOp.Op.length - case parsedOp.Op.length < 0: + parsedOp.Data = script[i+1 : i+parsedOp.op.length] + i += parsedOp.op.length + case parsedOp.op.length < 0: var l uint offset := i + 1 - if len(script[offset:]) < -parsedOp.Op.length { + if len(script[offset:]) < -parsedOp.op.length { return nil, errs.NewError(errs.ErrMalformedPush, "opcode %s required %d bytes, script has %d remaining", - parsedOp.Name(), parsedOp.Op.length, len(script[offset:])) + parsedOp.Name(), parsedOp.op.length, len(script[offset:])) } // Next -length bytes are little endian length of data. - switch parsedOp.Op.length { + switch parsedOp.op.length { case -1: l = uint(script[offset]) case -2: @@ -165,17 +175,17 @@ func (p *DefaultOpcodeParser) Parse(s *bscript.Script) (ParsedScript, error) { (uint(script[offset+1]) << 8) | uint(script[offset])) default: - return nil, errs.NewError(errs.ErrMalformedPush, "invalid opcode length %d", parsedOp.Op.length) + return nil, errs.NewError(errs.ErrMalformedPush, "invalid opcode length %d", parsedOp.op.length) } - offset += -parsedOp.Op.length + offset += -parsedOp.op.length if int(l) > len(script[offset:]) || int(l) < 0 { return nil, errs.NewError(errs.ErrMalformedPush, "opcode %s pushes %d bytes, script has %d remaining", parsedOp.Name(), l, len(script[offset:])) } parsedOp.Data = script[offset : offset+int(l)] - i += 1 - parsedOp.Op.length + int(l) + i += 1 - parsedOp.op.length + int(l) } parsedOps = append(parsedOps, parsedOp) @@ -200,7 +210,7 @@ func (p *DefaultOpcodeParser) Unparse(pscr ParsedScript) (*bscript.Script, error // IsPushOnly returns true if the ParsedScript only contains push commands func (p ParsedScript) IsPushOnly() bool { for _, op := range p { - if op.Op.val > bscript.Op16 { + if op.op.val > bscript.Op16 { return false } } @@ -224,7 +234,7 @@ func (p ParsedScript) removeOpcodeByData(data []byte) ParsedScript { func (p ParsedScript) removeOpcode(opcode byte) ParsedScript { retScript := make(ParsedScript, 0, len(p)) for _, pop := range p { - if pop.Op.val != opcode { + if pop.op.val != opcode { retScript = append(retScript, pop) } } @@ -235,8 +245,8 @@ func (p ParsedScript) removeOpcode(opcode byte) ParsedScript { // canonicalPush returns true if the object is either not a push instruction // or the push instruction contained wherein is matches the canonical form // or using the smallest instruction to do the job. False otherwise. -func (o ParsedOp) canonicalPush() bool { - opcode := o.Op.val +func (o ParsedOpcode) canonicalPush() bool { + opcode := o.op.val data := o.Data dataLen := len(o.Data) if opcode > bscript.Op16 { @@ -260,17 +270,17 @@ func (o ParsedOp) canonicalPush() bool { // bytes returns any data associated with the opcode encoded as it would be in // a script. This is used for unparsing scripts from parsed opcodes. -func (o *ParsedOp) bytes() ([]byte, error) { +func (o *ParsedOpcode) bytes() ([]byte, error) { var retbytes []byte - if o.Op.length > 0 { - retbytes = make([]byte, 1, o.Op.length) + if o.op.length > 0 { + retbytes = make([]byte, 1, o.op.length) } else { retbytes = make([]byte, 1, 1+len(o.Data)- - o.Op.length) + o.op.length) } - retbytes[0] = o.Op.val - if o.Op.length == 1 { + retbytes[0] = o.op.val + if o.op.length == 1 { if len(o.Data) != 0 { return nil, errs.NewError( errs.ErrInternal, @@ -280,11 +290,11 @@ func (o *ParsedOp) bytes() ([]byte, error) { } return retbytes, nil } - nbytes := o.Op.length - if o.Op.length < 0 { + nbytes := o.op.length + if o.op.length < 0 { l := len(o.Data) // tempting just to hardcode to avoid the complexity here. - switch o.Op.length { + switch o.op.length { case -1: retbytes = append(retbytes, byte(l)) nbytes = int(retbytes[1]) + len(retbytes) diff --git a/bscript/interpreter/opcodeparser_test.go b/bscript/interpreter/opcodeparser_test.go index 78d5bcf6..13460c7b 100644 --- a/bscript/interpreter/opcodeparser_test.go +++ b/bscript/interpreter/opcodeparser_test.go @@ -19,7 +19,7 @@ func TestOpcodeDisabled(t *testing.T) { tests := []byte{bscript.Op2MUL, bscript.Op2DIV} for _, opcodeVal := range tests { - pop := ParsedOp{Op: opcodeArray[opcodeVal], Data: nil} + pop := ParsedOpcode{op: opcodeArray[opcodeVal], Data: nil} err := opcodeDisabled(&pop, nil) if !errs.IsErrorCode(err, errs.ErrDisabledOpcode) { t.Errorf("opcodeDisabled: unexpected error - got %v, "+ diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 07088d81..4c089fa4 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -27,7 +27,7 @@ type opcode struct { val byte name string length int - exec func(*ParsedOp, *thread) error + exec func(*ParsedOpcode, *thread) error } func (o opcode) Name() string { @@ -325,11 +325,11 @@ var opcodeArray = [256]opcode{ // opcodes before executing in an initial parse step, the consensus rules // dictate the script doesn't fail until the program counter passes over a // disabled opcode (even when they appear in a branch that is not executed). -func opcodeDisabled(op *ParsedOp, t *thread) error { +func opcodeDisabled(op *ParsedOpcode, t *thread) error { return errs.NewError(errs.ErrDisabledOpcode, "attempt to execute disabled opcode %s", op.Name()) } -func opcodeVerConditional(op *ParsedOp, t *thread) error { +func opcodeVerConditional(op *ParsedOpcode, t *thread) error { if t.afterGenesis && !t.shouldExec(*op) { return nil } @@ -338,33 +338,33 @@ func opcodeVerConditional(op *ParsedOp, t *thread) error { // opcodeReserved is a common handler for all reserved opcodes. It returns an // appropriate error indicating the opcode is reserved. -func opcodeReserved(op *ParsedOp, t *thread) error { +func opcodeReserved(op *ParsedOpcode, t *thread) error { return errs.NewError(errs.ErrReservedOpcode, "attempt to execute reserved opcode %s", op.Name()) } // opcodeInvalid is a common handler for all invalid opcodes. It returns an // appropriate error indicating the opcode is invalid. -func opcodeInvalid(op *ParsedOp, t *thread) error { +func opcodeInvalid(op *ParsedOpcode, t *thread) error { return errs.NewError(errs.ErrReservedOpcode, "attempt to execute invalid opcode %s", op.Name()) } // opcodeFalse pushes an empty array to the data stack to represent false. Note // that 0, when encoded as a number according to the numeric encoding consensus // rules, is an empty array. -func opcodeFalse(op *ParsedOp, t *thread) error { +func opcodeFalse(op *ParsedOpcode, t *thread) error { t.dstack.PushByteArray(nil) return nil } // opcodePushData is a common handler for the vast majority of opcodes that push // raw data (bytes) to the data stack. -func opcodePushData(op *ParsedOp, t *thread) error { +func opcodePushData(op *ParsedOpcode, t *thread) error { t.dstack.PushByteArray(op.Data) return nil } // opcode1Negate pushes -1, encoded as a number, to the data stack. -func opcode1Negate(op *ParsedOp, t *thread) error { +func opcode1Negate(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(-1) return nil } @@ -372,25 +372,25 @@ func opcode1Negate(op *ParsedOp, t *thread) error { // opcodeN is a common handler for the small integer data push opcodes. It // pushes the numeric value the opcode represents (which will be from 1 to 16) // onto the data stack. -func opcodeN(op *ParsedOp, t *thread) error { +func opcodeN(op *ParsedOpcode, t *thread) error { // The opcodes are all defined consecutively, so the numeric value is // the difference. - t.dstack.PushInt(scriptNum((op.Op.val - (bscript.Op1 - 1)))) + t.dstack.PushInt(scriptNum((op.op.val - (bscript.Op1 - 1)))) return nil } // opcodeNop is a common handler for the NOP family of opcodes. As the name // implies it generally does nothing, however, it will return an error when // the flag to discourage use of NOPs is set for select opcodes. -func opcodeNop(op *ParsedOp, t *thread) error { - switch op.Op.val { +func opcodeNop(op *ParsedOpcode, t *thread) error { + switch op.op.val { case bscript.OpNOP1, bscript.OpNOP4, bscript.OpNOP5, bscript.OpNOP6, bscript.OpNOP7, bscript.OpNOP8, bscript.OpNOP9, bscript.OpNOP10: if t.hasFlag(scriptflag.DiscourageUpgradableNops) { return errs.NewError( errs.ErrDiscourageUpgradableNOPs, "bscript.OpNOP%d reserved for soft-fork upgrades", - op.Op.val-(bscript.OpNOP1-1), + op.op.val-(bscript.OpNOP1-1), ) } } @@ -434,7 +434,7 @@ func popIfBool(t *thread) (bool, error) { // // Data stack transformation: [... bool] -> [...] // Conditional stack transformation: [...] -> [... OpCondValue] -func opcodeIf(op *ParsedOp, t *thread) error { +func opcodeIf(op *ParsedOpcode, t *thread) error { condVal := opCondFalse if t.shouldExec(*op) { if t.isBranchExecuting() { @@ -472,7 +472,7 @@ func opcodeIf(op *ParsedOp, t *thread) error { // // Data stack transformation: [... bool] -> [...] // Conditional stack transformation: [...] -> [... OpCondValue] -func opcodeNotIf(op *ParsedOp, t *thread) error { +func opcodeNotIf(op *ParsedOpcode, t *thread) error { condVal := opCondFalse if t.shouldExec(*op) { if t.isBranchExecuting() { @@ -499,7 +499,7 @@ func opcodeNotIf(op *ParsedOp, t *thread) error { // An error is returned if there has not already been a matching bscript.OpIF. // // Conditional stack transformation: [... OpCondValue] -> [... !OpCondValue] -func opcodeElse(op *ParsedOp, t *thread) error { +func opcodeElse(op *ParsedOpcode, t *thread) error { if len(t.condStack) == 0 { return errs.NewError(errs.ErrUnbalancedConditional, "encountered opcode %s with no matching opcode to begin conditional execution", op.Name()) @@ -536,7 +536,7 @@ func opcodeElse(op *ParsedOp, t *thread) error { // An error is returned if there has not already been a matching bscript.OpIF. // // Conditional stack transformation: [... OpCondValue] -> [...] -func opcodeEndif(op *ParsedOp, t *thread) error { +func opcodeEndif(op *ParsedOpcode, t *thread) error { if len(t.condStack) == 0 { return errs.NewError(errs.ErrUnbalancedConditional, "encountered opcode %s with no matching opcode to begin conditional execution", op.Name()) @@ -555,7 +555,7 @@ func opcodeEndif(op *ParsedOp, t *thread) error { // item on the stack or when that item evaluates to false. In the latter case // where the verification fails specifically due to the top item evaluating // to false, the returned error will use the passed error code. -func abstractVerify(op *ParsedOp, t *thread, c errs.ErrorCode) error { +func abstractVerify(op *ParsedOpcode, t *thread, c errs.ErrorCode) error { verified, err := t.dstack.PopBool() if err != nil { return err @@ -569,13 +569,13 @@ func abstractVerify(op *ParsedOp, t *thread, c errs.ErrorCode) error { // opcodeVerify examines the top item on the data stack as a boolean value and // verifies it evaluates to true. An error is returned if it does not. -func opcodeVerify(op *ParsedOp, t *thread) error { +func opcodeVerify(op *ParsedOpcode, t *thread) error { return abstractVerify(op, t, errs.ErrVerify) } // opcodeReturn returns an appropriate error since it is always an error to // return early from a script. -func opcodeReturn(op *ParsedOp, t *thread) error { +func opcodeReturn(op *ParsedOpcode, t *thread) error { if !t.afterGenesis { return errs.NewError(errs.ErrEarlyReturn, "script returned early") } @@ -614,7 +614,7 @@ func verifyLockTime(txLockTime, threshold, lockTime int64) error { // validating if the transaction outputs are spendable yet. If flag // ScriptVerifyCheckLockTimeVerify is not set, the code continues as if bscript.OpNOP2 // were executed. -func opcodeCheckLockTimeVerify(op *ParsedOp, t *thread) error { +func opcodeCheckLockTimeVerify(op *ParsedOpcode, t *thread) error { // If the ScriptVerifyCheckLockTimeVerify script flag is not set, treat // opcode as bscript.OpNOP2 instead. if !t.hasFlag(scriptflag.VerifyCheckLockTimeVerify) || t.afterGenesis { @@ -684,7 +684,7 @@ func opcodeCheckLockTimeVerify(op *ParsedOp, t *thread) error { // validating if the transaction outputs are spendable yet. If flag // ScriptVerifyCheckSequenceVerify is not set, the code continues as if bscript.OpNOP3 // were executed. -func opcodeCheckSequenceVerify(op *ParsedOp, t *thread) error { +func opcodeCheckSequenceVerify(op *ParsedOpcode, t *thread) error { // If the ScriptVerifyCheckSequenceVerify script flag is not set, treat // opcode as bscript.OpNOP3 instead. if !t.hasFlag(scriptflag.VerifyCheckSequenceVerify) || t.afterGenesis { @@ -756,7 +756,7 @@ func opcodeCheckSequenceVerify(op *ParsedOp, t *thread) error { // // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2 y3 x3] -func opcodeToAltStack(op *ParsedOp, t *thread) error { +func opcodeToAltStack(op *ParsedOpcode, t *thread) error { so, err := t.dstack.PopByteArray() if err != nil { return err @@ -772,7 +772,7 @@ func opcodeToAltStack(op *ParsedOp, t *thread) error { // // Main data stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 y3] // Alt data stack transformation: [... y1 y2 y3] -> [... y1 y2] -func opcodeFromAltStack(op *ParsedOp, t *thread) error { +func opcodeFromAltStack(op *ParsedOpcode, t *thread) error { so, err := t.astack.PopByteArray() if err != nil { return err @@ -786,35 +786,35 @@ func opcodeFromAltStack(op *ParsedOp, t *thread) error { // opcode2Drop removes the top 2 items from the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1] -func opcode2Drop(op *ParsedOp, t *thread) error { +func opcode2Drop(op *ParsedOpcode, t *thread) error { return t.dstack.DropN(2) } // opcode2Dup duplicates the top 2 items on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2 x3] -func opcode2Dup(op *ParsedOp, t *thread) error { +func opcode2Dup(op *ParsedOpcode, t *thread) error { return t.dstack.DupN(2) } // opcode3Dup duplicates the top 3 items on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x1 x2 x3] -func opcode3Dup(op *ParsedOp, t *thread) error { +func opcode3Dup(op *ParsedOpcode, t *thread) error { return t.dstack.DupN(3) } // opcode2Over duplicates the 2 items before the top 2 items on the data stack. // // Stack transformation: [... x1 x2 x3 x4] -> [... x1 x2 x3 x4 x1 x2] -func opcode2Over(op *ParsedOp, t *thread) error { +func opcode2Over(op *ParsedOpcode, t *thread) error { return t.dstack.OverN(2) } // opcode2Rot rotates the top 6 items on the data stack to the left twice. // // Stack transformation: [... x1 x2 x3 x4 x5 x6] -> [... x3 x4 x5 x6 x1 x2] -func opcode2Rot(op *ParsedOp, t *thread) error { +func opcode2Rot(op *ParsedOpcode, t *thread) error { return t.dstack.RotN(2) } @@ -822,7 +822,7 @@ func opcode2Rot(op *ParsedOp, t *thread) error { // before them. // // Stack transformation: [... x1 x2 x3 x4] -> [... x3 x4 x1 x2] -func opcode2Swap(op *ParsedOp, t *thread) error { +func opcode2Swap(op *ParsedOpcode, t *thread) error { return t.dstack.SwapN(2) } @@ -830,7 +830,7 @@ func opcode2Swap(op *ParsedOp, t *thread) error { // // Stack transformation (x1==0): [... x1] -> [... x1] // Stack transformation (x1!=0): [... x1] -> [... x1 x1] -func opcodeIfDup(op *ParsedOp, t *thread) error { +func opcodeIfDup(op *ParsedOpcode, t *thread) error { so, err := t.dstack.PeekByteArray(0) if err != nil { return err @@ -850,7 +850,7 @@ func opcodeIfDup(op *ParsedOp, t *thread) error { // Stack transformation: [...] -> [... ] // Example with 2 items: [x1 x2] -> [x1 x2 2] // Example with 3 items: [x1 x2 x3] -> [x1 x2 x3 3] -func opcodeDepth(op *ParsedOp, t *thread) error { +func opcodeDepth(op *ParsedOpcode, t *thread) error { t.dstack.PushInt(scriptNum(t.dstack.Depth())) return nil } @@ -858,28 +858,28 @@ func opcodeDepth(op *ParsedOp, t *thread) error { // opcodeDrop removes the top item from the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2] -func opcodeDrop(op *ParsedOp, t *thread) error { +func opcodeDrop(op *ParsedOpcode, t *thread) error { return t.dstack.DropN(1) } // opcodeDup duplicates the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x3] -func opcodeDup(op *ParsedOp, t *thread) error { +func opcodeDup(op *ParsedOpcode, t *thread) error { return t.dstack.DupN(1) } // opcodeNip removes the item before the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x3] -func opcodeNip(op *ParsedOp, t *thread) error { +func opcodeNip(op *ParsedOpcode, t *thread) error { return t.dstack.NipN(1) } // opcodeOver duplicates the item before the top item on the data stack. // // Stack transformation: [... x1 x2 x3] -> [... x1 x2 x3 x2] -func opcodeOver(op *ParsedOp, t *thread) error { +func opcodeOver(op *ParsedOpcode, t *thread) error { return t.dstack.OverN(1) } @@ -889,7 +889,7 @@ func opcodeOver(op *ParsedOp, t *thread) error { // Stack transformation: [xn ... x2 x1 x0 n] -> [xn ... x2 x1 x0 xn] // Example with n=1: [x2 x1 x0 1] -> [x2 x1 x0 x1] // Example with n=2: [x2 x1 x0 2] -> [x2 x1 x0 x2] -func opcodePick(op *ParsedOp, t *thread) error { +func opcodePick(op *ParsedOpcode, t *thread) error { val, err := t.dstack.PopInt() if err != nil { return err @@ -904,7 +904,7 @@ func opcodePick(op *ParsedOp, t *thread) error { // Stack transformation: [xn ... x2 x1 x0 n] -> [... x2 x1 x0 xn] // Example with n=1: [x2 x1 x0 1] -> [x2 x0 x1] // Example with n=2: [x2 x1 x0 2] -> [x1 x0 x2] -func opcodeRoll(op *ParsedOp, t *thread) error { +func opcodeRoll(op *ParsedOpcode, t *thread) error { val, err := t.dstack.PopInt() if err != nil { return err @@ -916,14 +916,14 @@ func opcodeRoll(op *ParsedOp, t *thread) error { // opcodeRot rotates the top 3 items on the data stack to the left. // // Stack transformation: [... x1 x2 x3] -> [... x2 x3 x1] -func opcodeRot(op *ParsedOp, t *thread) error { +func opcodeRot(op *ParsedOpcode, t *thread) error { return t.dstack.RotN(1) } // opcodeSwap swaps the top two items on the stack. // // Stack transformation: [... x1 x2] -> [... x2 x1] -func opcodeSwap(op *ParsedOp, t *thread) error { +func opcodeSwap(op *ParsedOpcode, t *thread) error { return t.dstack.SwapN(1) } @@ -931,7 +931,7 @@ func opcodeSwap(op *ParsedOp, t *thread) error { // second-to-top item. // // Stack transformation: [... x1 x2] -> [... x2 x1 x2] -func opcodeTuck(op *ParsedOp, t *thread) error { +func opcodeTuck(op *ParsedOpcode, t *thread) error { return t.dstack.Tuck() } @@ -939,7 +939,7 @@ func opcodeTuck(op *ParsedOp, t *thread) error { // not be larger than MaxScriptElementSize. // // Stack transformation: {Ox11} {0x22, 0x33} bscript.OpCAT -> 0x112233 -func opcodeCat(op *ParsedOp, t *thread) error { +func opcodeCat(op *ParsedOpcode, t *thread) error { b, err := t.dstack.PopByteArray() if err != nil { return err @@ -964,7 +964,7 @@ func opcodeCat(op *ParsedOp, t *thread) error { // This operation is the exact inverse of bscript.OpCAT // // Stack transformation: x n bscript.OpSPLIT -> x1 x2 -func opcodeSplit(op *ParsedOp, t *thread) error { +func opcodeSplit(op *ParsedOpcode, t *thread) error { n, err := t.dstack.PopInt() if err != nil { return err @@ -995,7 +995,7 @@ func opcodeSplit(op *ParsedOp, t *thread) error { // produced uses the little-endian encoding. // // Stack transformation: a b bscript.OpNUM2BIN -> x -func opcodeNum2bin(op *ParsedOp, t *thread) error { +func opcodeNum2bin(op *ParsedOpcode, t *thread) error { n, err := t.dstack.PopInt() if err != nil { return err @@ -1048,7 +1048,7 @@ func opcodeNum2bin(op *ParsedOp, t *thread) error { // value in little-endian encoding. // // Stack transformation: a bscript.OpBIN2NUM -> x -func opcodeBin2num(op *ParsedOp, t *thread) error { +func opcodeBin2num(op *ParsedOpcode, t *thread) error { a, err := t.dstack.PopByteArray() if err != nil { return err @@ -1070,7 +1070,7 @@ func opcodeBin2num(op *ParsedOp, t *thread) error { // stack. // // Stack transformation: [... x1] -> [... x1 len(x1)] -func opcodeSize(op *ParsedOp, t *thread) error { +func opcodeSize(op *ParsedOpcode, t *thread) error { so, err := t.dstack.PeekByteArray(0) if err != nil { return err @@ -1083,7 +1083,7 @@ func opcodeSize(op *ParsedOp, t *thread) error { // opcodeInvert flips all of the top stack item's bits // // Stack transformation: a -> ~a -func opcodeInvert(op *ParsedOp, t *thread) error { +func opcodeInvert(op *ParsedOpcode, t *thread) error { ba, err := t.dstack.PeekByteArray(0) if err != nil { return err @@ -1099,7 +1099,7 @@ func opcodeInvert(op *ParsedOp, t *thread) error { // opcodeAnd executes a boolean and between each bit in the operands // // Stack transformation: x1 x2 bscript.OpAND -> out -func opcodeAnd(op *ParsedOp, t *thread) error { //nolint:dupl // to keep functionality with function signature +func opcodeAnd(op *ParsedOpcode, t *thread) error { //nolint:dupl // to keep functionality with function signature a, err := t.dstack.PopByteArray() if err != nil { return err @@ -1126,7 +1126,7 @@ func opcodeAnd(op *ParsedOp, t *thread) error { //nolint:dupl // to keep functio // opcodeOr executes a boolean or between each bit in the operands // // Stack transformation: x1 x2 bscript.OpOR -> out -func opcodeOr(op *ParsedOp, t *thread) error { //nolint:dupl // to keep functionality with function signature +func opcodeOr(op *ParsedOpcode, t *thread) error { //nolint:dupl // to keep functionality with function signature a, err := t.dstack.PopByteArray() if err != nil { return err @@ -1153,7 +1153,7 @@ func opcodeOr(op *ParsedOp, t *thread) error { //nolint:dupl // to keep function // opcodeXor executes a boolean xor between each bit in the operands // // Stack transformation: x1 x2 bscript.OpXOR -> out -func opcodeXor(op *ParsedOp, t *thread) error { //nolint:dupl // to keep functionality with function signature +func opcodeXor(op *ParsedOpcode, t *thread) error { //nolint:dupl // to keep functionality with function signature a, err := t.dstack.PopByteArray() if err != nil { return err @@ -1181,7 +1181,7 @@ func opcodeXor(op *ParsedOp, t *thread) error { //nolint:dupl // to keep functio // bytes, and pushes the result, encoded as a boolean, back to the stack. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeEqual(op *ParsedOp, t *thread) error { +func opcodeEqual(op *ParsedOpcode, t *thread) error { a, err := t.dstack.PopByteArray() if err != nil { return err @@ -1203,7 +1203,7 @@ func opcodeEqual(op *ParsedOp, t *thread) error { // evaluates to true. An error is returned if it does not. // // Stack transformation: [... x1 x2] -> [... bool] -> [...] -func opcodeEqualVerify(op *ParsedOp, t *thread) error { +func opcodeEqualVerify(op *ParsedOpcode, t *thread) error { if err := opcodeEqual(op, t); err != nil { return err } @@ -1215,7 +1215,7 @@ func opcodeEqualVerify(op *ParsedOp, t *thread) error { // it with its incremented value (plus 1). // // Stack transformation: [... x1 x2] -> [... x1 x2+1] -func opcode1Add(op *ParsedOp, t *thread) error { +func opcode1Add(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1229,7 +1229,7 @@ func opcode1Add(op *ParsedOp, t *thread) error { // it with its decremented value (minus 1). // // Stack transformation: [... x1 x2] -> [... x1 x2-1] -func opcode1Sub(op *ParsedOp, t *thread) error { +func opcode1Sub(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1243,7 +1243,7 @@ func opcode1Sub(op *ParsedOp, t *thread) error { // it with its negation. // // Stack transformation: [... x1 x2] -> [... x1 -x2] -func opcodeNegate(op *ParsedOp, t *thread) error { +func opcodeNegate(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1257,7 +1257,7 @@ func opcodeNegate(op *ParsedOp, t *thread) error { // it with its absolute value. // // Stack transformation: [... x1 x2] -> [... x1 abs(x2)] -func opcodeAbs(op *ParsedOp, t *thread) error { +func opcodeAbs(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1283,7 +1283,7 @@ func opcodeAbs(op *ParsedOp, t *thread) error { // Stack transformation (x2==0): [... x1 0] -> [... x1 1] // Stack transformation (x2!=0): [... x1 1] -> [... x1 0] // Stack transformation (x2!=0): [... x1 17] -> [... x1 0] -func opcodeNot(op *ParsedOp, t *thread) error { +func opcodeNot(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1304,7 +1304,7 @@ func opcodeNot(op *ParsedOp, t *thread) error { // Stack transformation (x2==0): [... x1 0] -> [... x1 0] // Stack transformation (x2!=0): [... x1 1] -> [... x1 1] // Stack transformation (x2!=0): [... x1 17] -> [... x1 1] -func opcode0NotEqual(op *ParsedOp, t *thread) error { +func opcode0NotEqual(op *ParsedOpcode, t *thread) error { m, err := t.dstack.PopInt() if err != nil { return err @@ -1322,7 +1322,7 @@ func opcode0NotEqual(op *ParsedOp, t *thread) error { // them with their sum. // // Stack transformation: [... x1 x2] -> [... x1+x2] -func opcodeAdd(op *ParsedOp, t *thread) error { +func opcodeAdd(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1342,7 +1342,7 @@ func opcodeAdd(op *ParsedOp, t *thread) error { // entry. // // Stack transformation: [... x1 x2] -> [... x1-x2] -func opcodeSub(op *ParsedOp, t *thread) error { +func opcodeSub(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1360,7 +1360,7 @@ func opcodeSub(op *ParsedOp, t *thread) error { // opcodeMul treats the top two items on the data stack as integers and replaces // them with the result of subtracting the top entry from the second-to-top // entry. -func opcodeMul(op *ParsedOp, t *thread) error { +func opcodeMul(op *ParsedOpcode, t *thread) error { n1, err := t.dstack.PopInt() if err != nil { return err @@ -1381,7 +1381,7 @@ func opcodeMul(op *ParsedOp, t *thread) error { // would be a non-integer it is rounded towards zero. // // Stack transformation: a b bscript.OpDIV -> out -func opcodeDiv(op *ParsedOp, t *thread) error { +func opcodeDiv(op *ParsedOpcode, t *thread) error { b, err := t.dstack.PopInt() if err != nil { return err @@ -1404,7 +1404,7 @@ func opcodeDiv(op *ParsedOp, t *thread) error { // be represented using the least number of bytes required. // // Stack transformation: a b bscript.OpMOD -> out -func opcodeMod(op *ParsedOp, t *thread) error { +func opcodeMod(op *ParsedOpcode, t *thread) error { b, err := t.dstack.PopInt() if err != nil { return err @@ -1423,7 +1423,7 @@ func opcodeMod(op *ParsedOp, t *thread) error { return nil } -func opcodeLShift(op *ParsedOp, t *thread) error { +func opcodeLShift(op *ParsedOpcode, t *thread) error { n, err := t.dstack.PopInt() if err != nil { return err @@ -1448,7 +1448,7 @@ func opcodeLShift(op *ParsedOp, t *thread) error { return nil } -func opcodeRShift(op *ParsedOp, t *thread) error { +func opcodeRShift(op *ParsedOpcode, t *thread) error { n, err := t.dstack.PopInt() if err != nil { return err @@ -1480,7 +1480,7 @@ func opcodeRShift(op *ParsedOp, t *thread) error { // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 0] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 0] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] -func opcodeBoolAnd(op *ParsedOp, t *thread) error { +func opcodeBoolAnd(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1507,7 +1507,7 @@ func opcodeBoolAnd(op *ParsedOp, t *thread) error { // Stack transformation (x1!=0, x2==0): [... 5 0] -> [... 1] // Stack transformation (x1==0, x2!=0): [... 0 7] -> [... 1] // Stack transformation (x1!=0, x2!=0): [... 4 8] -> [... 1] -func opcodeBoolOr(op *ParsedOp, t *thread) error { +func opcodeBoolOr(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1532,7 +1532,7 @@ func opcodeBoolOr(op *ParsedOp, t *thread) error { // // Stack transformation (x1==x2): [... 5 5] -> [... 1] // Stack transformation (x1!=x2): [... 5 7] -> [... 0] -func opcodeNumEqual(op *ParsedOp, t *thread) error { +func opcodeNumEqual(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1560,7 +1560,7 @@ func opcodeNumEqual(op *ParsedOp, t *thread) error { // to true. An error is returned if it does not. // // Stack transformation: [... x1 x2] -> [... bool] -> [...] -func opcodeNumEqualVerify(op *ParsedOp, t *thread) error { +func opcodeNumEqualVerify(op *ParsedOpcode, t *thread) error { if err := opcodeNumEqual(op, t); err != nil { return err } @@ -1573,7 +1573,7 @@ func opcodeNumEqualVerify(op *ParsedOp, t *thread) error { // // Stack transformation (x1==x2): [... 5 5] -> [... 0] // Stack transformation (x1!=x2): [... 5 7] -> [... 1] -func opcodeNumNotEqual(op *ParsedOp, t *thread) error { +func opcodeNumNotEqual(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1598,7 +1598,7 @@ func opcodeNumNotEqual(op *ParsedOp, t *thread) error { // otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeLessThan(op *ParsedOp, t *thread) error { +func opcodeLessThan(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1623,7 +1623,7 @@ func opcodeLessThan(op *ParsedOp, t *thread) error { // with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeGreaterThan(op *ParsedOp, t *thread) error { +func opcodeGreaterThan(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1648,7 +1648,7 @@ func opcodeGreaterThan(op *ParsedOp, t *thread) error { // replaced with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeLessThanOrEqual(op *ParsedOp, t *thread) error { +func opcodeLessThanOrEqual(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1673,7 +1673,7 @@ func opcodeLessThanOrEqual(op *ParsedOp, t *thread) error { // item, they are replaced with a 1, otherwise a 0. // // Stack transformation: [... x1 x2] -> [... bool] -func opcodeGreaterThanOrEqual(op *ParsedOp, t *thread) error { +func opcodeGreaterThanOrEqual(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1697,7 +1697,7 @@ func opcodeGreaterThanOrEqual(op *ParsedOp, t *thread) error { // them with the minimum of the two. // // Stack transformation: [... x1 x2] -> [... min(x1, x2)] -func opcodeMin(op *ParsedOp, t *thread) error { +func opcodeMin(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1721,7 +1721,7 @@ func opcodeMin(op *ParsedOp, t *thread) error { // them with the maximum of the two. // // Stack transformation: [... x1 x2] -> [... max(x1, x2)] -func opcodeMax(op *ParsedOp, t *thread) error { +func opcodeMax(op *ParsedOpcode, t *thread) error { v0, err := t.dstack.PopInt() if err != nil { return err @@ -1749,7 +1749,7 @@ func opcodeMax(op *ParsedOp, t *thread) error { // the third-to-top item is the value to test. // // Stack transformation: [... x1 min max] -> [... bool] -func opcodeWithin(op *ParsedOp, t *thread) error { +func opcodeWithin(op *ParsedOpcode, t *thread) error { maxVal, err := t.dstack.PopInt() if err != nil { return err @@ -1784,7 +1784,7 @@ func calcHash(buf []byte, hasher hash.Hash) []byte { // replaces it with ripemd160(data). // // Stack transformation: [... x1] -> [... ripemd160(x1)] -func opcodeRipemd160(op *ParsedOp, t *thread) error { +func opcodeRipemd160(op *ParsedOpcode, t *thread) error { buf, err := t.dstack.PopByteArray() if err != nil { return err @@ -1798,7 +1798,7 @@ func opcodeRipemd160(op *ParsedOp, t *thread) error { // with sha1(data). // // Stack transformation: [... x1] -> [... sha1(x1)] -func opcodeSha1(op *ParsedOp, t *thread) error { +func opcodeSha1(op *ParsedOpcode, t *thread) error { buf, err := t.dstack.PopByteArray() if err != nil { return err @@ -1813,7 +1813,7 @@ func opcodeSha1(op *ParsedOp, t *thread) error { // it with sha256(data). // // Stack transformation: [... x1] -> [... sha256(x1)] -func opcodeSha256(op *ParsedOp, t *thread) error { +func opcodeSha256(op *ParsedOpcode, t *thread) error { buf, err := t.dstack.PopByteArray() if err != nil { return err @@ -1828,7 +1828,7 @@ func opcodeSha256(op *ParsedOp, t *thread) error { // it with ripemd160(sha256(data)). // // Stack transformation: [... x1] -> [... ripemd160(sha256(x1))] -func opcodeHash160(op *ParsedOp, t *thread) error { +func opcodeHash160(op *ParsedOpcode, t *thread) error { buf, err := t.dstack.PopByteArray() if err != nil { return err @@ -1843,7 +1843,7 @@ func opcodeHash160(op *ParsedOp, t *thread) error { // it with sha256(sha256(data)). // // Stack transformation: [... x1] -> [... sha256(sha256(x1))] -func opcodeHash256(op *ParsedOp, t *thread) error { +func opcodeHash256(op *ParsedOpcode, t *thread) error { buf, err := t.dstack.PopByteArray() if err != nil { return err @@ -1857,7 +1857,7 @@ func opcodeHash256(op *ParsedOp, t *thread) error { // seen bscript.OpCODESEPARATOR which is used during signature checking. // // This opcode does not change the contents of the data stack. -func opcodeCodeSeparator(op *ParsedOp, t *thread) error { +func opcodeCodeSeparator(op *ParsedOpcode, t *thread) error { t.lastCodeSep = t.scriptOff return nil } @@ -1876,7 +1876,7 @@ func opcodeCodeSeparator(op *ParsedOp, t *thread) error { // cryptographic methods against the provided public key. // // Stack transformation: [... signature pubkey] -> [... bool] -func opcodeCheckSig(op *ParsedOp, t *thread) error { +func opcodeCheckSig(op *ParsedOpcode, t *thread) error { pkBytes, err := t.dstack.PopByteArray() if err != nil { return err @@ -1977,7 +1977,7 @@ func opcodeCheckSig(op *ParsedOp, t *thread) error { // documentation for each of those opcodes for more details. // // Stack transformation: signature pubkey] -> [... bool] -> [...] -func opcodeCheckSigVerify(op *ParsedOp, t *thread) error { +func opcodeCheckSigVerify(op *ParsedOpcode, t *thread) error { if err := opcodeCheckSig(op, t); err != nil { return err } @@ -2013,7 +2013,7 @@ type parsedSigInfo struct { // // Stack transformation: // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -func opcodeCheckMultiSig(op *ParsedOp, t *thread) error { +func opcodeCheckMultiSig(op *ParsedOpcode, t *thread) error { numKeys, err := t.dstack.PopInt() if err != nil { return err @@ -2212,7 +2212,7 @@ func opcodeCheckMultiSig(op *ParsedOp, t *thread) error { // // Stack transformation: // [... dummy [sig ...] numsigs [pubkey ...] numpubkeys] -> [... bool] -> [...] -func opcodeCheckMultiSigVerify(op *ParsedOp, t *thread) error { +func opcodeCheckMultiSigVerify(op *ParsedOpcode, t *thread) error { if err := opcodeCheckMultiSig(op, t); err != nil { return err } diff --git a/bscript/interpreter/options.go b/bscript/interpreter/options.go index 6c776945..df90b1c2 100644 --- a/bscript/interpreter/options.go +++ b/bscript/interpreter/options.go @@ -12,44 +12,68 @@ type ExecutionOptionFunc func(p *execOpts) // WithTx configure the execution to run again a tx. func WithTx(tx *bt.Tx, inputIdx int, prevOutput *bt.Output) ExecutionOptionFunc { return func(p *execOpts) { - p.Tx = tx - p.PreviousTxOut = prevOutput - p.InputIdx = inputIdx + p.tx = tx + p.previousTxOut = prevOutput + p.inputIdx = inputIdx } } // WithScripts configure the execution to run again a set of *bscript.Script. func WithScripts(lockingScript *bscript.Script, unlockingScript *bscript.Script) ExecutionOptionFunc { return func(p *execOpts) { - p.LockingScript = lockingScript - p.UnlockingScript = unlockingScript + p.lockingScript = lockingScript + p.unlockingScript = unlockingScript } } // WithAfterGenesis configure the execution to operate in an after-genesis context. func WithAfterGenesis() ExecutionOptionFunc { return func(p *execOpts) { - p.Flags.AddFlag(scriptflag.UTXOAfterGenesis) + p.flags.AddFlag(scriptflag.UTXOAfterGenesis) } } // WithForkID configure the execution to allow a tx with a fork id. func WithForkID() ExecutionOptionFunc { return func(p *execOpts) { - p.Flags.AddFlag(scriptflag.EnableSighashForkID) + p.flags.AddFlag(scriptflag.EnableSighashForkID) } } // WithP2SH configure the execution to allow a P2SH output. func WithP2SH() ExecutionOptionFunc { return func(p *execOpts) { - p.Flags.AddFlag(scriptflag.Bip16) + p.flags.AddFlag(scriptflag.Bip16) } } // WithFlags configure the execution with the provided flags. func WithFlags(flags scriptflag.Flag) ExecutionOptionFunc { return func(p *execOpts) { - p.Flags.AddFlag(flags) + p.flags.AddFlag(flags) + } +} + +// WithDebugger enable execution debugging with the provided configured debugger. +// It is important to note that when this setting is applied, it enables thread +// state cloning, at every configured debug step. +func WithDebugger(debugger Debugger) ExecutionOptionFunc { + return func(p *execOpts) { + p.debugger = debugger + } +} + +// WithState inject the provided state into the execution thread. This assumes +// that the state is correct for the scripts provided. +// +// NOTE: This is highly experimental and is unstable when used with unintended states, +// and likely still when used in a happy path scenario. Therefore, it is recommended +// to only be used for debugging purposes. +// +// The safest recommended *interpreter.State records for a given script can be +// are those which can be captured during `debugger.BeforeStep` and `debugger.AfterStep`. +func WithState(state *State) ExecutionOptionFunc { + return func(p *execOpts) { + p.state = state } } diff --git a/bscript/interpreter/stack.go b/bscript/interpreter/stack.go index 7bb3bb5e..ec2e838f 100644 --- a/bscript/interpreter/stack.go +++ b/bscript/interpreter/stack.go @@ -40,12 +40,16 @@ type stack struct { stk [][]byte maxNumLength int verifyMinimalData bool + debug Debugger + sh StateHandler } func newStack(cfg config, verifyMinimalData bool) stack { return stack{ maxNumLength: cfg.MaxScriptNumberLength(), verifyMinimalData: verifyMinimalData, + debug: &nopDebugger{}, + sh: &nopStateHandler{}, } } @@ -58,6 +62,8 @@ func (s *stack) Depth() int32 { // // Stack transformation: [... x1 x2] -> [... x1 x2 data] func (s *stack) PushByteArray(so []byte) { + defer s.afterStackPush(so) + s.beforeStackPush(so) s.stk = append(s.stk, so) } @@ -81,7 +87,13 @@ func (s *stack) PushBool(val bool) { // // Stack transformation: [... x1 x2 x3] -> [... x1 x2] func (s *stack) PopByteArray() ([]byte, error) { - return s.nipN(0) + s.beforeStackPop() + data, err := s.nipN(0) + if err != nil { + return nil, err + } + s.afterStackPop(data) + return data, nil } // PopInt pops the value off the top of the stack, converts it into a script @@ -359,10 +371,27 @@ func (s *stack) String() string { return result } +func (s *stack) beforeStackPush(bb []byte) { + s.debug.BeforeStackPush(s.sh.State(), bb) +} + +func (s *stack) afterStackPush(bb []byte) { + s.debug.AfterStackPush(s.sh.State(), bb) +} + +func (s *stack) beforeStackPop() { + s.debug.BeforeStackPop(s.sh.State()) +} + +func (s *stack) afterStackPop(bb []byte) { + s.debug.AfterStackPop(s.sh.State(), bb) +} + type boolStack interface { PushBool(b bool) PopBool() (bool, error) PeekBool(int32) (bool, error) + Depth() int32 } type nopBoolStack struct{} @@ -376,3 +405,7 @@ func (n *nopBoolStack) PopBool() (bool, error) { func (n *nopBoolStack) PeekBool(int32) (bool, error) { return false, nil } + +func (n *nopBoolStack) Depth() int32 { + return 0 +} diff --git a/bscript/interpreter/state.go b/bscript/interpreter/state.go new file mode 100644 index 00000000..a538ba35 --- /dev/null +++ b/bscript/interpreter/state.go @@ -0,0 +1,135 @@ +package interpreter + +import "github.com/libsv/go-bt/v2/bscript/interpreter/scriptflag" + +// State a snapshot of a threads state during execution. +type State struct { + DataStack [][]byte + AltStack [][]byte + ElseStack [][]byte + CondStack []int + SavedFirstStack [][]byte + Scripts []ParsedScript + ScriptIdx int + OpcodeIdx int + LastCodeSeperatorIdx int + NumOps int + Flags scriptflag.Flag + IsFinished bool + Genesis struct { + AfterGenesis bool + EarlyReturn bool + } +} + +// Opcode the current interpreter.ParsedOpcode from the +// threads program counter. +func (s *State) Opcode() ParsedOpcode { + return s.Scripts[s.ScriptIdx][s.OpcodeIdx] +} + +// RemainingScript the remaining script to be executed. +func (s *State) RemainingScript() ParsedScript { + return s.Scripts[s.ScriptIdx][s.OpcodeIdx:] +} + +// StateHandler interfaces getting and applying state. +type StateHandler interface { + State() *State + SetState(state *State) +} + +type nopStateHandler struct{} + +func (n *nopStateHandler) State() *State { + return &State{} +} +func (n *nopStateHandler) SetState(state *State) {} + +func (t *thread) State() *State { + scriptIdx := t.scriptIdx + offsetIdx := t.scriptOff + if scriptIdx >= len(t.scripts) { + scriptIdx = len(t.scripts) - 1 + offsetIdx = len(t.scripts[scriptIdx]) - 1 + } + + if offsetIdx >= len(t.scripts[scriptIdx]) { + offsetIdx = len(t.scripts[scriptIdx]) - 1 + } + ts := State{ + DataStack: make([][]byte, int(t.dstack.Depth())), + AltStack: make([][]byte, int(t.astack.Depth())), + ElseStack: make([][]byte, int(t.elseStack.Depth())), + CondStack: make([]int, len(t.condStack)), + SavedFirstStack: make([][]byte, len(t.savedFirstStack)), + Scripts: make([]ParsedScript, len(t.scripts)), + ScriptIdx: scriptIdx, + OpcodeIdx: offsetIdx, + LastCodeSeperatorIdx: t.lastCodeSep, + NumOps: t.numOps, + Flags: t.flags, + IsFinished: t.scriptIdx > scriptIdx, + Genesis: struct { + AfterGenesis bool + EarlyReturn bool + }{ + AfterGenesis: t.afterGenesis, + EarlyReturn: t.earlyReturnAfterGenesis, + }, + } + + for i, dd := range t.dstack.stk { + ts.DataStack[i] = make([]byte, len(dd)) + copy(ts.DataStack[i], dd) + } + + for i, aa := range t.astack.stk { + ts.AltStack[i] = make([]byte, len(aa)) + copy(ts.AltStack[i], aa) + } + + if stk, ok := t.elseStack.(*stack); ok { + for i, ee := range stk.stk { + ts.ElseStack[i] = make([]byte, len(ee)) + copy(ts.ElseStack[i], ee) + } + } + + for i, ss := range t.savedFirstStack { + ts.SavedFirstStack[i] = make([]byte, len(ss)) + copy(ts.SavedFirstStack[i], ss) + } + + copy(ts.CondStack, t.condStack) + + for i, script := range t.scripts { + ts.Scripts[i] = make(ParsedScript, len(script)) + copy(ts.Scripts[i], script) + } + + return &ts +} + +func (t *thread) SetState(state *State) { + setStack(&t.dstack, state.DataStack) + setStack(&t.astack, state.AltStack) + t.elseStack = &nopBoolStack{} + if state.Genesis.AfterGenesis { + es := &stack{debug: &nopDebugger{}, sh: &nopStateHandler{}} + setStack(es, state.ElseStack) + t.elseStack = es + } + t.condStack = make([]int, len(state.CondStack)) + copy(t.condStack, state.CondStack) + t.savedFirstStack = state.SavedFirstStack + + t.scripts = state.Scripts + t.scriptIdx = state.ScriptIdx + t.scriptOff = state.OpcodeIdx + t.lastCodeSep = state.LastCodeSeperatorIdx + t.numOps = state.NumOps + t.flags = state.Flags + t.afterGenesis = state.Genesis.AfterGenesis + t.earlyReturnAfterGenesis = state.Genesis.EarlyReturn +} diff --git a/bscript/interpreter/thread.go b/bscript/interpreter/thread.go index a3876646..5a414554 100644 --- a/bscript/interpreter/thread.go +++ b/bscript/interpreter/thread.go @@ -22,6 +22,9 @@ type thread struct { cfg config + debug Debugger + state StateHandler + scripts []ParsedScript condStack []int savedFirstStack [][]byte // stack from first script for bip16 scripts @@ -47,7 +50,7 @@ type thread struct { func createThread(opts *execOpts) (*thread, error) { th := &thread{ scriptParser: &DefaultOpcodeParser{ - ErrorOnCheckSig: opts.Tx == nil || opts.PreviousTxOut == nil, + ErrorOnCheckSig: opts.tx == nil || opts.previousTxOut == nil, }, cfg: &beforeGenesisConfig{}, } @@ -67,39 +70,41 @@ func createThread(opts *execOpts) (*thread, error) { // If checksig operaitons are to be executed without a Tx or a PreviousTxOut supplied, // the engine will return an ErrInvalidParams on execute. type execOpts struct { - LockingScript *bscript.Script - UnlockingScript *bscript.Script - PreviousTxOut *bt.Output - Tx *bt.Tx - InputIdx int - Flags scriptflag.Flag + lockingScript *bscript.Script + unlockingScript *bscript.Script + previousTxOut *bt.Output + tx *bt.Tx + inputIdx int + flags scriptflag.Flag + debugger Debugger + state *State } func (o execOpts) validate() error { // The provided transaction input index must refer to a valid input. - if o.InputIdx < 0 || (o.Tx != nil && o.InputIdx > o.Tx.InputCount()-1) { + if o.inputIdx < 0 || (o.tx != nil && o.inputIdx > o.tx.InputCount()-1) { return errs.NewError( errs.ErrInvalidIndex, - "transaction input index %d is negative or >= %d", o.InputIdx, len(o.Tx.Inputs), + "transaction input index %d is negative or >= %d", o.inputIdx, len(o.tx.Inputs), ) } - outputHasLockingScript := o.PreviousTxOut != nil && o.PreviousTxOut.LockingScript != nil - txHasUnlockingScript := o.Tx != nil && o.Tx.Inputs != nil && len(o.Tx.Inputs) > 0 && - o.Tx.Inputs[o.InputIdx] != nil && o.Tx.Inputs[o.InputIdx].UnlockingScript != nil + outputHasLockingScript := o.previousTxOut != nil && o.previousTxOut.LockingScript != nil + txHasUnlockingScript := o.tx != nil && o.tx.Inputs != nil && len(o.tx.Inputs) > 0 && + o.tx.Inputs[o.inputIdx] != nil && o.tx.Inputs[o.inputIdx].UnlockingScript != nil // If no locking script was provided - if o.LockingScript == nil && !outputHasLockingScript { + if o.lockingScript == nil && !outputHasLockingScript { return errs.NewError(errs.ErrInvalidParams, "no locking script provided") } // If no unlocking script was provided - if o.UnlockingScript == nil && !txHasUnlockingScript { + if o.unlockingScript == nil && !txHasUnlockingScript { return errs.NewError(errs.ErrInvalidParams, "no unlocking script provided") } // If both a locking script and previous output were provided, make sure the scripts match - if o.LockingScript != nil && outputHasLockingScript { - if !o.LockingScript.Equals(o.PreviousTxOut.LockingScript) { + if o.lockingScript != nil && outputHasLockingScript { + if !o.lockingScript.Equals(o.previousTxOut.LockingScript) { return errs.NewError( errs.ErrInvalidParams, "locking script does not match the previous outputs locking script", @@ -108,8 +113,8 @@ func (o execOpts) validate() error { } // If both a unlocking script and an input were provided, make sure the scripts match - if o.UnlockingScript != nil && txHasUnlockingScript { - if !o.UnlockingScript.Equals(o.Tx.Inputs[o.InputIdx].UnlockingScript) { + if o.unlockingScript != nil && txHasUnlockingScript { + if !o.unlockingScript.Equals(o.tx.Inputs[o.inputIdx].UnlockingScript) { return errs.NewError( errs.ErrInvalidParams, "unlocking script does not match the unlocking script of the requested input", @@ -144,7 +149,7 @@ func (t *thread) isBranchExecuting() bool { // executeOpcode performs execution on the passed opcode. It takes into account // whether it is hidden by conditionals, but some rules still must be // tested in this case. -func (t *thread) executeOpcode(pop ParsedOp) error { +func (t *thread) executeOpcode(pop ParsedOpcode) error { if len(pop.Data) > t.cfg.MaxScriptElementSize() { return errs.NewError(errs.ErrElementTooBig, "element size %d exceeds max allowed size %d", len(pop.Data), t.cfg.MaxScriptElementSize()) @@ -163,7 +168,7 @@ func (t *thread) executeOpcode(pop ParsedOp) error { } // Note that this includes OP_RESERVED which counts as a push operation. - if pop.Op.val > bscript.Op16 { + if pop.op.val > bscript.Op16 { t.numOps++ if t.numOps > t.cfg.MaxOps() { return errs.NewError(errs.ErrTooManyOperations, "exceeded max operation limit of %d", t.cfg.MaxOps()) @@ -184,8 +189,8 @@ func (t *thread) executeOpcode(pop ParsedOp) error { // Ensure all executed data push opcodes use the minimal encoding when // the minimal data verification flag is set. - if t.dstack.verifyMinimalData && t.isBranchExecuting() && pop.Op.val <= bscript.OpPUSHDATA4 && exec { - if err := pop.EnforceMinimumDataPush(); err != nil { + if t.dstack.verifyMinimalData && t.isBranchExecuting() && pop.op.val <= bscript.OpPUSHDATA4 && exec { + if err := pop.enforceMinimumDataPush(); err != nil { return err } } @@ -196,7 +201,7 @@ func (t *thread) executeOpcode(pop ParsedOp) error { return nil } - return pop.Op.exec(&pop, t) + return pop.op.exec(&pop, t) } // validPC returns an error if the current script position is valid for @@ -233,97 +238,10 @@ func (t *thread) CheckErrorCondition(finalScript bool) error { return errs.NewError(errs.ErrEvalFalse, "false stack entry at end of script execution") } - return nil -} - -// Step will execute the next instruction and move the program counter to the -// next opcode in the script, or the next script if the current has ended. Step -// will return true in the case that the last opcode was successfully executed. -// -// The result of calling Step or any other method is undefined if an error is -// returned. -func (t *thread) Step() (bool, error) { - // Verify that it is pointing to a valid script address. - if err := t.validPC(); err != nil { - return true, err + if finalScript { + t.afterSuccess() } - - opcode := t.scripts[t.scriptIdx][t.scriptOff] - t.scriptOff++ - - // Execute the opcode while taking into account several things such as - // disabled opcodes, illegal opcodes, maximum allowed operations per - // script, maximum script element sizes, and conditionals. - if err := t.executeOpcode(opcode); err != nil { - if ok := errs.IsErrorCode(err, errs.ErrOK); ok { - // If returned early, move onto the next script - t.shiftScript() - return t.scriptIdx >= len(t.scripts), nil - } - return true, err - } - - // The number of elements in the combination of the data and alt stacks - // must not exceed the maximum number of stack elements allowed. - combinedStackSize := t.dstack.Depth() + t.astack.Depth() - if combinedStackSize > int32(t.cfg.MaxStackSize()) { - return false, errs.NewError(errs.ErrStackOverflow, - "combined stack size %d > max allowed %d", combinedStackSize, t.cfg.MaxStackSize()) - } - - if t.scriptOff < len(t.scripts[t.scriptIdx]) { - return false, nil - } - - // Prepare for next instruction. - // Illegal to have an `if' that straddles two scripts. - if len(t.condStack) != 0 { - return false, errs.NewError(errs.ErrUnbalancedConditional, "end of script reached in conditional execution") - } - - // Alt stack doesn't persist. - _ = t.astack.DropN(t.astack.Depth()) - - // Move onto the next script - t.shiftScript() - - if t.bip16 && !t.afterGenesis && t.scriptIdx <= 2 { - switch t.scriptIdx { - case 1: - t.savedFirstStack = t.GetStack() - case 2: - // Put us past the end for CheckErrorCondition() - // Check script ran successfully and pull the script - // out of the first stack and execute that. - if err := t.CheckErrorCondition(false); err != nil { - return false, err - } - - script := t.savedFirstStack[len(t.savedFirstStack)-1] - pops, err := t.scriptParser.Parse(bscript.NewFromBytes(script)) - if err != nil { - return false, err - } - - t.scripts = append(t.scripts, pops) - - // Set stack to be the stack from first script minus the - // script itself - t.SetStack(t.savedFirstStack[:len(t.savedFirstStack)-1]) - } - } - - // there are zero length scripts in the wild - if t.scriptIdx < len(t.scripts) && t.scriptOff >= len(t.scripts[t.scriptIdx]) { - t.scriptIdx++ - } - - t.lastCodeSep = 0 - if t.scriptIdx >= len(t.scripts) { - return true, nil - } - - return false, nil + return nil } func (t *thread) apply(opts *execOpts) error { @@ -331,17 +249,17 @@ func (t *thread) apply(opts *execOpts) error { return err } - if opts.UnlockingScript == nil { - opts.UnlockingScript = opts.Tx.Inputs[opts.InputIdx].UnlockingScript + if opts.unlockingScript == nil { + opts.unlockingScript = opts.tx.Inputs[opts.inputIdx].UnlockingScript } - if opts.LockingScript == nil { - opts.LockingScript = opts.PreviousTxOut.LockingScript + if opts.lockingScript == nil { + opts.lockingScript = opts.previousTxOut.LockingScript } - t.tx = opts.Tx - t.flags = opts.Flags - t.inputIdx = opts.InputIdx - t.prevOutput = opts.PreviousTxOut + t.tx = opts.tx + t.flags = opts.flags + t.inputIdx = opts.inputIdx + t.prevOutput = opts.previousTxOut // The clean stack flag (ScriptVerifyCleanStack) is not allowed without // the pay-to-script-hash (P2SH) evaluation (ScriptBip16). @@ -357,13 +275,13 @@ func (t *thread) apply(opts *execOpts) error { t.elseStack = &nopBoolStack{} if t.hasFlag(scriptflag.UTXOAfterGenesis) { - t.elseStack = &stack{} + t.elseStack = &stack{debug: &nopDebugger{}, sh: &nopStateHandler{}} t.afterGenesis = true t.cfg = &afterGenesisConfig{} } - uls := opts.UnlockingScript - ls := opts.LockingScript + uls := opts.unlockingScript + ls := opts.lockingScript // When both the signature script and public key script are empty the // result is necessarily an error since the stack would end up being @@ -437,23 +355,141 @@ func (t *thread) apply(opts *execOpts) error { t.tx.InputIdx(t.inputIdx).PreviousTxSatoshis = t.prevOutput.Satoshis } + t.state = t + if opts.debugger == nil { + opts.debugger = &nopDebugger{} + t.state = &nopStateHandler{} + } + t.debug = opts.debugger + t.dstack.debug = t.debug + t.dstack.sh = t.state + t.astack.debug = t.debug + t.astack.sh = t.state + + if opts.state != nil { + t.SetState(opts.state) + } + return nil } func (t *thread) execute() error { - for { - done, err := t.Step() - if err != nil { - return err - } - if done { - break + if err := func() error { + defer t.afterExecute() + t.beforeExecute() + for { + t.beforeStep() + + done, err := t.Step() + if err != nil { + return err + } + + t.afterStep() + if done { + return nil + } } + }(); err != nil { + return err } return t.CheckErrorCondition(true) } +// Step will execute the next instruction and move the program counter to the +// next opcode in the script, or the next script if the current has ended. Step +// will return true in the case that the last opcode was successfully executed. +// +// The result of calling Step or any other method is undefined if an error is +// returned. +func (t *thread) Step() (bool, error) { + // Verify that it is pointing to a valid script address. + if err := t.validPC(); err != nil { + return true, err + } + + opcode := t.scripts[t.scriptIdx][t.scriptOff] + + t.beforeExecuteOpcode() + // Execute the opcode while taking into account several things such as + // disabled opcodes, illegal opcodes, maximum allowed operations per + // script, maximum script element sizes, and conditionals. + if err := t.executeOpcode(opcode); err != nil { + if ok := errs.IsErrorCode(err, errs.ErrOK); ok { + // If returned early, move onto the next script + t.shiftScript() + return t.scriptIdx >= len(t.scripts), nil + } + return true, err + } + t.afterExecuteOpcode() + + t.scriptOff++ + + // The number of elements in the combination of the data and alt stacks + // must not exceed the maximum number of stack elements allowed. + combinedStackSize := t.dstack.Depth() + t.astack.Depth() + if combinedStackSize > int32(t.cfg.MaxStackSize()) { + return false, errs.NewError(errs.ErrStackOverflow, + "combined stack size %d > max allowed %d", combinedStackSize, t.cfg.MaxStackSize()) + } + + if t.scriptOff < len(t.scripts[t.scriptIdx]) { + return false, nil + } + + // Prepare for next instruction. + // Illegal to have an `if' that straddles two scripts. + if len(t.condStack) != 0 { + return false, errs.NewError(errs.ErrUnbalancedConditional, "end of script reached in conditional execution") + } + + // Alt stack doesn't persist. + _ = t.astack.DropN(t.astack.Depth()) + + // Move onto the next script + t.shiftScript() + + if t.bip16 && !t.afterGenesis && t.scriptIdx <= 2 { + switch t.scriptIdx { + case 1: + t.savedFirstStack = t.GetStack() + case 2: + // Put us past the end for CheckErrorCondition() + // Check script ran successfully and pull the script + // out of the first stack and execute that. + if err := t.CheckErrorCondition(false); err != nil { + return false, err + } + + script := t.savedFirstStack[len(t.savedFirstStack)-1] + pops, err := t.scriptParser.Parse(bscript.NewFromBytes(script)) + if err != nil { + return false, err + } + + t.scripts = append(t.scripts, pops) + + // Set stack to be the stack from first script minus the + // script itself + t.SetStack(t.savedFirstStack[:len(t.savedFirstStack)-1]) + } + } + + // there are zero length scripts in the wild + if t.scriptIdx < len(t.scripts) && t.scriptOff >= len(t.scripts[t.scriptIdx]) { + t.scriptIdx++ + } + + t.lastCodeSep = 0 + if t.scriptIdx >= len(t.scripts) { + return true, nil + } + + return false, nil +} + // GetStack returns the contents of the primary stack as an array. where the // last item in the array is the top of the stack. func (t *thread) GetStack() [][]byte { @@ -726,7 +762,7 @@ func setStack(stack *stack, data [][]byte) { // shouldExec returns true if the engine should execute the passed in operation, // based on its own internal state. -func (t *thread) shouldExec(pop ParsedOp) bool { +func (t *thread) shouldExec(pop ParsedOpcode) bool { if !t.afterGenesis { return true } @@ -738,12 +774,55 @@ func (t *thread) shouldExec(pop ParsedOp) bool { } } - return cf && (!t.earlyReturnAfterGenesis || pop.Op.val == bscript.OpRETURN) + return cf && (!t.earlyReturnAfterGenesis || pop.op.val == bscript.OpRETURN) } func (t *thread) shiftScript() { + defer t.afterScriptChange() + t.beforeScriptChange() + t.numOps = 0 t.scriptOff = 0 t.scriptIdx++ t.earlyReturnAfterGenesis = false } + +func (t *thread) beforeExecute() { + t.debug.BeforeExecute(t.state.State()) +} + +func (t *thread) afterExecute() { + t.debug.AfterExecute(t.state.State()) +} + +func (t *thread) beforeStep() { + t.debug.BeforeStep(t.state.State()) +} + +func (t *thread) afterStep() { + t.debug.AfterStep(t.state.State()) +} + +func (t *thread) beforeExecuteOpcode() { + t.debug.BeforeExecuteOpcode(t.state.State()) +} + +func (t *thread) afterExecuteOpcode() { + t.debug.AfterExecuteOpcode(t.state.State()) +} + +func (t *thread) beforeScriptChange() { + t.debug.BeforeScriptChange(t.state.State()) +} + +func (t *thread) afterScriptChange() { + t.debug.AfterScriptChange(t.state.State()) +} + +func (t *thread) afterError(err error) { + t.debug.AfterError(t.state.State(), err) +} + +func (t *thread) afterSuccess() { + t.debug.AfterSuccess(t.state.State()) +}