Completed
Pull Request — master (#99)
by Ruben de
01:02
created

lib/btccom.convert.js   D

Complexity

Total Complexity 68
Complexity/F 1.94

Size

Lines of Code 444
Function Count 35

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 0
wmc 68
c 3
b 1
f 0
nc 64
mnd 3
bc 65
fnc 35
dl 0
loc 444
rs 4.2608
bpm 1.8571
cpm 1.9428
noi 2

27 Functions

Rating   Name   Duplication   Size   Complexity  
A btccom.convert.js ➔ prettifyAsm 0 7 2
A btccom.convert.js ➔ getScriptAsm 0 10 2
A btccom.convert.js ➔ getAddressScriptInfo 0 10 2
A btccom.convert.js ➔ getType 0 10 2
B btccom.convert.js ➔ getBase58AddressHash160 0 21 6
A BtccomConverter.convertAddress 0 18 1
B btccom.convert.js ➔ convertBtccomOutputScriptType 0 20 9
A BtccomConverter.paginationParams 0 12 3
A BtccomConverter.convertTxs 0 12 1
A btccom.convert.js ➔ flattenAddresses 0 9 3
D btccom.convert.js ➔ convertBtccomTxToBlocktrail 0 102 10
A BtccomConverter.getUrlForTransactions 0 3 1
A btccom.convert.js ➔ utcTimestampToISODateStr 0 3 1
A BtccomConverter.convertAddressTxs 0 56 1
A BtccomConverter.getUrlForAddress 0 3 1
A BtccomConverter.getUrlForAddressTransactions 0 3 1
A BtccomConverter.getUrlForAddressUnspent 0 3 1
A BtccomConverter.handleErros 0 11 3
A BtccomConverter.getUrlForAllBlocks 0 3 1
A BtccomConverter.getUrlForBlock 0 3 1
B BtccomConverter.convertBlock 0 29 1
B BtccomConverter.convertAddressUnspentOutputs 0 24 1
A BtccomConverter.convertTx 0 5 1
A BtccomConverter.getUrlForTransaction 0 3 1
A BtccomConverter.getUrlForBlockTransaction 0 3 1
A BtccomConverter.convertBlockTxs 0 16 1
A btccom.convert.js ➔ BtccomConverter 0 11 1

How to fix   Complexity   

Complexity

Complex classes like lib/btccom.convert.js 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.block_time = utcTimestampToISODateStr(tx.block_time);
124
    data.block_hash = null; // TODO: missing block_hash
125
    data.confirmations = tx.confirmations;
126
    data.is_coinbase = tx.is_coinbase;
127
128
    var totalInputValue;
129
    if (data.is_coinbase) {
130
        totalInputValue = tx.outputs[0].value - tx.fee;
131
    } else {
132
        totalInputValue = tx.inputs_value;
133
    }
134
135
    data.total_input_value = totalInputValue;
136
    data.total_output_value = tx.outputs.reduce(function(total, output) {
137
        return total + output.value;
138
    }, 0);
139
    data.total_fee = tx.fee;
140
    data.inputs = [];
141
    data.outputs = [];
142
    data.opt_in_rbf = false;
143
144
    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...
145
        var input = tx.inputs[inputIdx];
146
        var scriptType;
147
        var inputValue;
148
        var inputTxid;
149
        var outpointIdx;
150
151
        if (input.sequence < bitcoinJS.Transaction.DEFAULT_SEQUENCE - 1) {
152
            data.opt_in_rbf = true;
153
        }
154
155
        if (data.is_coinbase && input.prev_position === -1 &&
156
            input.prev_tx_hash === "0000000000000000000000000000000000000000000000000000000000000000") {
157
            scriptType = "coinbase";
158
            inputTxid = null;
159
            inputValue = totalInputValue;
160
            outpointIdx = 0xffffffff;
161
        } else {
162
            scriptType = input.prev_type;
163
            inputValue = input.prev_value;
164
            inputTxid = input.prev_tx_hash;
165
            outpointIdx = input.prev_position;
166
        }
167
168
        data.inputs.push({
169
            index: parseInt(inputIdx, 10),
170
            output_hash: inputTxid,
171
            output_index: outpointIdx,
172
            value: inputValue,
173
            sequence: input.sequence,
174
            address: flattenAddresses(input.prev_addresses),
175
            type: convertBtccomOutputScriptType(scriptType),
176
            script_signature: input.script_hex
177
        });
178
    }
179
180
    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...
181
        var output = tx.outputs[outIdx];
182
183
        data.outputs.push({
184
            index: parseInt(outIdx, 10),
185
            value: output.value,
186
            address: flattenAddresses(output.addresses),
187
            type: convertBtccomOutputScriptType(output.type),
188
            script: prettifyAsm(output.script_asm),
189
            script_hex: output.script_hex,
190
            spent_hash: output.spent_by_tx,
191
            spent_index: output.spent_by_tx_position
192
        });
193
    }
194
195
    data.size = tx.size;
196
    data.is_double_spend = tx.is_double_spend;
197
198
    data.lock_time_timestamp = null;
199
    data.lock_time_block_height = null;
200
    if (tx.lock_time) {
201
        if (tx.lock_time < blocktrail.LOCK_TIME_TIMESTAMP_THRESHOLD) {
202
            data.lock_time_block_height = tx.lock_time;
203
        } else {
204
            data.lock_time_timestamp = tx.lock_time;
205
        }
206
    }
207
208
    // Extra fields from Btc.com
209
    data.is_sw_tx = tx.is_sw_tx;
210
    data.weight = tx.weight;
211
    data.witness_hash = tx.witness_hash;
212
    data.lock_time  = tx.lock_time;
213
    data.sigops = tx.sigops;
214
    data.version = tx.version;
215
216
    return data;
217
}
218
219
BtccomConverter.prototype.paginationParams = function(params) {
220
    if (!params) {
221
        return params;
222
    }
223
224
    if (typeof params.limit !== "undefined") {
225
        params.pagesize = params.limit;
226
        delete params.limit;
227
    }
228
229
    return params;
230
};
231
232
BtccomConverter.prototype.getUrlForBlock = function(blockHash) {
233
    return "/block/" + blockHash;
234
};
235
236
BtccomConverter.prototype.getUrlForTransaction = function(txId) {
237
    return "/tx/" + txId + "?verbose=3";
238
};
239
240
BtccomConverter.prototype.getUrlForTransactions = function(txIds) {
241
    return "/tx/" + txIds.join(",") + "?verbose=3";
242
};
243
244
BtccomConverter.prototype.getUrlForBlockTransaction = function(blockHash) {
245
    return "/block/" + blockHash + "/tx";
246
};
247
248
BtccomConverter.prototype.getUrlForAddress = function(address) {
249
    return "/address/" + address;
250
};
251
252
BtccomConverter.prototype.getUrlForAddressTransactions = function(address) {
253
    return "/address/" + address + "/tx?verbose=3";
254
};
255
256
BtccomConverter.prototype.getUrlForAddressUnspent = function(address) {
257
    return "/address/" + address + "/unspent";
258
};
259
260
BtccomConverter.prototype.getUrlForAllBlocks = function() {
261
    return "/all-blocks";
262
};
263
264
BtccomConverter.prototype.handleErros = function(self, data) {
265
    if (data.err_no === 0 || data.data !== null) {
266
        return data;
267
    } else {
268
        return {
269
            err_no: data.err_no,
270
            err_msg: data.err_msg,
271
            data: data.data
272
        };
273
    }
274
};
275
276
BtccomConverter.prototype.convertBlock = function(oldData) {
277
    var data = {
278
        hash: oldData.data.hash,
279
        version: oldData.data.version,
280
        height: oldData.data.height,
281
        block_time: utcTimestampToISODateStr(oldData.data.timestamp),
282
        arrival_time: utcTimestampToISODateStr(oldData.data.timestamp),
283
        bits: oldData.data.bits,
284
        nonce: oldData.data.nonce,
285
        merkleroot: oldData.data.mrkl_root,
286
        prev_block: oldData.data.prev_block_hash,
287
        next_block: oldData.data.next_block_hash,
288
        byte_size: oldData.data.stripped_size,
289
        difficulty: Math.floor(oldData.data.difficulty),
290
        transactions: oldData.data.tx_count,
291
        reward_block: oldData.data.reward_block,
292
        reward_fees: oldData.data.reward_fees,
293
        created_at: oldData.data.created_at,
294
        confirmations: oldData.data.confirmations,
295
        is_orphan: oldData.data.is_orphan,
296
        is_sw_block: oldData.data.is_sw_block,
297
        weight: oldData.data.weight,
298
        miningpool_name: oldData.data.miningpool_name || null,
299
        miningpool_url: oldData.data.miningpool_url || null,
300
        miningpool_slug: oldData.data.miningpool_slug || null
301
    };
302
303
    return data;
304
};
305
306
307
BtccomConverter.prototype.convertBlockTxs = function(oldData) {
308
309
    var list = [];
310
    oldData.data.list.forEach(function(oldTx) {
311
        var resTx = convertBtccomTxToBlocktrail(oldTx);
312
313
        list.push(resTx);
314
    });
315
316
    return {
317
        data: list,
318
        current_page: oldData.data.page,
319
        per_page: oldData.data.pagesize,
320
        total: oldData.data.total_count
321
    };
322
};
323
324
BtccomConverter.prototype.convertTx = function(oldData, rawTx) {
325
    var data = convertBtccomTxToBlocktrail(oldData.data);
326
    data.raw = rawTx;
327
    return data;
328
};
329
330
BtccomConverter.prototype.convertTxs = function(oldData) {
331
    var res = {};
332
333
    oldData.data
334
        .filter(function(tx) { return !!tx; })
335
        .forEach(function(oldTx) {
336
            var tx = convertBtccomTxToBlocktrail(oldTx);
337
            res[tx.hash] = tx;
338
        });
339
340
    return {data: res};
341
};
342
343
BtccomConverter.prototype.convertAddressTxs = function(oldData) {
344
    var data = oldData.data.list.map(function(tx) {
345
        var res = {};
346
347
        res.hash = tx.hash;
348
        res.time = utcTimestampToISODateStr(tx.block_time);
349
        res.confirmations = tx.confirmations;
350
        res.block_height = tx.block_height;
351
        res.block_hash = null; // @TODO: missing block_hash
352
        res.is_coinbase = tx.is_coinbase;
353
        res.total_input_value = tx.inputs_value;
354
        res.total_output_value = tx.outputs_value;
355
        res.total_fee = tx.fee;
356
357
        res.inputs = tx.inputs.map(function(input, inIdx) {
358
            return {
359
                index: inIdx,
360
                output_hash: input.prev_tx_hash,
361
                output_index: input.prev_position,
362
                value: input.prev_value,
363
                address: flattenAddresses(input.prev_addresses),
364
                type:  res.is_coinbase ? res.is_coinbase : convertBtccomOutputScriptType(input.prev_type),
365
                script_signature: input.script_hex
366
            };
367
        });
368
369
        res.outputs = tx.outputs.map(function(output, outIdx) {
370
            return {
371
                index: outIdx,
372
                value: output.value,
373
                address: flattenAddresses(output.addresses),
374
                type: convertBtccomOutputScriptType(output.type),
375
                script: prettifyAsm(output.script_asm),
376
                spent_hash: output.spent_by_tx || null,
377
                script_hex: output.script_hex,
378
                spent_index: output.spent_by_tx_position
379
            };
380
        });
381
382
        // Extra fields from Btc.com
383
        res.is_double_spend = tx.is_double_spend;
384
        res.is_sw_tx = tx.is_sw_tx;
385
        res.weight = tx.weight;
386
        res.witness_hash = tx.witness_hash;
387
        res.version = tx.version;
388
389
        return res;
390
    });
391
392
    return {
393
        data: data,
394
        current_page: oldData.data.page,
395
        per_page: oldData.data.pagesize,
396
        total: oldData.data.total_count
397
    };
398
};
399
400
BtccomConverter.prototype.convertAddress = function(oldData) {
401
    var data = {};
402
403
    data.address = oldData.data.address;
404
    data.hash160 = getBase58AddressHash160(oldData.data.address, this.network, this.useNewCashAddr).toString("hex").toUpperCase();
405
    data.balance = oldData.data.balance;
406
    data.received = oldData.data.received;
407
    data.sent = oldData.data.sent;
408
    data.transactions = oldData.data.tx_count;
409
    data.utxos = oldData.data.unspent_tx_count;
410
    data.unconfirmed_received = oldData.data.unconfirmed_received;
411
    data.unconfirmed_sent = oldData.data.unconfirmed_sent;
412
    data.unconfirmed_transactions = oldData.data.unconfirmed_tx_count;
413
    data.first_tx = oldData.data.first_tx;
414
    data.last_tx = oldData.data.last_tx;
415
416
    return data;
417
};
418
419
BtccomConverter.prototype.convertAddressUnspentOutputs = function(oldData, address) {
420
    var script = getAddressScriptInfo(address, this.network, this.useNewCashAddr);
421
    var script_hex = script.toString("hex");
422
    var script_asm = getScriptAsm(script);
423
    var type = getType(script);
424
    var data = oldData.data.list.map(function(utxo) {
425
        return {
426
            hash: utxo.tx_hash,
427
            confirmations: utxo.confirmations,
428
            value: utxo.value,
429
            index: utxo.tx_output_n,
430
            address: address,
431
            type: type,
432
            script: script_asm,
433
            script_hex: script_hex
434
        };
435
    });
436
437
    return {
438
        data: data,
439
        current_page: oldData.data.page,
440
        total: oldData.data.total_count
441
    };
442
};
443
444
exports = module.exports = BtccomConverter;
445