Completed
Pull Request — master (#524)
by thomas
40:24 queued 38:13
created

BranchInterpreter   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 93.68%

Importance

Changes 0
Metric Value
dl 0
loc 204
ccs 89
cts 95
cp 0.9368
rs 9.2
c 0
b 0
f 0
wmc 34
lcom 1
cbo 8

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getScriptBranches() 0 16 3
A getScriptTree() 0 16 3
B getAstForLogicalOps() 0 32 6
A getBranchForPath() 0 9 1
A checkExec() 0 11 3
C evaluateUsingStack() 0 71 18
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 18
    public function getScriptBranches(ScriptInterface $script)
27
    {
28 18
        $ast = $this->getAstForLogicalOps($script);
29 18
        $paths = $ast->flags();
30 18
        $results = [];
31
32 18
        if (count($paths) > 1) {
33 10
            foreach ($paths as $path) {
34 10
                $results[] = $this->getBranchForPath($script, $path);
35
            }
36
        } else {
37 8
            $results[] = $this->getBranchForPath($script, []);
38
        }
39
40 18
        return $results;
41
    }
42
43
    /**
44
     * @param ScriptInterface $script
45
     * @return ParsedScript
46
     */
47 108
    public function getScriptTree(ScriptInterface $script)
48
    {
49 108
        $ast = $this->getAstForLogicalOps($script);
50 108
        $paths = $ast->flags();
51 108
        $results = [];
52
53 108
        if (count($paths) > 1) {
54 22
            foreach ($paths as $path) {
55 22
                $results[] = $this->getBranchForPath($script, $path);
56
            }
57
        } else {
58 86
            $results[] = $this->getBranchForPath($script, []);
59
        }
60
61 108
        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 126
    public function getAstForLogicalOps(ScriptInterface $script)
70
    {
71 126
        $root = new LogicOpNode(null);
72 126
        $nextId = 1;
73 126
        $current = $root;
74 126
        $segments = [$root];
75
76 126
        foreach ($script->getScriptParser()->decode() as $op) {
77 126
            switch ($op->getOp()) {
78 126
                case Opcodes::OP_IF:
79 28
                    list ($node0, $node1) = $current->split();
80 28
                    $segments[$nextId++] = $node0;
81 28
                    $segments[$nextId++] = $node1;
82 28
                    $current = $node1;
83 28
                    break;
84 126
                case Opcodes::OP_NOTIF:
85 12
                    list ($node0, $node1) = $current->split();
86 12
                    $segments[$nextId++] = $node0;
87 12
                    $segments[$nextId++] = $node1;
88 12
                    $current = $node0;
89 12
                    break;
90 126
                case Opcodes::OP_ENDIF:
91 32
                    $current = $current->getParent();
92 32
                    break;
93 126
                case Opcodes::OP_ELSE:
94 28
                    $current = $current->getParent()->getChild(!$current->getValue());
95 126
                    break;
96
            }
97
        }
98
99 126
        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 126
    public function getBranchForPath(ScriptInterface $script, array $path)
110
    {
111
        // parses the opcodes which were actually run
112 126
        $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 126
        return new ScriptBranch($script, $path, $segments);
117
    }
118
119
    /**
120
     * @param Stack $vfStack
121
     * @param bool $value
122
     * @return bool
123
     */
124 126
    private function checkExec(Stack $vfStack, $value)
125
    {
126 126
        $ret = 0;
127 126
        foreach ($vfStack as $item) {
128 32
            if ($item === $value) {
129 32
                $ret++;
130
            }
131
        }
132
133 126
        return $ret;
134
    }
135
136
    /**
137
     * @param ScriptInterface $script
138
     * @param int[] $path
139
     * @return PathTrace
140
     */
141 126
    public function evaluateUsingStack(ScriptInterface $script, array $path)
142
    {
143 126
        $mainStack = new Stack();
144 126
        foreach (array_reverse($path) as $setting) {
145 32
            $mainStack->push($setting);
146
        }
147
148 126
        $vfStack = new Stack();
149 126
        $parser = $script->getScriptParser();
150 126
        $tracer = new PathTracer();
151
152 126
        foreach ($parser as $i => $operation) {
153 126
            $opCode = $operation->getOp();
154 126
            $fExec = !$this->checkExec($vfStack, false);
155
156 126
            if (in_array($opCode, $this->disabledOps, true)) {
157
                throw new \RuntimeException('Disabled Opcode');
158
            }
159
160 126
            if (Opcodes::OP_IF <= $opCode && $opCode <= Opcodes::OP_ENDIF) {
161
                switch ($opCode) {
162 32
                    case Opcodes::OP_IF:
163 32
                    case Opcodes::OP_NOTIF:
164
                        // <expression> if [statements] [else [statements]] endif
165 32
                        $value = false;
166 32
                        if ($fExec) {
167 32
                            if ($mainStack->isEmpty()) {
168
                                throw new \RuntimeException('Unbalanced conditional');
169
                            }
170
171 32
                            $value = $mainStack->pop();
172 32
                            if ($opCode === Opcodes::OP_NOTIF) {
173 12
                                $value = !$value;
174
                            }
175
                        }
176 32
                        $vfStack->push($value);
177 32
                        break;
178
179 32
                    case Opcodes::OP_ELSE:
180 28
                        if ($vfStack->isEmpty()) {
181
                            throw new \RuntimeException('Unbalanced conditional');
182
                        }
183 28
                        $vfStack->push(!$vfStack->pop());
184 28
                        break;
185
186 32
                    case Opcodes::OP_ENDIF:
187 32
                        if ($vfStack->isEmpty()) {
188
                            throw new \RuntimeException('Unbalanced conditional');
189
                        }
190 32
                        $vfStack->pop();
191
192 32
                        break;
193
                }
194
195 32
                $tracer->operation($operation);
196 126
            } else if ($fExec) {
197
                // Fill up trace with executed opcodes
198 126
                $tracer->operation($operation);
199
            }
200
        }
201
202 126
        if (count($vfStack) !== 0) {
203
            throw new \RuntimeException('Unbalanced conditional at script end');
204
        }
205
206 126
        if (count($mainStack) !== 0) {
207
            throw new \RuntimeException('Values remaining after script execution - invalid branch data');
208
        }
209
210 126
        return $tracer->done();
211
    }
212
}
213