Completed
Pull Request — master (#99)
by Ruben de
54s
created

btccom.convert.js ➔ convertBtccomTxToBlocktrail   D

Complexity

Conditions 10
Paths 60

Size

Total Lines 103

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
c 1
b 0
f 0
nc 60
dl 0
loc 103
rs 4.8196
nop 1

1 Function

Rating   Name   Duplication   Size   Complexity  
A btccom.convert.js ➔ ... ➔ tx.outputs.reduce 0 3 1

How to fix   Long Method    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:

Complexity

Complex classes like btccom.convert.js ➔ convertBtccomTxToBlocktrail 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.

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.

1
var RestClient = require('./rest_client');
2
var Wallet = require('./wallet');
3
var blocktrail = require('./blocktrail');
4
var bitcoinJS = require('bitcoinjs-lib');
5
6
var BtccomConverter = function(network, useNewCashAddr, rawTxHost) {
7
    this.network = network;
8
    this.useNewCashAddr = useNewCashAddr;
9
10
    this.specialRawTxClientOnly = new RestClient({
11
        host: rawTxHost,
12
        https: true,
13
        port: 443,
14
        endpoint: "/"
15
    });
16
};
17
18
function getAddressScriptInfo(address, network, useNewCashAddr) {
19
    var addressScriptInfo;
20
21
    try {
22
        addressScriptInfo = bitcoinJS.address.toOutputScript(address, network, useNewCashAddr);
23
    } catch (e) {
24
        addressScriptInfo = null;
25
    }
26
    return addressScriptInfo;
27
}
28
29
function getScriptAsm(chunks) {
30
    var scriptAsm;
31
32
    try {
33
        scriptAsm = bitcoinJS.script.toASM(chunks);
34
    } catch (e) {
35
        scriptAsm = null;
36
    }
37
    return scriptAsm;
38
}
39
40
function prettifyAsm(asm) {
41
    if (!asm) {
42
        return asm;
43
    }
44
45
    return asm.replace(/^0 /, "OP_0 ");
46
}
47
48
function getType(script) {
49
    var type;
50
51
    try {
52
        type = bitcoinJS.script.classifyOutput(script);
53
    } catch (e) {
54
        type = null;
55
    }
56
    return type;
57
}
58
59
function getBase58AddressHash160(address, network, useNewCashAddr) {
60
    var addressInfo;
61
    try {
62
        addressInfo = Wallet.getAddressAndType(address, network, useNewCashAddr);
63
    } catch (e) {
64
        return null;
65
    }
66
67
    if (addressInfo.type === "base58") {
68
        return addressInfo.decoded.hash;
69
    } else if (addressInfo.type === "bech32") {
70
        if (addressInfo.data.length === 20) {
71
            return addressInfo.decoded.hash;
72
        }
73
        return null;
74
    } else if (addressInfo.type === "") {
75
        return addressInfo.decoded.hash;
76
    }
77
78
    return null;
79
}
80
81
function convertBtccomOutputScriptType(scriptType) {
82
    switch (scriptType) {
83
        case "P2PKH_PUBKEY":
84
            return "pubkey";
85
        case "P2PKH":
86
            return "pubkeyhash";
87
        case "P2SH":
88
            return "scripthash";
89
        case "P2WSH_V0":
90
            return "witnessscripthash";
91
        case "P2WPKH":
92
            return "witnesspubkeyhash";
93
        case "NULL_DATA":
94
            return "op_return";
95
        case "coinbase":
96
            return "coinbase";
97
        default:
98
            throw new Error("Not implemented yet, script type: " + scriptType);
99
    }
100
}
101
102
function utcTimestampToISODateStr(time) {
103
    return (new Date(time * 1000)).toISOString().replace(/\.000Z$/, '+0000');
104
}
105
106
function flattenAddresses(addrs) {
107
    if (!addrs) {
108
        return addrs;
109
    } else if (addrs.length === 1) {
110
        return addrs[0];
111
    } else {
112
        return addrs;
113
    }
114
}
115
116
function convertBtccomTxToBlocktrail(tx) {
117
    /* jshint -W071, -W074 */
118
    var data = {};
119
120
    data.size = tx.vsize;
121
    data.hash = tx.hash;
122
    data.block_height = tx.block_height;
123
    data.time =
124
    data.block_time = utcTimestampToISODateStr(tx.block_time);
125
    data.block_hash = tx.block_hash;
126
    data.confirmations = tx.confirmations;
127
    data.is_coinbase = tx.is_coinbase;
128
129
    var totalInputValue;
130
    if (data.is_coinbase) {
131
        totalInputValue = tx.outputs[0].value - tx.fee;
132
    } else {
133
        totalInputValue = tx.inputs_value;
134
    }
135
136
    data.total_input_value = totalInputValue;
137
    data.total_output_value = tx.outputs.reduce(function(total, output) {
138
        return total + output.value;
139
    }, 0);
140
    data.total_fee = tx.fee;
141
    data.inputs = [];
142
    data.outputs = [];
143
    data.opt_in_rbf = false;
144
145
    for (var inputIdx in tx.inputs) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
146
        var input = tx.inputs[inputIdx];
147
        var scriptType;
148
        var inputValue;
149
        var inputTxid;
150
        var outpointIdx;
151
152
        if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) {
153
            data.opt_in_rbf = true;
154
        }
155
156
        if (data.is_coinbase && input.prev_position === -1 &&
157
            input.prev_tx_hash === "0000000000000000000000000000000000000000000000000000000000000000") {
158
            scriptType = "coinbase";
159
            inputTxid = null;
160
            inputValue = totalInputValue;
161
            outpointIdx = 0xffffffff;
162
        } else {
163
            scriptType = input.prev_type;
164
            inputValue = input.prev_value;
165
            inputTxid = input.prev_tx_hash;
166
            outpointIdx = input.prev_position;
167
        }
168
169
        data.inputs.push({
170
            index: parseInt(inputIdx, 10),
171
            output_hash: inputTxid,
172
            output_index: outpointIdx,
173
            value: inputValue,
174
            sequence: input.sequence,
175
            address: flattenAddresses(input.prev_addresses),
176
            type: convertBtccomOutputScriptType(scriptType),
177
            script_signature: input.script_hex
178
        });
179
    }
180
181
    for (var outIdx in tx.outputs) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
182
        var output = tx.outputs[outIdx];
183
184
        data.outputs.push({
185
            index: parseInt(outIdx, 10),
186
            value: output.value,
187
            address: flattenAddresses(output.addresses),
188
            type: convertBtccomOutputScriptType(output.type),
189
            script: prettifyAsm(output.script_asm),
190
            script_hex: output.script_hex,
191
            spent_hash: output.spent_by_tx,
192
            spent_index: output.spent_by_tx_position
193
        });
194
    }
195
196
    data.size = tx.size;
197
    data.is_double_spend = tx.is_double_spend;
198
199
    data.lock_time_timestamp = null;
200
    data.lock_time_block_height = null;
201
    if (tx.lock_time) {
202
        if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) {
203
            data.lock_time_block_height = tx.lock_time;
204
        } else {
205
            data.lock_time_timestamp = tx.lock_time;
206
        }
207
    }
208
209
    // Extra fields from Btc.com
210
    data.is_sw_tx = tx.is_sw_tx;
211
    data.weight = tx.weight;
212
    data.witness_hash = tx.witness_hash;
213
    data.lock_time  = tx.lock_time;
214
    data.sigops = tx.sigops;
215
    data.version = tx.version;
216
217
    return data;
218
}
219
220
BtccomConverter.prototype.paginationParams = function(params) {
221
    if (!params) {
222
        return params;
223
    }
224
225
    if (typeof params.limit !== "undefined") {
226
        params.pagesize = params.limit;
227
        delete params.limit;
228
    }
229
230
    return params;
231
};
232
233
BtccomConverter.prototype.getUrlForBlock = function(blockHash) {
234
    return "/block/" + blockHash;
235
};
236
237
BtccomConverter.prototype.getUrlForTransaction = function(txId) {
238
    return "/tx/" + txId + "?verbose=3";
239
};
240
241
BtccomConverter.prototype.getUrlForTransactions = function(txIds) {
242
    return "/tx/" + txIds.join(",") + "?verbose=3";
243
};
244
245
BtccomConverter.prototype.getUrlForBlockTransaction = function(blockHash) {
246
    return "/block/" + blockHash + "/tx?verbose=3";
247
};
248
249
BtccomConverter.prototype.getUrlForAddress = function(address) {
250
    return "/address/" + address;
251
};
252
253
BtccomConverter.prototype.getUrlForAddressTransactions = function(address) {
254
    return "/address/" + address + "/tx?verbose=3";
255
};
256
257
BtccomConverter.prototype.getUrlForAddressUnspent = function(address) {
258
    return "/address/" + address + "/unspent";
259
};
260
261
BtccomConverter.prototype.getUrlForAllBlocks = function() {
262
    return "/block/list";
263
};
264
265
BtccomConverter.prototype.handleErros = function(self, data) {
266
    if (data.err_no === 0 || data.data !== null) {
267
        return data;
268
    } else {
269
        return {
270
            err_no: data.err_no,
271
            err_msg: data.err_msg,
272
            data: data.data
273
        };
274
    }
275
};
276
277
BtccomConverter.prototype.convertBlock = function(oldData) {
278
    var data = {
279
        hash: oldData.hash,
280
        version: oldData.version,
281
        height: oldData.height,
282
        block_time: utcTimestampToISODateStr(oldData.timestamp),
283
        arrival_time: utcTimestampToISODateStr(oldData.timestamp),
284
        bits: oldData.bits,
285
        nonce: oldData.nonce,
286
        merkleroot: oldData.mrkl_root,
287
        prev_block: oldData.prev_block_hash,
288
        next_block: oldData.next_block_hash,
289
        byte_size: oldData.stripped_size,
290
        difficulty: Math.floor(oldData.difficulty),
291
        transactions: oldData.tx_count,
292
        reward_block: oldData.reward_block,
293
        reward_fees: oldData.reward_fees,
294
        created_at: oldData.created_at,
295
        confirmations: oldData.confirmations,
296
        is_orphan: oldData.is_orphan,
297
        is_sw_block: oldData.is_sw_block,
298
        weight: oldData.weight,
299
        miningpool_name: oldData.miningpool_name || null,
300
        miningpool_url: oldData.miningpool_url || null,
301
        miningpool_slug: oldData.miningpool_slug || null
302
    };
303
304
    return data;
305
};
306
307
BtccomConverter.prototype.convertBlocks = function(oldData) {
308
    var self = this;
0 ignored issues
show
Unused Code introduced by
The variable self seems to be never used. Consider removing it.
Loading history...
309
310
    return {
311
        data: oldData.data.list,
312
        current_page: oldData.data.page,
313
        per_page: oldData.data.pagesize,
314
        total: oldData.data.total_count
315
    };
316
};
317
318
BtccomConverter.prototype.convertBlockTxs = function(oldData) {
319
    var list = [];
320
    oldData.data.list.forEach(function(oldTx) {
321
        var resTx = convertBtccomTxToBlocktrail(oldTx);
322
323
        list.push(resTx);
324
    });
325
326
    return {
327
        data: list,
328
        current_page: oldData.data.page,
329
        per_page: oldData.data.pagesize,
330
        total: oldData.data.total_count
331
    };
332
};
333
334
BtccomConverter.prototype.convertTx = function(oldData, rawTx) {
335
    var data = convertBtccomTxToBlocktrail(oldData.data);
336
    data.raw = rawTx;
337
    return data;
338
};
339
340
BtccomConverter.prototype.convertTxs = function(oldData) {
341
    var res = {};
342
343
    oldData.data
344
        .filter(function(tx) { return !!tx; })
345
        .forEach(function(oldTx) {
346
            var tx = convertBtccomTxToBlocktrail(oldTx);
347
            res[tx.hash] = tx;
348
        });
349
350
    return {data: res};
351
};
352
353
BtccomConverter.prototype.convertAddressTxs = function(oldData) {
354
    var data = oldData.data.list.map(function(tx) {
355
        var res = {};
356
357
        res.hash = tx.hash;
358
        res.time = utcTimestampToISODateStr(tx.block_time);
359
        res.confirmations = tx.confirmations;
360
        res.block_height = tx.block_height;
361
        res.block_hash = tx.block_hash;
362
        res.is_coinbase = tx.is_coinbase;
363
        res.total_input_value = tx.inputs_value;
364
        res.total_output_value = tx.outputs_value;
365
        res.total_fee = tx.fee;
366
367
        res.inputs = tx.inputs.map(function(input, inIdx) {
368
            return {
369
                index: inIdx,
370
                output_hash: input.prev_tx_hash,
371
                output_index: input.prev_position,
372
                value: input.prev_value,
373
                address: flattenAddresses(input.prev_addresses),
374
                type:  res.is_coinbase ? res.is_coinbase : convertBtccomOutputScriptType(input.prev_type),
375
                script_signature: input.script_hex
376
            };
377
        });
378
379
        res.outputs = tx.outputs.map(function(output, outIdx) {
380
            return {
381
                index: outIdx,
382
                value: output.value,
383
                address: flattenAddresses(output.addresses),
384
                type: convertBtccomOutputScriptType(output.type),
385
                script: prettifyAsm(output.script_asm),
386
                spent_hash: output.spent_by_tx || null,
387
                script_hex: output.script_hex,
388
                spent_index: output.spent_by_tx_position
389
            };
390
        });
391
392
        // Extra fields from Btc.com
393
        res.is_double_spend = tx.is_double_spend;
394
        res.is_sw_tx = tx.is_sw_tx;
395
        res.weight = tx.weight;
396
        res.witness_hash = tx.witness_hash;
397
        res.version = tx.version;
398
399
        return res;
400
    });
401
402
    return {
403
        data: data,
404
        current_page: oldData.data.page,
405
        per_page: oldData.data.pagesize,
406
        total: oldData.data.total_count
407
    };
408
};
409
410
BtccomConverter.prototype.convertAddress = function(oldData) {
411
    var data = {};
412
413
    data.address = oldData.data.address;
414
    data.hash160 = getBase58AddressHash160(oldData.data.address, this.network, this.useNewCashAddr).toString("hex").toUpperCase();
415
    data.balance = oldData.data.balance;
416
    data.received = oldData.data.received;
417
    data.sent = oldData.data.sent;
418
    data.transactions = oldData.data.tx_count;
419
    data.utxos = oldData.data.unspent_tx_count;
420
    data.unconfirmed_received = oldData.data.unconfirmed_received;
421
    data.unconfirmed_sent = oldData.data.unconfirmed_sent;
422
    data.unconfirmed_transactions = oldData.data.unconfirmed_tx_count;
423
    data.first_tx = oldData.data.first_tx;
424
    data.last_tx = oldData.data.last_tx;
425
426
    return data;
427
};
428
429
BtccomConverter.prototype.convertAddressUnspentOutputs = function(oldData, address) {
430
    var script = getAddressScriptInfo(address, this.network, this.useNewCashAddr);
431
    var script_hex = script.toString("hex");
432
    var script_asm = getScriptAsm(script);
433
    var type = getType(script);
434
    var data = oldData.data.list.map(function(utxo) {
435
        return {
436
            hash: utxo.tx_hash,
437
            confirmations: utxo.confirmations,
438
            value: utxo.value,
439
            index: utxo.tx_output_n,
440
            address: address,
441
            type: type,
442
            script: script_asm,
443
            script_hex: script_hex
444
        };
445
    });
446
447
    return {
448
        data: data,
449
        current_page: oldData.data.page,
450
        total: oldData.data.total_count
451
    };
452
};
453
454
exports = module.exports = BtccomConverter;
455