Completed
Pull Request — master (#125)
by thomas
03:07 queued 45s
created

BtccomConverter::convertBitwaspScriptType()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
nc 10
nop 1
dl 0
loc 22
ccs 0
cts 22
cp 0
crap 110
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Blocktrail\SDK\Backend;
4
5
use BitWasp\Bitcoin\Address\AddressFactory;
6
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
7
use BitWasp\Bitcoin\Script\ScriptType;
8
use BitWasp\Bitcoin\Transaction\Transaction;
9
use BitWasp\Bitcoin\Transaction\TransactionInput;
10
use Blocktrail\SDK\BlocktrailSDK;
11
use Blocktrail\SDK\Connection\Exceptions\EndpointSpecificError;
12
13
class BtccomConverter implements ConverterInterface {
14
    public function paginationParams($params) {
15
        if (!$params) {
16
            return $params;
17
        }
18
19
        if (isset($params['limit'])) {
20
            $params['pagesize'] = $params['limit'];
21
        }
22
23
        return $params;
24
    }
25
26
    public function getUrlForBlock($blockHash) {
27
        return "block/{$blockHash}";
28
    }
29
30
    public function getUrlForTransaction($txId) {
31
        return "tx/{$txId}?verbose=3";
32
    }
33
34
    public function getUrlForTransactions($txIds) {
35
        return "tx/" . implode(",", $txIds) . "?verbose=3";
36
    }
37
38
    public function getUrlForBlockTransaction($blockHash) {
39
        return "block/{$blockHash}/tx?verbose=3";
40
    }
41
42
    public function getUrlForAddress($address) {
43
        return "address/{$address}";
44
    }
45
46
    public function getUrlForAddressTransactions($address) {
47
        return "address/{$address}/tx?verbose=3";
48
    }
49
50
    public function getUrlForAddressUnspent($address) {
51
        return "address/{$address}/unspent";
52
    }
53
54
    public function getUrlForBatchAddressesUnspent($addresses) {
55
        return "multi-address/" . \implode(",", $addresses) . "/unspent";
56
    }
57
58
    public function getUrlForAllBlocks() {
59
        return "block/list";
60
    }
61
62
    public function handleErros($data) {
63
        if (isset($data['err_no']) && $data['err_no'] > 0) {
64
            throw new EndpointSpecificError($data['err_msg'], $data['err_no']);
65
        }
66
    }
67
68
    public function convertBlock($res) {
69
        $data = BlocktrailSDK::jsonDecode($res, true);
70
        $this->handleErros($data);
71
72
        return $this->_convertBlock($data['data']);
73
    }
74
75
    private function _convertBlock($blockData) {
76
        return [
77
            "hash" => $blockData['hash'],
78
            "version" => (string)$blockData['version'],
79
            "height" => $blockData['height'],
80
            "block_time" => self::utcTimestampToISODateStr($blockData['timestamp']),
81
            "arrival_time" => self::utcTimestampToISODateStr($blockData['timestamp']),
82
            "bits" => $blockData['bits'],
83
            "nonce" => $blockData['nonce'],
84
            "merkleroot" => $blockData['mrkl_root'],
85
            "prev_block" => $blockData['prev_block_hash'],
86
            "next_block" => $blockData['next_block_hash'],
87
            "byte_size" => $blockData['stripped_size'],
88
            "difficulty" => (int)\floor($blockData['difficulty']),
89
            "transactions" => $blockData['tx_count'],
90
            "reward_block" => $blockData['reward_block'],
91
            "reward_fees" => $blockData['reward_fees'],
92
            "created_at" => $blockData['created_at'],
93
            "confirmations" => $blockData['confirmations'],
94
            "is_orphan" => $blockData['is_orphan'],
95
            "is_sw_block" => $blockData['is_sw_block'],
96
            "weight" => $blockData['weight'],
97
            "miningpool_name" => isset($blockData['miningpool_name']) ? $blockData['miningpool_name'] : null,
98
            "miningpool_url" => isset($blockData['miningpool_url']) ? $blockData['miningpool_url'] : null,
99
            "miningpool_slug" => isset($blockData['miningpool_slug']) ? $blockData['miningpool_slug'] : null
100
        ];
101
    }
102
103
    public function convertBlocks($res) {
104
        $data = BlocktrailSDK::jsonDecode($res, true);
105
        $this->handleErros($data);
106
107
        return [
108
            'data' => $data['data']['list'],
109
            'current_page' => $data['data']['page'],
110
            'per_page' => $data['data']['pagesize'],
111
            'total' => $data['data']['total_count'],
112
        ];
113
    }
114
115
    public function convertBlockTxs($res) {
116
        $data = BlocktrailSDK::jsonDecode($res, true);
117
        $this->handleErros($data);
118
119
        $list = array_map(function ($tx) {
120
            return $this->_convertTx($tx);
121
        }, $data['data']['list']);
122
123
        return [
124
            'data' => $list,
125
            'current_page' => $data['data']['page'],
126
            'per_page' => $data['data']['pagesize'],
127
            'total' => $data['data']['total_count'],
128
        ];
129
    }
130
131
    public function convertTx($res, $rawTx) {
132
        $data = BlocktrailSDK::jsonDecode($res, true);
133
        $this->handleErros($data);
134
        return $this->_convertTx($data['data']);
135
    }
136
137
    public function convertTxs($res) {
138
        $data = BlocktrailSDK::jsonDecode($res, true);
139
        $this->handleErros($data);
140
141
        return ['data' => array_map(function ($tx) {
142
            return $this->_convertTx($tx);
143
        }, $data['data'])];
144
    }
145
146
    public function convertAddressTxs($res) {
147
        $data = BlocktrailSDK::jsonDecode($res, true);
148
        $this->handleErros($data);
149
150
        $list = array_map(function ($tx) {
151
            return $this->_convertTx($tx);
152
        }, $data['data']['list']);
153
154
        return [
155
            'data' => $list,
156
            'current_page' => $data['data']['page'],
157
            'per_page' => $data['data']['pagesize'],
158
            'total' => $data['data']['total_count'],
159
        ];
160
    }
161
162
    public function convertAddress($res) {
163
        $data = BlocktrailSDK::jsonDecode($res, true);
164
        $this->handleErros($data);
165
166
        return [
167
            'address' => $data['data']['address'],
168
            'hash160' => self::getBase58AddressHash160($data['data']['address']),
169
            'balance' => $data['data']['balance'],
170
            'received' => $data['data']['received'],
171
            'sent' => $data['data']['sent'],
172
            'transactions' => $data['data']['tx_count'],
173
            'utxos' => $data['data']['unspent_tx_count'],
174
            'unconfirmed_received' => $data['data']['unconfirmed_received'],
175
            'unconfirmed_sent' => $data['data']['unconfirmed_sent'],
176
            'unconfirmed_transactions' => $data['data']['unconfirmed_tx_count'],
177
            'first_tx' => $data['data']['first_tx'],
178
            'last_tx' => $data['data']['last_tx'],
179
        ];
180
    }
181
182
    public function convertAddressUnspentOutputs($res, $address) {
183
        $data = BlocktrailSDK::jsonDecode($res, true);
184
        $this->handleErros($data);
185
186
        $spk = AddressFactory::fromString($address)->getScriptPubKey();
187
        $type = (new OutputClassifier())->classify($spk);
188
        $scriptAsm = $spk->getScriptParser()->getHumanReadable();
189
        $scriptHex = $spk->getHex();
190
191
        $list = array_map(function ($tx) use ($address, $type, $scriptAsm, $scriptHex) {
192
            return $this->_convertUtxo($tx, $address, $type, $scriptAsm, $scriptHex);
193
        }, $data['data']['list']);
194
195
        return [
196
            'data' => $list,
197
            'current_page' => $data['data']['page'],
198
            'per_page' => $data['data']['pagesize'],
199
            'total' => $data['data']['total_count'],
200
        ];
201
    }
202
203
    public function convertBatchAddressesUnspentOutputs($res) {
204
        $data = BlocktrailSDK::jsonDecode($res, true);
205
        $this->handleErros($data);
206
207
        $list = [];
208
209
        foreach ($data['data'] as $row) {
210
            if (!$row) {
211
                continue;
212
            }
213
214
            $spk = AddressFactory::fromString($row['address'])->getScriptPubKey();
215
            $type = (new OutputClassifier())->classify($spk);
216
            $scriptAsm = $spk->getScriptParser()->getHumanReadable();
217
            $scriptHex = $spk->getHex();
218
219
            foreach ($row['list'] as $utxo) {
220
                $list[] = $this->_convertUtxo($utxo, $row['address'], $type, $scriptAsm, $scriptHex);
221
            }
222
        }
223
224
        return [
225
            'data' => $list,
226
            'current_page' => null,
227
            'per_page' => null,
228
            'total' => count($list),
229
        ];
230
    }
231
232
    private function _convertUtxo($utxo, $address, $type, $scriptAsm, $scriptHex) {
233
        return [
234
            'hash' => $utxo['tx_hash'],
235
            'confirmations' => $utxo['confirmations'],
236
            'value' => $utxo['value'],
237
            'index' => $utxo['tx_output_n'],
238
            'address' => $address,
239
            'type' => $type,
240
            'script' => $scriptAsm,
241
            'script_hex' => $scriptHex,
242
        ];
243
    }
244
245
    private function _convertTx($tx) {
246
        $data = [];
247
        $data['size'] = $tx['vsize'];
248
        $data['hash'] = $tx['hash'];
249
        $data['block_height'] = $tx['block_height'];
250
        $data['block_time'] =
251
        $data['time'] = self::utcTimestampToISODateStr($tx['block_time']);
252
        $data['block_hash'] = isset($tx['block_hash']) ? $tx['block_hash'] : null;
253
        $data['confirmations'] = $tx['confirmations'];
254
        $data['is_coinbase'] = $tx['is_coinbase'];
255
256
        if ($data['is_coinbase']) {
257
            $totalInputValue = $tx['outputs'][0]['value'] - $tx['fee'];
258
        } else {
259
            $totalInputValue = $tx['inputs_value'];
260
        }
261
262
        $data['total_input_value'] = $totalInputValue;
263
        $data['total_output_value'] = array_reduce($tx['outputs'], function ($total, $output) {
264
            return $total + $output['value'];
265
        }, 0);
266
        $data['total_fee'] = $tx['fee'];
267
        $data['inputs'] = [];
268
        $data['outputs'] = [];
269
        $data['opt_in_rbf'] = false;
270
271
        foreach ($tx['inputs'] as $inputIdx => $input) {
272
            if ($input['sequence'] < TransactionInput::SEQUENCE_FINAL - 1) {
273
                $data['opt_in_rbf'] = true;
274
            }
275
276
            if ($data['is_coinbase'] && $input['prev_position'] === -1 &&
277
                $input['prev_tx_hash'] === "0000000000000000000000000000000000000000000000000000000000000000") {
278
                $scriptType = "coinbase";
279
                $inputTxid = null;
280
                $inputValue = $totalInputValue;
281
                $outpointIdx = 0;
282
            } else {
283
                $scriptType = $input['prev_type'];
284
                $inputValue = $input['prev_value'];
285
                $inputTxid = $input['prev_tx_hash'];
286
                $outpointIdx = $input['prev_position'];
287
            }
288
289
            $data['inputs'][] = [
290
                'index' => (int)$inputIdx,
291
                'output_hash' => $inputTxid,
292
                'output_index' => $outpointIdx,
293
                'value' => $inputValue,
294
                'sequence' => $input['sequence'],
295
                'address' => self::flattenAddresses($input['prev_addresses'], $scriptType),
296
                'type' => self::convertBtccomOutputScriptType($scriptType),
297
                'script_signature' => $input['script_hex'],
298
            ];
299
        }
300
301
302
        foreach ($tx['outputs'] as $outIdx => $output) {
303
            $data['outputs'][] = [
304
                'index' => (int)$outIdx,
305
                'value' => $output['value'],
306
                'address' => self::flattenAddresses($output['addresses'], $output['type']),
307
                'type' => self::convertBtccomOutputScriptType($output['type']),
308
                'script' => self::prettifyAsm($output['script_asm']),
309
                'script_hex' => $output['script_hex'],
310
                'spent_hash' => $output['spent_by_tx'],
311
                'spent_index' => $output['spent_by_tx_position'],
312
            ];
313
        }
314
315
        $data['size'] = $tx['size'];
316
        $data['is_double_spend'] = $tx['is_double_spend'];
317
318
        $data['lock_time_timestamp'] = null;
319
        $data['lock_time_block_height'] = null;
320
        if ($tx['lock_time']) {
321
            if ($tx['lock_time'] < 5000000) {
322
                $data['lock_time_block_height'] = $tx['lock_time'];
323
            } else {
324
                $data['lock_time_timestamp'] = $tx['lock_time'];
325
            }
326
        }
327
328
        // Extra fields from Btc.com
329
        $data['is_sw_tx'] = $tx['is_sw_tx'];
330
        $data['weight'] = $tx['weight'];
331
        $data['witness_hash'] = $tx['witness_hash'];
332
        $data['lock_time'] = $tx['lock_time'];
333
        $data['sigops'] = $tx['sigops'];
334
        $data['version'] = $tx['version'];
335
336
        return $data;
337
    }
338
339
    protected static function flattenAddresses($addresses, $type = null) {
340
        if ($type && in_array($type, ["P2WSH_V0", "P2WPKH"])) {
341
            return null;
342
        }
343
344
        if (!$addresses) {
345
            return null;
346
        } else if (count($addresses) === 1) {
347
            return $addresses[0];
348
        } else {
349
            return $addresses;
350
        }
351
    }
352
353
    protected static function convertBtccomOutputScriptType($scriptType) {
354
        switch ($scriptType) {
355
            case "P2PKH_PUBKEY":
356
                return "pubkey";
357
            case "P2PKH":
358
                return "pubkeyhash";
359
            case "P2SH":
360
                return "scripthash";
361
            case "P2WSH_V0":
362
                return "unknown";
363
            case "P2WPKH":
364
                return "unknown";
365
            case "NULL_DATA":
366
                return "op_return";
367
            case "coinbase":
368
                return "coinbase";
369
            default:
370
                throw new \Exception("Not implemented yet, script type: {$scriptType}");
371
        }
372
    }
373
374
    protected static function convertBitwaspScriptType($scriptType) {
375
        switch ($scriptType) {
376
            case ScriptType::P2PK:
377
                return "pubkey";
378
            case ScriptType::P2PKH:
379
                return "pubkeyhash";
380
            case ScriptType::NULLDATA:
381
                return "op_return";
382
            case ScriptType::P2SH:
383
                return "scripthash";
384
            case ScriptType::P2WSH:
385
                return "witnessscripthash";
386
            case ScriptType::P2WKH:
387
                return "witnesspubkeyhash";
388
            case ScriptType::MULTISIG:
389
            case ScriptType::WITNESS_COINBASE_COMMITMENT:
390
            case ScriptType::NONSTANDARD:
391
                return "unknown";
392
            default:
393
                throw new \Exception("Not implemented yet, script type: {$scriptType}");
394
        }
395
    }
396
397
    protected static function prettifyAsm($asm) {
398
        if (!$asm) {
399
            return $asm;
400
        }
401
402
        return preg_replace("/^0 /", "OP_0 ", $asm);
403
    }
404
405
    protected static function utcTimestampToISODateStr($time) {
406
        return (new \DateTime("@{$time}"))->format(\DATE_ISO8601);
407
    }
408
409
    protected static function getBase58AddressHash160($addr) {
410
        try {
411
            return \strtoupper(AddressFactory::fromString($addr)->getHash()->getHex());
412
        } catch (\Exception $e) {
413
            return null;
414
        }
415
    }
416
}
417