Completed
Push — master ( 40a4e1...e12674 )
by thomas
37:14 queued 34:53
created

BranchInterpreter::getScriptBranches()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 2
nop 1
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 3
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 ParsedScript
25
     */
26 174
    public function getScriptTree(ScriptInterface $script)
27
    {
28 174
        $ast = $this->getAstForLogicalOps($script);
29 174
        $scriptPaths = $ast->flags();
30
31 174
        $scriptBranches = [];
32 174
        if (count($scriptPaths) > 1) {
33 46
            foreach ($scriptPaths as $path) {
34 46
                $scriptBranches[] = $this->getBranchForPath($script, $path);
35
            }
36
        } else {
37 128
            $scriptBranches[] = $this->getBranchForPath($script, []);
38
        }
39
40 174
        return new ParsedScript($script, $ast, $scriptBranches);
41
    }
42
43
    /**
44
     * Build tree of dependent logical ops
45
     * @param ScriptInterface $script
46
     * @return LogicOpNode
47
     */
48 218
    public function getAstForLogicalOps(ScriptInterface $script)
49
    {
50 218
        $root = new LogicOpNode(null);
51 218
        $current = $root;
52
53 218
        foreach ($script->getScriptParser()->decode() as $op) {
54 214
            switch ($op->getOp()) {
55 214
                case Opcodes::OP_IF:
56 214
                case Opcodes::OP_NOTIF:
57 72
                    $split = $current->split();
58 72
                    $current = $split[$op->getOp() & 1];
59 72
                    break;
60 214
                case Opcodes::OP_ENDIF:
61 72
                    if (null === $current->getParent()) {
62 2
                        throw new \RuntimeException("Unexpected ENDIF, current scope had no parent");
63
                    }
64 70
                    $current = $current->getParent();
65 70
                    break;
66 212
                case Opcodes::OP_ELSE:
67 68
                    if (null === $current->getParent()) {
68 2
                        throw new \RuntimeException("Unexpected ELSE, current scope had no parent");
69
                    }
70 66
                    $current = $current->getParent()->getChild(!$current->getValue());
71 210
                    break;
72
            }
73
        }
74
75 214
        if (!$current->isRoot()) {
76 2
            throw new \RuntimeException("Unbalanced conditional - vfStack not empty at script termination");
77
        }
78
79 212
        return $root;
80
    }
81
82
    /**
83
     * Given a script and path, attempt to produce a ScriptBranch instance
84
     *
85
     * @param ScriptInterface $script
86
     * @param bool[] $path
87
     * @return ScriptBranch
88
     */
89 174
    public function getBranchForPath(ScriptInterface $script, array $path)
90
    {
91
        // parses the opcodes which were actually run
92 174
        $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...
93
94 174
        return new ScriptBranch($script, $path, $segments);
95
    }
96
97
    /**
98
     * @param Stack $vfStack
99
     * @param bool $value
100
     * @return bool
101
     */
102 186
    private function checkExec(Stack $vfStack, $value)
103
    {
104 186
        $ret = 0;
105 186
        foreach ($vfStack as $item) {
106 48
            if ($item === $value) {
107 48
                $ret++;
108
            }
109
        }
110
111 186
        return $ret;
112
    }
113
114
    /**
115
     * @param ScriptInterface $script
116
     * @param int[] $logicalPath
117
     * @return array - array of Operation[] representing script segments
118
     */
119 188
    public function evaluateUsingStack(ScriptInterface $script, array $logicalPath)
120
    {
121 188
        $mainStack = new Stack();
122 188
        foreach (array_reverse($logicalPath) as $setting) {
123 50
            $mainStack->push($setting);
124
        }
125
126 188
        $vfStack = new Stack();
127 188
        $parser = $script->getScriptParser();
128 188
        $tracer = new PathTracer();
129
130 188
        foreach ($parser as $i => $operation) {
131 186
            $opCode = $operation->getOp();
132 186
            $fExec = !$this->checkExec($vfStack, false);
133
134 186
            if (in_array($opCode, $this->disabledOps, true)) {
135 2
                throw new \RuntimeException('Disabled Opcode');
136
            }
137
138 184
            if (Opcodes::OP_IF <= $opCode && $opCode <= Opcodes::OP_ENDIF) {
139
                switch ($opCode) {
140 58
                    case Opcodes::OP_IF:
141 54
                    case Opcodes::OP_NOTIF:
142
                        // <expression> if [statements] [else [statements]] endif
143 54
                        $value = false;
144 54
                        if ($fExec) {
145 54
                            if ($mainStack->isEmpty()) {
146 4
                                $op = $script->getOpcodes()->getOp($opCode & 1 ? Opcodes::OP_IF : Opcodes::OP_NOTIF);
147 4
                                throw new \RuntimeException("Unbalanced conditional at {$op} - not included in logicalPath");
148
                            }
149
150 50
                            $value = $mainStack->pop();
151 50
                            if ($opCode === Opcodes::OP_NOTIF) {
152 18
                                $value = !$value;
153
                            }
154
                        }
155 50
                        $vfStack->push($value);
156 50
                        break;
157
158 52
                    case Opcodes::OP_ELSE:
159 44
                        if ($vfStack->isEmpty()) {
160 2
                            throw new \RuntimeException('Unbalanced conditional at OP_ELSE');
161
                        }
162 42
                        $vfStack->push(!$vfStack->pop());
163 42
                        break;
164
165 50
                    case Opcodes::OP_ENDIF:
166 50
                        if ($vfStack->isEmpty()) {
167 2
                            throw new \RuntimeException('Unbalanced conditional at OP_ENDIF');
168
                        }
169 48
                        $vfStack->pop();
170
171 48
                        break;
172
                }
173
174 50
                $tracer->operation($operation);
175 172
            } else if ($fExec) {
176
                // Fill up trace with executed opcodes
177 176
                $tracer->operation($operation);
178
            }
179
        }
180
181 178
        if (count($vfStack) !== 0) {
182 2
            throw new \RuntimeException('Unbalanced conditional at script end');
183
        }
184
185 176
        if (count($mainStack) !== 0) {
186 2
            throw new \RuntimeException('Values remaining after script execution - invalid branch data');
187
        }
188
189 174
        return $tracer->done();
190
    }
191
}
192