fcoin/lib/utils/btcutils.js
2016-11-19 05:29:29 -08:00

404 lines
7.6 KiB
JavaScript

/*!
* btcutils.js - bitcoin-related utils for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var BN = require('bn.js');
var constants = require('../protocol/constants');
var utils = require('./utils');
var btcutils = exports;
/**
* Convert a compact number to a big number.
* Used for `block.bits` -> `target` conversion.
* @param {Number} compact
* @returns {BN}
*/
btcutils.fromCompact = function fromCompact(compact) {
var exponent = compact >>> 24;
var negative = (compact >>> 23) & 1;
var mantissa = compact & 0x7fffff;
var num;
if (compact === 0)
return new BN(0);
// Logic ported from btcd since
// the bitcoind code is a nightmare.
if (exponent <= 3) {
mantissa >>>= 8 * (3 - exponent);
num = new BN(mantissa);
} else {
num = new BN(mantissa);
num.iushln(8 * (exponent - 3));
}
if (negative)
num.ineg();
return num;
};
/**
* Convert a big number to a compact number.
* Used for `target` -> `block.bits` conversion.
* @param {BN} num
* @returns {Number}
*/
btcutils.toCompact = function toCompact(num) {
var mantissa, exponent, compact;
if (num.cmpn(0) === 0)
return 0;
exponent = num.byteLength();
// Logic ported from btcd since
// the bitcoind code is a nightmare.
if (exponent <= 3) {
mantissa = num.toNumber();
mantissa <<= 8 * (3 - exponent);
} else {
mantissa = num.ushrn(8 * (exponent - 3)).toNumber();
}
if (mantissa & 0x800000) {
mantissa >>= 8;
exponent++;
}
compact = (exponent << 24) | mantissa;
if (num.isNeg())
compact |= 0x800000;
compact >>>= 0;
return compact;
};
/**
* Verify proof-of-work.
* @returns {Boolean}
*/
btcutils.verifyPOW = function verifyPOW(hash, bits) {
var target = btcutils.fromCompact(bits);
if (target.isNeg() || target.cmpn(0) === 0)
return false;
hash = new BN(hash, 'le');
if (hash.cmp(target) > 0)
return false;
return true;
};
/**
* Calculate block subsidy.
* @param {Number} height - Reward era by height.
* @returns {Amount}
*/
btcutils.getReward = function getReward(height, interval) {
var halvings = height / interval | 0;
assert(height >= 0, 'Bad height for reward.');
// BIP 42 (well, our own version of it,
// since we can only handle 32 bit shifts).
// https://github.com/bitcoin/bips/blob/master/bip-0042.mediawiki
if (halvings >= 33)
return 0;
// We need to shift right by `halvings`,
// but 50 btc is a 33 bit number, so we
// cheat. We only start halving once the
// halvings are at least 1.
if (halvings === 0)
return 5000000000;
return 2500000000 >>> (halvings - 1);
};
/**
* Calculate minimum fee based on rate and size.
* @param {Number?} size
* @param {Rate?} rate - Rate of satoshi per kB.
* @returns {Amount} fee
*/
btcutils.getMinFee = function getMinFee(size, rate) {
var fee;
if (rate == null)
rate = constants.tx.MIN_RELAY;
fee = Math.floor(rate * size / 1000);
if (fee === 0 && rate > 0)
fee = rate;
return fee;
};
/**
* Calculate the minimum fee in order for the transaction
* to be relayable, but _round to the nearest kilobyte
* when taking into account size.
* @param {Number?} size
* @param {Rate?} rate - Rate of satoshi per kB.
* @returns {Amount} fee
*/
btcutils.getRoundFee = function getRoundFee(size, rate) {
var fee;
if (rate == null)
rate = constants.tx.MIN_RELAY;
fee = rate * Math.ceil(size / 1000);
if (fee === 0 && rate > 0)
fee = rate;
return fee;
};
/**
* Calculate a fee rate based on size and fees.
* @param {Number} size
* @param {Amount} fee
* @returns {Rate}
*/
btcutils.getRate = function getRate(size, fee) {
return Math.floor(fee * 1000 / size);
};
/**
* Safely convert satoshis to a BTC string.
* This function explicitly avoids any
* floating point arithmetic.
* @param {Amount} value - Satoshis.
* @returns {String} BTC string.
*/
btcutils.btc = function btc(value) {
var negative = false;
var hi, lo, result;
if (utils.isFloat(value))
return value;
assert(utils.isInt(value), 'Non-satoshi value for conversion.');
if (value < 0) {
value = -value;
negative = true;
}
assert(value <= utils.MAX_SAFE_INTEGER, 'Number exceeds 2^53-1.');
value = value.toString(10);
assert(value.length <= 16, 'Number exceeds 2^53-1.');
while (value.length < 9)
value = '0' + value;
hi = value.slice(0, -8);
lo = value.slice(-8);
lo = lo.replace(/0+$/, '');
if (lo.length === 0)
lo += '0';
result = hi + '.' + lo;
if (negative)
result = '-' + result;
return result;
};
/**
* Safely convert a BTC string to satoshis.
* This function explicitly avoids any
* floating point arithmetic. It also does
* extra validation to ensure the resulting
* Number will be 53 bits or less.
* @param {String} value - BTC
* @returns {Amount} Satoshis.
* @throws on parse error
*/
btcutils.satoshi = function satoshi(value) {
var negative = false;
var parts, hi, lo, result;
if (utils.isInt(value))
return value;
assert(utils.isFloat(value), 'Non-BTC value for conversion.');
if (value[0] === '-') {
negative = true;
value = value.substring(1);
}
parts = value.split('.');
assert(parts.length <= 2, 'Bad decimal point.');
hi = parts[0] || '0';
lo = parts[1] || '0';
hi = hi.replace(/^0+/, '');
lo = lo.replace(/0+$/, '');
assert(hi.length <= 8, 'Number exceeds 2^53-1.');
assert(lo.length <= 8, 'Too many decimal places.');
if (hi.length === 0)
hi = '0';
while (lo.length < 8)
lo += '0';
hi = parseInt(hi, 10);
lo = parseInt(lo, 10);
assert(hi < 90071992 || (hi === 90071992 && lo <= 54740991),
'Number exceeds 2^53-1.');
result = hi * 100000000 + lo;
if (negative)
result = -result;
return result;
};
/**
* Test and validate a satoshi value (Number).
* @param {Number?} value
* @returns {Boolean}
*/
btcutils.isSatoshi = function isSatoshi(value) {
if (typeof value !== 'number')
return false;
try {
utils.satoshi(value);
return true;
} catch (e) {
return false;
}
};
/**
* Test and validate a BTC string.
* @param {String?} value
* @returns {Boolean}
*/
btcutils.isBTC = function isBTC(value) {
if (typeof value !== 'string')
return false;
try {
utils.btc(value);
return true;
} catch (e) {
return false;
}
};
/**
* Sort an array of transactions in dependency order.
* @param {TX[]} txs
* @returns {TX[]}
*/
btcutils.sortTX = function sortTX(txs) {
var depMap = {};
var count = {};
var result = [];
var top = [];
var map = txs;
var i, j, tx, hash, input;
var prev, hasDeps, deps;
if (Array.isArray(txs)) {
map = {};
for (i = 0; i < txs.length; i++) {
tx = txs[i];
hash = tx.hash('hex');
map[hash] = tx;
}
}
for (i = 0; i < txs.length; i++) {
tx = txs[i];
hash = tx.hash('hex');
hasDeps = false;
count[hash] = 0;
for (j = 0; j < tx.inputs.length; j++) {
input = tx.inputs[j];
prev = input.prevout.hash;
if (!map[prev])
continue;
count[hash] += 1;
hasDeps = true;
if (!depMap[prev])
depMap[prev] = [];
depMap[prev].push(tx);
}
if (hasDeps)
continue;
top.push(tx);
}
for (i = 0; i < top.length; i++) {
tx = top[i];
hash = tx.hash('hex');
result.push(tx);
deps = depMap[hash];
if (!deps)
continue;
for (j = 0; j < deps.length; j++) {
tx = deps[j];
hash = tx.hash('hex');
if (--count[hash] === 0)
top.push(tx);
}
}
return result;
};