Transaction::makeUtxo()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction;
6
7
use BitWasp\Bitcoin\Bitcoin;
8
use BitWasp\Bitcoin\Crypto\Hash;
9
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
10
use BitWasp\Bitcoin\Serializable;
11
use BitWasp\Bitcoin\Serializer\Transaction\TransactionSerializer;
12
use BitWasp\Bitcoin\Util\IntRange;
13
use BitWasp\Bitcoin\Utxo\Utxo;
14
use BitWasp\Buffertools\BufferInterface;
15
16
class Transaction extends Serializable implements TransactionInterface
17
{
18
    /**
19
     * @var int
20
     */
21
    private $version;
22
23
    /**
24
     * @var TransactionInputInterface[]
25
     */
26
    private $inputs;
27
28
    /**
29
     * @var TransactionOutputInterface[]
30
     */
31
    private $outputs;
32
33
    /**
34
     * @var ScriptWitnessInterface[]
35
     */
36
    private $witness;
37
38
    /**
39
     * @var int
40
     */
41
    private $lockTime;
42
43
    /**
44
     * @var BufferInterface
45
     */
46
    private $wtxid;
47
48
    /**
49
     * @var BufferInterface
50
     */
51
    private $hash;
52
53
    /**
54
     * Transaction constructor.
55
     *
56
     * @param int $nVersion
57
     * @param TransactionInputInterface[] $vin
58
     * @param TransactionOutputInterface[] $vout
59
     * @param ScriptWitnessInterface[] $vwit
60
     * @param int $nLockTime
61
     */
62 5302
    public function __construct(
63
        int $nVersion = TransactionInterface::DEFAULT_VERSION,
64
        array $vin = [],
65
        array $vout = [],
66
        array $vwit = [],
67
        int $nLockTime = 0
68
    ) {
69 5302
        if ($nVersion < IntRange::I32_MIN || $nVersion > IntRange::I32_MAX) {
70 1
            throw new \InvalidArgumentException('Transaction version is outside valid range');
71
        }
72
73 5301
        if ($nLockTime < 0 || $nLockTime > TransactionInterface::MAX_LOCKTIME) {
74 1
            throw new \InvalidArgumentException('Locktime must be positive and less than ' . TransactionInterface::MAX_LOCKTIME);
75
        }
76
77 5300
        $this->version = $nVersion;
78 5300
        $this->lockTime = $nLockTime;
79
80 5300
        $this->inputs = array_map(function (TransactionInputInterface $input) {
81 5264
            return $input;
82 5300
        }, $vin);
83 5300
        $this->outputs = array_map(function (TransactionOutputInterface $output) {
84 5268
            return $output;
85 5300
        }, $vout);
86 5300
        $this->witness = array_map(function (ScriptWitnessInterface $scriptWitness) {
87 5192
            return $scriptWitness;
88 5300
        }, $vwit);
89 5300
    }
90
91
    /**
92
     * @return BufferInterface
93
     */
94 5105
    public function getTxHash(): BufferInterface
95
    {
96 5105
        if (null === $this->hash) {
97 5105
            $this->hash = Hash::sha256d($this->getBaseSerialization());
98
        }
99 5105
        return $this->hash;
100
    }
101
102
    /**
103
     * @return BufferInterface
104
     */
105 5105
    public function getTxId(): BufferInterface
106
    {
107 5105
        return $this->getTxHash()->flip();
108
    }
109
110
    /**
111
     * @return BufferInterface
112
     */
113
    public function getWitnessTxId(): BufferInterface
114
    {
115
        if (null === $this->wtxid) {
116
            $this->wtxid = Hash::sha256d($this->getBuffer())->flip();
117
        }
118
119
        return $this->wtxid;
120
    }
121
122
    /**
123
     * @return int
124
     */
125 5258
    public function getVersion(): int
126
    {
127 5258
        return $this->version;
128
    }
129
130
    /**
131
     * Get the array of inputs in the transaction
132
     *
133
     * @return TransactionInputInterface[]
134
     */
135 5252
    public function getInputs(): array
136
    {
137 5252
        return $this->inputs;
138
    }
139
140
    /**
141
     * @param int $index
142
     * @return TransactionInputInterface
143
     */
144 383
    public function getInput(int $index): TransactionInputInterface
145
    {
146 383
        if (!isset($this->inputs[$index])) {
147 2
            throw new \RuntimeException('No input at this index');
148
        }
149 381
        return $this->inputs[$index];
150
    }
151
152
    /**
153
     * Get Outputs
154
     *
155
     * @return TransactionOutputInterface[]
156
     */
157 5252
    public function getOutputs(): array
158
    {
159 5252
        return $this->outputs;
160
    }
161
162
    /**
163
     * @param int $vout
164
     * @return TransactionOutputInterface
165
     */
166 5158
    public function getOutput(int $vout): TransactionOutputInterface
167
    {
168 5158
        if (!isset($this->outputs[$vout])) {
169 1
            throw new \RuntimeException('No output at this index');
170
        }
171 5157
        return $this->outputs[$vout];
172
    }
173
174
    /**
175
     * @return bool
176
     */
177 218
    public function hasWitness(): bool
178
    {
179 218
        for ($l = count($this->inputs), $i = 0; $i < $l; $i++) {
180 218
            if (isset($this->witness[$i]) && count($this->witness[$i]) > 0) {
181 130
                return true;
182
            }
183
        }
184
185 96
        return false;
186
    }
187
188
    /**
189
     * @return ScriptWitnessInterface[]
190
     */
191 2562
    public function getWitnesses(): array
192
    {
193 2562
        return $this->witness;
194
    }
195
196
    /**
197
     * @param int $index
198
     * @return ScriptWitnessInterface
199
     */
200 2331
    public function getWitness(int $index): ScriptWitnessInterface
201
    {
202 2331
        if (!isset($this->witness[$index])) {
203
            throw new \RuntimeException('No witness at this index');
204
        }
205 2331
        return $this->witness[$index];
206
    }
207
208
    /**
209
     * @param int $vout
210
     * @return OutPointInterface
211
     */
212 5094
    public function makeOutpoint(int $vout): OutPointInterface
213
    {
214 5094
        $this->getOutput($vout);
215 5094
        return new OutPoint($this->getTxId(), $vout);
216
    }
217
218
    /**
219
     * @param int $vout
220
     * @return Utxo
221
     */
222
    public function makeUtxo(int $vout): Utxo
223
    {
224
        return new Utxo(new OutPoint($this->getTxId(), $vout), $this->getOutput($vout));
225
    }
226
227
    /**
228
     * Get Lock Time
229
     *
230
     * @return int
231
     */
232 5259
    public function getLockTime(): int
233
    {
234 5259
        return $this->lockTime;
235
    }
236
237
    /**
238
     * @return int
239
     */
240 1
    public function getValueOut(): int
241
    {
242 1
        $value = 0;
243 1
        foreach ($this->outputs as $output) {
244 1
            $value = $value + $output->getValue();
245
        }
246
247 1
        return $value;
248
    }
249
250
    /**
251
     * @return bool
252
     */
253 1
    public function isCoinbase(): bool
254
    {
255 1
        return count($this->inputs) === 1 && $this->getInput(0)->isCoinBase();
256
    }
257
258
    /**
259
     * @param TransactionInterface $tx
260
     * @return bool
261
     */
262 2
    public function equals(TransactionInterface $tx): bool
263
    {
264 2
        $version = gmp_cmp($this->version, $tx->getVersion());
265 2
        if ($version !== 0) {
266 1
            return false;
267
        }
268
269 2
        $nIn = count($this->inputs);
270 2
        $nOut = count($this->outputs);
271 2
        $nWit = count($this->witness);
272
273
        // Check the length of each field is equal
274 2
        if ($nIn !== count($tx->getInputs()) || $nOut !== count($tx->getOutputs()) || $nWit !== count($tx->getWitnesses())) {
275 1
            return false;
276
        }
277
278
        // Check each field
279 2
        for ($i = 0; $i < $nIn; $i++) {
280 2
            if (false === $this->getInput($i)->equals($tx->getInput($i))) {
281 1
                return false;
282
            }
283
        }
284
285 2
        for ($i = 0; $i < $nOut; $i++) {
286 2
            if (false === $this->getOutput($i)->equals($tx->getOutput($i))) {
287 1
                return false;
288
            }
289
        }
290
291 2
        for ($i = 0; $i < $nWit; $i++) {
292
            if (false === $this->getWitness($i)->equals($tx->getWitness($i))) {
293
                return false;
294
            }
295
        }
296
297 2
        return gmp_cmp($this->lockTime, $tx->getLockTime()) === 0;
298
    }
299
300
    /**
301
     * @return BufferInterface
302
     */
303 183
    public function getBuffer(): BufferInterface
304
    {
305 183
        return (new TransactionSerializer())->serialize($this);
306
    }
307
308
    /**
309
     * @return BufferInterface
310
     */
311 5149
    public function getBaseSerialization(): BufferInterface
312
    {
313 5149
        return (new TransactionSerializer())->serialize($this, TransactionSerializer::NO_WITNESS);
314
    }
315
316
    /**
317
     * @return BufferInterface
318
     */
319 50
    public function getWitnessSerialization(): BufferInterface
320
    {
321 50
        if (!$this->hasWitness()) {
322 28
            throw new \RuntimeException('Cannot get witness serialization for transaction without witnesses');
323
        }
324
325 22
        return $this->getBuffer();
326
    }
327
328
    /**
329
     * {@inheritdoc}
330
     * @see TransactionInterface::getWitnessBuffer()
331
     * @see TransactionInterface::getWitnessSerialization()
332
     * @deprecated
333
     */
334
    public function getWitnessBuffer(): BufferInterface
335
    {
336
        return $this->getWitnessSerialization();
337
    }
338
}
339