darkpay-insight-apiz
Version:
A Darkpay blockchain REST and web socket API service for Bitcore Node.
435 lines (369 loc) • 11.3 kB
JavaScript
;
var bitcore = require('darkpay-bitcore-lib');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
var BufferUtil = bitcore.util.buffer;
var Common = require('./common');
var async = require('async');
var MAXINT = 0xffffffff; // Math.pow(2, 32) - 1;
function TxController(node) {
this.node = node;
this.common = new Common({log: this.node.log});
}
TxController.prototype.show = function(req, res) {
if (req.transaction) {
res.jsonp(req.transaction);
}
};
/**
* Find transaction by hash ...
*/
TxController.prototype.transaction = function(req, res, next) {
var self = this;
var txid = req.params.txid;
this.node.getDetailedTransaction(txid, function(err, transaction) {
if (err && err.code === -5) {
return self.common.handleErrors(null, res);
} else if(err) {
return self.common.handleErrors(err, res);
}
self.transformTransaction(transaction, function(err, transformedTransaction) {
if (err) {
return self.common.handleErrors(err, res);
}
req.transaction = transformedTransaction;
next();
});
});
};
TxController.prototype.transformTransaction = function(transaction, options, callback) {
if (_.isFunction(options)) {
callback = options;
options = {};
}
$.checkArgument(_.isFunction(callback));
var confirmations = 0;
if(transaction.height >= 0) {
confirmations = this.node.services.bitcoind.height - transaction.height + 1;
}
var transformed = {
txid: transaction.hash,
version: transaction.version,
locktime: transaction.locktime
};
if (((transaction.version >> 8) & 0xFF) == 2)
transformed.isCoinStake = true;
if(transaction.coinbase) {
transformed.vin = [
{
coinbase: transaction.inputs[0].script,
sequence: transaction.inputs[0].sequence,
n: 0
}
];
} else {
transformed.vin = transaction.inputs.map(this.transformInput.bind(this, options));
}
options.isCoinStake = transformed.isCoinStake
transformed.vout = transaction.outputs.map(this.transformOutput.bind(this, options));
transformed.blockhash = transaction.blockHash;
transformed.blockheight = transaction.height;
transformed.confirmations = confirmations;
// TODO consider mempool txs with receivedTime?
var time = transaction.blockTimestamp ? transaction.blockTimestamp : Math.round(Date.now() / 1000);
transformed.time = time;
if (transformed.confirmations) {
transformed.blocktime = transformed.time;
}
if(transaction.coinbase) {
transformed.isCoinBase = true;
}
transformed.valueOut = transaction.outputSatoshis / 1e8;
transformed.size = transaction.hex.length / 2; // in bytes
if (!transaction.coinbase) {
transformed.valueIn = transaction.inputSatoshis / 1e8;
if (_.isUndefined(options.feeSatoshis))
transformed.fees = transaction.feeSatoshis / 1e8;
else
transformed.fees = options.feeSatoshis / 1e8;
}
callback(null, transformed);
};
TxController.prototype.transformInput = function(options, input, index) {
// Input scripts are validated and can be assumed to be valid
var transformed = {
txid: input.prevTxId,
vout: input.outputIndex,
sequence: input.sequence,
n: index
};
if (!options.noScriptSig) {
transformed.scriptSig = {
hex: input.script
};
if (!options.noAsm) {
transformed.scriptSig.asm = input.scriptAsm;
}
transformed.witnessStack = input.witnessStack;
}
transformed.type = input.type;
transformed.addr = input.address;
transformed.valueSat = input.satoshis;
transformed.value = input.satoshis / 1e8;
transformed.doubleSpentTxID = null; // TODO
//transformed.isConfirmed = null; // TODO
//transformed.confirmations = null; // TODO
//transformed.unconfirmedInput = null; // TODO
if (transformed.type == 'anon') {
transformed.num_inputs = input.num_inputs;
transformed.ring_size = input.ring_size;
}
return transformed;
};
function getVarIntAsFloat(offset, data_bytes)
{
var fl = 0.0;
var i;
for (i = 0; i < 10; ++i)
{
var b = data_bytes[offset++];
//rv += (b & 0x7F) << (7*i);
fl += (b & 0x7F) * Math.pow(2.0, 7*i)
if (!(b & 0x80) || offset >= data_bytes.length)
break
}
return [fl, i];
}
TxController.prototype.transformOutput = function(options, output, index) {
var transformed = {
value: (output.satoshis / 1e8).toFixed(8),
n: index,
scriptPubKey: {
hex: output.script
}
};
transformed.type = output.type;
if (!options.noAsm) {
transformed.scriptPubKey.asm = output.scriptAsm;
}
if (!options.noSpent) {
transformed.spentTxId = output.spentTxId || null;
transformed.spentIndex = _.isUndefined(output.spentIndex) ? null : output.spentIndex;
transformed.spentHeight = output.spentHeight || null;
}
if (output.address) {
transformed.scriptPubKey.addresses = [output.address];
var address = bitcore.Address(output.address); //TODO return type from bitcore-node
transformed.scriptPubKey.type = address.type;
}
if (output.type == 'blind' || output.type == 'anon')
{
transformed.valueCommitment = output.valueCommitment;
transformed.rp_exponent = output.rp_exponent;
transformed.rp_mantissa = output.rp_mantissa;
transformed.rp_min_value = output.rp_min_value;
transformed.rp_max_value = output.rp_max_value;
transformed.rp_size = output.rp_size;
}
if (output.data)
{
transformed.data = output.data;
transformed.v = []
var data_bytes = BufferUtil.hexToBuffer(output.data);
var offset = 0;
if (options.isCoinStake)
{
if (data_bytes.length >= 4)
{
var height = 0;
for (var i = 3; i >= 0; i--)
height = (height << 8) + data_bytes[i];
transformed.v.push({name:"height",value:height});
offset+=4;
};
};
while(offset < data_bytes.length)
{
var type = data_bytes[offset];
offset++;
if (type == 5) // VOTE
{
if (offset + 4 > data_bytes.length)
{
transformed.v.push({name:"error",value:type});
break;
};
var vote = 0;
for (var i = offset+3; i >= offset; i--)
vote = (vote << 8) + data_bytes[i];
offset+=4;
var strvote = "proposal " + (vote & 0xFFFF) + ", option " + (vote >> 16);
transformed.v.push({name:"vote",value:strvote});
} else
if (type == 6) // FEE
{
if (offset + 1 > data_bytes.length)
{
transformed.v.push({name:"error",value:type});
break;
};
var vi = getVarIntAsFloat(offset, data_bytes);
var fee = vi[0];
offset += vi[1]+1;
options.feeSatoshis = fee;
fee /= 1e8;
transformed.v.push({name:"fee",value:fee});
} else
if (type == 7) // DEV_FUND_CFWD
{
if (offset + 1 > data_bytes.length)
{
transformed.v.push({name:"error",value:type});
break;
};
var vi = getVarIntAsFloat(offset, data_bytes);
var cfwd = vi[0];
offset += vi[1]+1;
cfwd /= 1e8;
transformed.v.push({name:"foundation-fund",value:cfwd});
} else
{
transformed.v.push({name:"unknown",value:type});
break;
};
}
}
return transformed;
};
TxController.prototype.transformInvTransaction = function(transaction) {
var self = this;
var valueOut = 0;
var vout = [];
for (var i = 0; i < transaction.outputs.length; i++) {
var output = transaction.outputs[i];
valueOut += output.satoshis;
if (output.script) {
var address = output.script.toAddress(self.node.network);
if (address) {
var obj = {};
obj[address.toString()] = output.satoshis;
vout.push(obj);
}
}
}
var isRBF = _.some(_.map(transaction.inputs, 'sequenceNumber'), function(seq) {
return seq < MAXINT - 1;
});
var transformed = {
txid: transaction.hash,
valueOut: valueOut / 1e8,
vout: vout,
isRBF: isRBF,
};
return transformed;
};
TxController.prototype.rawTransaction = function(req, res, next) {
var self = this;
var txid = req.params.txid;
this.node.getTransaction(txid, function(err, transaction) {
if (err && err.code === -5) {
return self.common.handleErrors(null, res);
} else if(err) {
return self.common.handleErrors(err, res);
}
req.rawTransaction = {
'rawtx': transaction.toBuffer().toString('hex')
};
next();
});
};
TxController.prototype.showRaw = function(req, res) {
if (req.rawTransaction) {
res.jsonp(req.rawTransaction);
}
};
TxController.prototype.list = function(req, res) {
var self = this;
var blockHash = req.query.block;
var address = req.query.address;
var page = parseInt(req.query.pageNum) || 0;
var pageLength = 10;
var pagesTotal = 1;
if(blockHash) {
self.node.getBlockOverview(blockHash, function(err, block) {
if(err && err.code === -5) {
return self.common.handleErrors(null, res);
} else if(err) {
return self.common.handleErrors(err, res);
}
var totalTxs = block.txids.length;
var txids;
if(!_.isUndefined(page)) {
var start = page * pageLength;
txids = block.txids.slice(start, start + pageLength);
pagesTotal = Math.ceil(totalTxs / pageLength);
} else {
txids = block.txids;
}
async.mapSeries(txids, function(txid, next) {
self.node.getDetailedTransaction(txid, function(err, transaction) {
if (err) {
return next(err);
}
self.transformTransaction(transaction, next);
});
}, function(err, transformed) {
if(err) {
return self.common.handleErrors(err, res);
}
res.jsonp({
pagesTotal: pagesTotal,
txs: transformed
});
});
});
} else if(address) {
var options = {
from: page * pageLength,
to: (page + 1) * pageLength
};
self.node.getAddressHistory(address, options, function(err, result) {
if(err) {
return self.common.handleErrors(err, res);
}
var txs = result.items.map(function(info) {
return info.tx;
}).filter(function(value, index, self) {
return self.indexOf(value) === index;
});
async.map(
txs,
function(tx, next) {
self.transformTransaction(tx, next);
},
function(err, transformed) {
if (err) {
return self.common.handleErrors(err, res);
}
res.jsonp({
pagesTotal: Math.ceil(result.totalCount / pageLength),
txs: transformed
});
}
);
});
} else {
return self.common.handleErrors(new Error('Block hash or address expected'), res);
}
};
TxController.prototype.send = function(req, res) {
var self = this;
this.node.sendTransaction(req.body.rawtx, function(err, txid) {
if(err) {
// TODO handle specific errors
return self.common.handleErrors(err, res);
}
res.json({'txid': txid});
});
};
module.exports = TxController;