Completed
Pull Request — master (#446)
by thomas
70:27
created

Transaction::getBuffer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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