Completed
Pull Request — master (#241)
by thomas
133:23 queued 63:06
created

Script   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 213
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 41
c 7
b 1
f 1
lcom 1
cbo 13
dl 0
loc 213
rs 8.2769
ccs 58
cts 58
cp 1

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 3
A getBuffer() 0 4 1
A getScriptParser() 0 4 1
A getOpCodes() 0 4 1
A getScriptHash() 0 4 1
C countSigOps() 0 31 11
A witnessSigOps() 0 8 2
B countWitnessSigOps() 0 21 5
B countP2shSigOps() 0 28 6
A isPushOnly() 0 8 2
C isWitness() 0 27 8

How to fix   Complexity   

Complex Class

Complex classes like Script often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Script, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BitWasp\Bitcoin\Script;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
7
use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface;
8
use BitWasp\Bitcoin\Script\Parser\Parser;
9
use BitWasp\Buffertools\Buffer;
10
use BitWasp\Bitcoin\Crypto\Hash;
11
use BitWasp\Bitcoin\Serializable;
12
use BitWasp\Buffertools\BufferInterface;
13
14
class Script extends Serializable implements ScriptInterface
15
{
16
17
    /**
18
     * @var Opcodes
19
     */
20
    protected $opCodes;
21
22
    /**
23
     * @var string
24
     */
25
    protected $script;
26
27
    /**
28
     * @param BufferInterface $script
29 1666
     * @param Opcodes|null $opCodes
30
     */
31 1666
    public function __construct(BufferInterface $script = null, Opcodes $opCodes = null)
32 1666
    {
33 1666
        $this->script = $script instanceof BufferInterface ? $script->getBinary() : '';
34
        $this->opCodes = $opCodes ?: new Opcodes();
35
    }
36
37
    /**
38 1444
     * @return BufferInterface
39
     */
40 1444
    public function getBuffer()
41
    {
42
        return new Buffer($this->script);
43
    }
44
45
    /**
46 1114
     * @return Parser
47
     */
48 1114
    public function getScriptParser()
49
    {
50
        return new Parser(Bitcoin::getMath(), $this);
51
    }
52
53
    /**
54
     * Get all opcodes
55
     *
56 672
     * @return Opcodes
57
     */
58 672
    public function getOpCodes()
59
    {
60
        return $this->opCodes;
61
    }
62
63
    /**
64
     * Return a buffer containing the hash of this script.
65
     *
66 162
     * @return BufferInterface
67
     */
68 162
    public function getScriptHash()
69
    {
70
        return Hash::sha256ripe160($this->getBuffer());
71
    }
72
73
    /**
74
     * @param bool|true $accurate
75 48
     * @return int
76
     */
77 48
    public function countSigOps($accurate = true)
78 48
    {
79
        $count = 0;
80 48
        $parser = $this->getScriptParser();
81 48
82 36
        $lastOp = 0xff;
83 36
        foreach ($parser as $exec) {
84
            if ($exec->isPush()) {
85
                continue;
86 36
            }
87 36
88
            $op = $exec->getOp();
89 36
            if ($op > Opcodes::OP_PUSHDATA4) {
90 24
                // None of these are pushdatas, so just an opcode
91 36
                if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) {
92 30
                    $count++;
93 24
                } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) {
94 24
                    if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) {
95 24
                        $c = ($lastOp - (Opcodes::OP_1 - 1));
96 12
                        $count += $c;
97
                    } else {
98 30
                        $count += 20;
99
                    }
100 36
                }
101 36
102 48
                $lastOp = $op;
103
            }
104 48
        }
105
106
        return $count;
107
    }
108
109
    /**
110
     * @param WitnessProgram $program
111 18
     * @return int
112
     */
113 18
    private function witnessSigOps(WitnessProgram $program)
114 18
    {
115 18
        if ($program->getVersion() == 0) {
116 18
            return $program->getOutputScript()->countSigOps(true);
117 6
        }
118
119
        return 0;
120 18
    }
121
122 18
    /**
123 18
     * @param ScriptInterface $scriptSig
124 18
     * @param ScriptWitnessInterface $scriptWitness
125 6
     * @param int $flags
126
     * @return int
127
     */
128 12
    public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, $flags)
0 ignored issues
show
Unused Code introduced by
The parameter $scriptWitness is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
129 12
    {
130 12
        if ($flags & InterpreterInterface::VERIFY_WITNESS === 0) {
131 18
            return 0;
132
        }
133 18
134 6
        $program = null;
135
        if ($this->isWitness($program)) {
136
            return $this->witnessSigOps($program);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 134 can be null; however, BitWasp\Bitcoin\Script\Script::witnessSigOps() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
137 12
        }
138
139
        if ((new OutputClassifier($this))->isPayToScriptHash()) {
140
            $parsed = $scriptSig->getScriptParser()->decode();
141
            $subscript = new Script(end($parsed)->getData());
142
            if ($subscript->isWitness($program)) {
143 18
                return $this->witnessSigOps($program);
0 ignored issues
show
Bug introduced by
It seems like $program defined by null on line 134 can be null; however, BitWasp\Bitcoin\Script\Script::witnessSigOps() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
144
            }
145 18
        }
146 18
147 18
        return 0;
148 18
    }
149 18
150
    /**
151
     * @param ScriptInterface $scriptSig
152
     * @return int
153
     */
154
    public function countP2shSigOps(ScriptInterface $scriptSig)
155
    {
156
        if (ScriptFactory::scriptPubKey()
157
            ->classify($this)
158
            ->isPayToScriptHash() === false
159
        ) {
160
            return $this->countSigOps(true);
161
        }
162
163
        $parser = $scriptSig->getScriptParser();
164
165
        $data = null;
166
        foreach ($parser as $exec) {
167
            if ($exec->getOp() > Opcodes::OP_16) {
168
                return 0;
169
            }
170
171
            if ($exec->isPush()) {
172
                $data = $exec->getData();
173
            }
174
        }
175
176
        if (!$data instanceof BufferInterface) {
177
            return 0;
178
        }
179
180
        return (new Script($data))->countSigOps(true);
181
    }
182
183
    /**
184
     * @return bool
185
     */
186
    public function isPushOnly()
187
    {
188
        $pushOnly = true;
189
        foreach ($this->getScriptParser()->decode() as $entity) {
190
            $pushOnly &= $entity->isPush();
191
        }
192
        return $pushOnly;
193
    }
194
195
    /**
196
     * @param WitnessProgram|null $program
197
     * @return bool
198
     */
199
    public function isWitness(WitnessProgram & $program = null)
200
    {
201
        $buffer = $this->getBuffer();
202
        $size = $buffer->getSize();
203
        if ($size < 4 || $size > 34) {
204
            return false;
205
        }
206
207
        $parser = $this->getScriptParser();
208
        $script = $parser->decode();
209
        if (!$script[1]->isPush()) {
210
            return false;
211
        }
212
213
        $version = $script[0]->getOp();
214
        if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) {
215
            return false;
216
        }
217
218
        $witness = $script[1];
219
        if ($size === $witness->getDataSize() + 2) {
220
            $program = new WitnessProgram(decodeOpN($version), $witness->getData());
221
            return true;
222
        }
223
224
        return false;
225
    }
226
}
227