The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Bitcoin::Crypto::Script::Runner - Bitcoin Script runner

SYNOPSIS

        use Bitcoin::Crypto::Script::Runner;
        use Data::Dumper;

        my $runner = Bitcoin::Crypto::Script::Runner->new;

        # provide an instance of Bitcoin::Crypto::Script
        # runs the script all at once
        $runner->execute($script);

        # ... or: runs the script step by step
        $runner->start($script);
        while ($runner->step) {
                print 'runner step, stack: ';
                print Dumper($runner->stack);
        }

        print 'FAILURE' unless $runner->success;
        print 'resulting stack: ';
        print Dumper($runner->stack);

DESCRIPTION

This class instances can be used to execute Bitcoin scripts defined as instances of Bitcoin::Crypto::Script. Scripts can be executed in one go or step by step, and the execution stack is available through an accessor.

One runner can be used to execute scripts multiple times. Each time you call execute or start, the runner state is reset. Initial stack state can be provided to either one of those methods. This provides better control over execution than "run" in Bitcoin::Crypto::Script, which simply executes the script and returns its stack.

INTERFACE

Attributes

transaction

Instance of Bitcoin::Crypto::Transaction. It is optional, but some opcodes will refuse to function without it.

predicate: has_transaction

writer: set_transaction

stack

Not assignable in the constructor

Array reference - the stack which is used during script execution. Last item in this array is the stack top. Use $runner->stack->[-1] to examine the stack top.

Each item on the stack is a byte string. Use "to_int" and "to_bool" to transform it into an integer or boolean value in the same fashion bitcoin script interpreter does it.

alt_stack

Not assignable in the constructor

Array reference - alt stack, used by OP_TOALTSTACK and OP_FROMALTSTACK.

operations

Not assignable in the constructor

Array reference - An array of operations to be executed. Same as "operations" in Bitcoin::Crypto::Script and automatically obtained by calling it.

pos

Not assignable in the constructor

Positive integer - the position of the operation to be run in the next step (from "operations").

Methods

new

        $object = $class->new(%data)

This is a standard Moo constructor, which can be used to create the object. It takes arguments specified in "Attributes".

Returns class instance.

execute

        $object = $object->execute($script, \@initial_stack = [])

Executes the script in one go. Returns runner instance (for chaining).

$script must be an instance of Bitcoin::Crypto::Script. If you only have a serialized script in a string, call "from_serialized" in Bitcoin::Crypto::Script first to get a proper script instance. $initial_stack will be used to pre-populate the stack before running the script.

After the method returns call "stack" to get execution stack. This can be done in a single line:

        my $stack = $runner->execute($script)->stack;

If errors occur, they will be thrown as exceptions. See "EXCEPTIONS".

start

        $object = $object->start($script, \@initial_stack = [])

Same as "execute", but only sets initial runner state and does not actually execute any script opcodes. "step" must be called to continue the execution.

step

        while ($runner->step) {
                # do something after each step
        }

Executes the next script opcode. Returns a false value if the script finished the execution, and a true value otherwise.

"start" must be called before this method is called.

Note that not every opcode will take a step to execute. This means that this script:

        OP_1 OP_IF OP_PUSHDATA1 1 0x1f OP_ENDIF

will take four steps to execute (OP_1 -> OP_IF -> 0x1f -> OP_ENDIF).

This one however:

        OP_1 OP_IF OP_PUSHDATA1 1 0x1f OP_ELSE OP_PUSHDATA1 2 0xe15e OP_ENDIF

will also take four steps (OP_1 -> OP_IF -> 0x1f -> OP_ELSE). This happens because OP_ELSE performs a jump past OP_ENDIF. If the initial op was OP_0, the execution would be OP_0 -> OP_IF -> 0xe15e -> OP_ENDIF. No OP_ELSE since it was jumped over and reaching OP_ENDIF.

These details should not matter usually, but may be confusing if you would want to for example print your stack step by step. When in doubt, check $runner->pos, which contains the position of the next opcode to execute.

subscript

        $subscript = $object->subscript()

Returns current subscript - part of the running script from after the last codeseparator, with all other codeseparators removed.

success

        $boolean = $object->success()

Returns a boolean indicating whether the script execution was successful.

Helper methods

to_int, from_int

        my $int = $runner->to_int($byte_vector);
        my $byte_vector = $runner->from_int($int);

These methods encode and decode numbers in format which is used on "stack".

BigInts are used. to_int will return an instance of Math::BigInt, while from_int can accept it (but it should also handle regular numbers just fine).

to_bool, from_bool

These methods encode and decode booleans in format which is used on "stack".

stack_serialized

Returns the serialized stack. Any null vectors will be transformed to 0x00.

CAVEATS

There is curretly no limit on the size of byte vector which is going to be transformed to an integer for ops like OP_ADD. BigInts are used for all integers.

EXCEPTIONS

This module throws an instance of Bitcoin::Crypto::Exception if it encounters an error. It can produce the following error types from the Bitcoin::Crypto::Exception namespace:

  • ScriptRuntime - script has encountered a runtime exception - the transaction is invalid

  • ScriptSyntax - script syntax is invalid

SEE ALSO

Bitcoin::Crypto::Script