271 lines
6.4 KiB
JavaScript
271 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
var mongoose = require('mongoose'),
|
|
async = require('async'),
|
|
util = require('bitcore/util/util'),
|
|
TransactionRpc = require('../../lib/TransactionRpc').class(),
|
|
Schema = mongoose.Schema;
|
|
|
|
var CONCURRENCY = 15;
|
|
// TODO: use bitcore networks module
|
|
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
|
|
|
|
var TransactionOutSchema = new Schema({
|
|
txidBuf: {
|
|
type: Buffer,
|
|
index: true,
|
|
},
|
|
index: Number,
|
|
addr: {
|
|
type: String,
|
|
index: true,
|
|
},
|
|
value_sat: Number,
|
|
fromOrphan: Boolean,
|
|
|
|
spendTxIdBuf: Buffer,
|
|
spendIndex: Number,
|
|
spendFromOrphan: Boolean,
|
|
});
|
|
|
|
|
|
// Compound index
|
|
|
|
TransactionOutSchema.index({txidBuf: 1, index: 1}, {unique: true, sparse: true});
|
|
TransactionOutSchema.index({spendTxIdBuf: 1, spendIndex: 1}, {unique: true, sparse: true});
|
|
|
|
TransactionOutSchema.virtual('txid').get(function () {
|
|
return this.txidBuf.toString('hex');
|
|
});
|
|
|
|
|
|
TransactionOutSchema.virtual('spendTxid').get(function () {
|
|
if (!this.spendTxIdBuf) return (null);
|
|
return this.spendTxIdBuf.toString('hex');
|
|
});
|
|
|
|
|
|
TransactionOutSchema.virtual('txid').set(function (txidStr) {
|
|
if (txidStr)
|
|
this.txidBuf = new Buffer(txidStr,'hex');
|
|
else
|
|
this.txidBuf = null;
|
|
});
|
|
|
|
TransactionOutSchema.statics.fromTxId = function(txid, cb) {
|
|
var txidBuf = new Buffer(txid, 'hex');
|
|
|
|
this.find({
|
|
txidBuf: txidBuf,
|
|
}).exec(function (err,items) {
|
|
|
|
// sort by index
|
|
return cb(err,items.sort(function(a,b){
|
|
return a.index - b.index;
|
|
}));
|
|
});
|
|
};
|
|
|
|
TransactionOutSchema.statics.fromTxIdOne = function(txid, cb) {
|
|
var txidBuf = new Buffer(txid, 'hex');
|
|
|
|
this.find({
|
|
txidBuf: txidBuf,
|
|
}).exec(function (err,item) {
|
|
return cb(err, item[0]);
|
|
});
|
|
};
|
|
|
|
|
|
TransactionOutSchema.statics.fromTxIdN = function(txid, n, cb) {
|
|
var txidBuf = new Buffer(txid, 'hex');
|
|
this.findOne({
|
|
txidBuf: txidBuf, index: n
|
|
}).exec(cb);
|
|
};
|
|
|
|
TransactionOutSchema.statics.removeFromTxId = function(txid, cb) {
|
|
var txidBuf = new Buffer(txid, 'hex');
|
|
this.remove({ txidBuf: txidBuf }).exec(cb);
|
|
};
|
|
|
|
|
|
|
|
TransactionOutSchema.statics.storeTransactionOuts = function(txInfo, fromOrphan, cb) {
|
|
|
|
var Self = this;
|
|
var addrs = [];
|
|
var is_new = true;
|
|
|
|
if (txInfo.hash) {
|
|
|
|
// adapt bitcore TX object to bitcoind JSON response
|
|
txInfo.txid = txInfo.hash;
|
|
|
|
var count = 0;
|
|
txInfo.vin = txInfo.in.map(function (txin) {
|
|
var i = {};
|
|
|
|
if (txin.coinbase) {
|
|
txInfo.isCoinBase = true;
|
|
}
|
|
else {
|
|
i.txid= txin.prev_out.hash;
|
|
i.vout= txin.prev_out.n;
|
|
};
|
|
i.n = count++;
|
|
return i;
|
|
});
|
|
|
|
|
|
count = 0;
|
|
txInfo.vout = txInfo.out.map(function (txout) {
|
|
var o = {};
|
|
|
|
o.value = txout.value;
|
|
o.n = count++;
|
|
|
|
if (txout.addrStr){
|
|
o.scriptPubKey = {};
|
|
o.scriptPubKey.addresses = [txout.addrStr];
|
|
}
|
|
return o;
|
|
});
|
|
|
|
}
|
|
|
|
var bTxId = new Buffer(txInfo.txid,'hex');
|
|
|
|
|
|
async.series([
|
|
// Input Outpoints (mark them as spended)
|
|
function(p_c) {
|
|
if (txInfo.isCoinBase) return p_c();
|
|
async.forEachLimit(txInfo.vin, CONCURRENCY,
|
|
function(i, next_out) {
|
|
var b = new Buffer(i.txid,'hex');
|
|
var data = {
|
|
txidBuf: b,
|
|
index: i.vout,
|
|
|
|
spendTxIdBuf: bTxId,
|
|
spendIndex: i.n,
|
|
};
|
|
if (fromOrphan) data.spendFromOrphan = true;
|
|
Self.update({txidBuf: b, index: i.vout}, data, {upsert: true}, next_out);
|
|
},
|
|
function (err) {
|
|
if (err) {
|
|
if (!err.message.match(/E11000/)) {
|
|
console.log('ERR at TX %s: %s', txInfo.txid, err);
|
|
return cb(err);
|
|
}
|
|
}
|
|
return p_c();
|
|
});
|
|
},
|
|
// Parse Outputs
|
|
function(p_c) {
|
|
async.forEachLimit(txInfo.vout, CONCURRENCY,
|
|
function(o, next_out) {
|
|
if (o.value && o.scriptPubKey &&
|
|
o.scriptPubKey.addresses &&
|
|
o.scriptPubKey.addresses[0] &&
|
|
! o.scriptPubKey.addresses[1] // TODO : not supported
|
|
){
|
|
|
|
// This is only to broadcast (WIP)
|
|
// if (addrs.indexOf(o.scriptPubKey.addresses[0]) === -1) {
|
|
// addrs.push(o.scriptPubKey.addresses[0]);
|
|
// }
|
|
|
|
var data = {
|
|
txidBuf: bTxId,
|
|
index : o.n,
|
|
|
|
value_sat : o.value * util.COIN,
|
|
addr : o.scriptPubKey.addresses[0],
|
|
};
|
|
if (fromOrphan) data.fromOrphan = true;
|
|
Self.update({txidBuf: bTxId, index: o.n}, data, {upsert: true}, next_out);
|
|
}
|
|
else {
|
|
console.log ('WARN in TX: %s could not parse OUTPUT %d', txInfo.txid, o.n);
|
|
return next_out();
|
|
}
|
|
},
|
|
function (err) {
|
|
if (err) {
|
|
if (err.message.match(/E11000/)) {
|
|
is_new = false;
|
|
}
|
|
else {
|
|
console.log('ERR at TX %s: %s', txInfo.txid, err);
|
|
return cb(err);
|
|
}
|
|
}
|
|
return p_c();
|
|
});
|
|
}], function(err) {
|
|
return cb(err, addrs, is_new);
|
|
});
|
|
};
|
|
|
|
|
|
// txs can be a [hashes] or [txObjects]
|
|
TransactionOutSchema.statics.createFromTxs = function(txs, fromOrphan, next) {
|
|
var Self = this;
|
|
|
|
if (typeof fromOrphan === 'function') {
|
|
next = fromOrphan;
|
|
fromOrphan = false;
|
|
}
|
|
|
|
if (!txs) return next();
|
|
|
|
var inserted_txs = [];
|
|
var updated_addrs = {};
|
|
|
|
async.forEachLimit(txs, CONCURRENCY, function(t, each_cb) {
|
|
|
|
var txInfo;
|
|
|
|
async.series([
|
|
function(a_cb) {
|
|
if (typeof t !== 'string') {
|
|
txInfo = t;
|
|
return a_cb();
|
|
}
|
|
|
|
// Is it from genesis block? (testnet==livenet)
|
|
// TODO: parse it from networks.genesisTX?
|
|
if (t === genesisTXID) return a_cb();
|
|
|
|
TransactionRpc.getRpcInfo(t, function(err, inInfo) {
|
|
txInfo =inInfo;
|
|
return a_cb(err);
|
|
});
|
|
},
|
|
function(a_cb) {
|
|
if (!txInfo) return a_cb();
|
|
|
|
Self.storeTransactionOuts(txInfo, fromOrphan, function(err, addrs) {
|
|
if (err) return a_cb(err);
|
|
return a_cb();
|
|
});
|
|
}],
|
|
function(err) {
|
|
return each_cb(err);
|
|
});
|
|
},
|
|
function(err) {
|
|
return next(err, inserted_txs, updated_addrs);
|
|
});
|
|
};
|
|
|
|
|
|
module.exports = mongoose.model('TransactionOut', TransactionOutSchema);
|