Completed
Pull Request — master (#524)
by thomas
71:45
created

BranchInterpreter::getScriptBranches()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 2
nop 1
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Script\Path;
4
5
use BitWasp\Bitcoin\Script\Interpreter\Stack;
6
use BitWasp\Bitcoin\Script\Opcodes;
7
use BitWasp\Bitcoin\Script\ScriptInterface;
8
9
class BranchInterpreter
10
{
11
12
    /**
13
     * @var array
14
     */
15
    private $disabledOps = [
16
        Opcodes::OP_CAT,    Opcodes::OP_SUBSTR, Opcodes::OP_LEFT,  Opcodes::OP_RIGHT,
17
        Opcodes::OP_INVERT, Opcodes::OP_AND,    Opcodes::OP_OR,    Opcodes::OP_XOR,
18
        Opcodes::OP_2MUL,   Opcodes::OP_2DIV,   Opcodes::OP_MUL,   Opcodes::OP_DIV,
19
        Opcodes::OP_MOD,    Opcodes::OP_LSHIFT, Opcodes::OP_RSHIFT
20
    ];
21
22
    /**
23
     * @param ScriptInterface $script
24
     * @return ScriptBranch[]
25
     */
26
    public function getScriptBranches(ScriptInterface $script)
27
    {
28
        $ast = $this->getAstForLogicalOps($script);
29
        $paths = $ast->flags();
30
        $results = [];
31
32
        if (count($paths) > 1) {
33
            foreach ($paths as $path) {
34
                $results[] = $this->getBranchForPath($script, $path);
35
            }
36
        } else {
37
            $results[] = $this->getBranchForPath($script, []);
38
        }
39
40
        return $results;
41
    }
42
43
    /**
44
     * @param ScriptInterface $script
45
     * @return ParsedScript
46
     */
47
    public function getScriptTree(ScriptInterface $script)
48
    {
49
        $ast = $this->getAstForLogicalOps($script);
50
        $paths = $ast->flags();
51
        $results = [];
52
53
        if (count($paths) > 1) {
54
            foreach ($paths as $path) {
55
                $results[] = $this->getBranchForPath($script, $path);
56
            }
57
        } else {
58
            $results[] = $this->getBranchForPath($script, []);
59
        }
60
61
        return new ParsedScript($script, $ast, $results);
62
    }
63
64
    /**
65
     * Build tree of dependent logical ops
66
     * @param ScriptInterface $script
67
     * @return LogicOpNode
68
     */
69
    public function getAstForLogicalOps(ScriptInterface $script)
70
    {
71
        $root = new LogicOpNode(null);
72
        $nextId = 1;
73
        $current = $root;
74
        $segments = [$root];
75
76
        foreach ($script->getScriptParser()->decode() as $op) {
77
            switch ($op->getOp()) {
78
                case Opcodes::OP_IF:
79
                    list ($node0, $node1) = $current->split();
80
                    $segments[$nextId++] = $node0;
81
                    $segments[$nextId++] = $node1;
82
                    $current = $node1;
83
                    break;
84
                case Opcodes::OP_NOTIF:
85
                    list ($node0, $node1) = $current->split();
86
                    $segments[$nextId++] = $node0;
87
                    $segments[$nextId++] = $node1;
88
                    $current = $node0;
89
                    break;
90
                case Opcodes::OP_ENDIF:
91
                    $current = $current->getParent();
92
                    break;
93
                case Opcodes::OP_ELSE:
94
                    $current = $current->getParent()->getChild(!$current->getValue());
95
                    break;
96
            }
97
        }
98
99
        return $root;
100
    }
101
102
    /**
103
     * Given a script and path, attempt to produce a ScriptBranch instance
104
     *
105
     * @param ScriptInterface $script
106
     * @param bool[] $path
107
     * @return ScriptBranch
108
     */
109
    public function getBranchForPath(ScriptInterface $script, array $path)
110
    {
111
        // parses the opcodes which were actually run
112
        $segments = $this->evaluateUsingStack($script, $path);
0 ignored issues
show
Documentation introduced by
$path is of type array<integer,boolean>, but the function expects a array<integer,integer>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
113
114
        //
115
116
        return new ScriptBranch($script, $path, $segments);
117
    }
118
119
    /**
120
     * @param Stack $vfStack
121
     * @param bool $value
122
     * @return bool
123
     */
124
    private function checkExec(Stack $vfStack, $value)
125
    {
126
        $ret = 0;
127
        foreach ($vfStack as $item) {
128
            if ($item === $value) {
129
                $ret++;
130
            }
131
        }
132
133
        return $ret;
134
    }
135
136
    /**
137
     * @param ScriptInterface $script
138
     * @param int[] $path
139
     * @return PathTrace
140
     */
141
    public function evaluateUsingStack(ScriptInterface $script, array $path)
142
    {
143
        $mainStack = new Stack();
144
        foreach (array_reverse($path) as $setting) {
145
            $mainStack->push($setting);
146
        }
147
148
        $vfStack = new Stack();
149
        $parser = $script->getScriptParser();
150
        $tracer = new PathTracer();
151
152
        foreach ($parser as $i => $operation) {
153
            $opCode = $operation->getOp();
154
            $fExec = !$this->checkExec($vfStack, false);
155
156
            if (in_array($opCode, $this->disabledOps, true)) {
157
                throw new \RuntimeException('Disabled Opcode');
158
            }
159
160
            if (Opcodes::OP_IF <= $opCode && $opCode <= Opcodes::OP_ENDIF) {
161
                switch ($opCode) {
162
                    case Opcodes::OP_IF:
163
                    case Opcodes::OP_NOTIF:
164
                        // <expression> if [statements] [else [statements]] endif
165
                        $value = false;
166
                        if ($fExec) {
167
                            if ($mainStack->isEmpty()) {
168
                                throw new \RuntimeException('Unbalanced conditional');
169
                            }
170
171
                            $value = $mainStack->pop();
172
                            if ($opCode === Opcodes::OP_NOTIF) {
173
                                $value = !$value;
174
                            }
175
                        }
176
                        $vfStack->push($value);
177
                        break;
178
179
                    case Opcodes::OP_ELSE:
180
                        if ($vfStack->isEmpty()) {
181
                            throw new \RuntimeException('Unbalanced conditional');
182
                        }
183
                        $vfStack->push(!$vfStack->pop());
184
                        break;
185
186
                    case Opcodes::OP_ENDIF:
187
                        if ($vfStack->isEmpty()) {
188
                            throw new \RuntimeException('Unbalanced conditional');
189
                        }
190
                        $vfStack->pop();
191
192
                        break;
193
                }
194
195
                $tracer->operation($operation);
196
            } else if ($fExec) {
197
                // Fill up trace with executed opcodes
198
                $tracer->operation($operation);
199
            }
200
        }
201
202
        if (count($vfStack) !== 0) {
203
            throw new \RuntimeException('Unbalanced conditional at script end');
204
        }
205
206
        if (count($mainStack) !== 0) {
207
            throw new \RuntimeException('Values remaining after script execution - invalid branch data');
208
        }
209
210
        return $tracer->done();
211
    }
212
}
213