insight-ui-zcash/app/models/TransactionItem.js

184 lines
4.2 KiB
JavaScript

'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
async = require('async'),
Transaction = require('./Transaction').class(),
Schema = mongoose.Schema;
var CONCURRENCY = 15;
var TransactionItemSchema = new Schema({
txid: String,
index: Number,
addr: {
type: String,
index: true,
},
// OJO: mongoose doesnt accept camelcase for field names
// <0 is Input >0 is Output
value_sat: Number,
ts: Number,
});
// TODO: use bitcore networks module
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
// Compound index
TransactionItemSchema.index({txid: 1, index: 1, value_sat: 1}, {unique: true, dropDups: true});
TransactionItemSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).exec(cb);
};
TransactionItemSchema.statics.fromTxId = function(txid, cb) {
this.find({
txid: txid,
}).exec(function (err,items) {
// sort by 1) value sign 2) index
return cb(err,items.sort(function(a,b){
var sa= a.value_sat < 0 ? -1 : 1;
var sb= b.value_sat < 0 ? -1 : 1;
if (sa !== sb) {
return sa-sb;
}
else {
return a.index - b.index;
}
}));
});
};
TransactionItemSchema.statics.explodeTransactionItems = function(txid, cb) {
var Self = this;
var addrs = [];
var is_new = true;
// Is it from genesis block? (testnet==livenet)
// TODO: parse it from networks.genesisTX
if (txid === genesisTXID) return cb();
Transaction.queryInfo(txid, function(err, info) {
if (err || !info) return cb(err);
var index = 0;
info.vin.forEach( function(i){
i.n = index++;
});
async.forEachLimit(info.vin, CONCURRENCY, function(i, next_in) {
if (i.addr && i.value) {
Self.create({
txid : txid,
value_sat : -1 * i.valueSat,
addr : i.addr,
index : i.n,
ts : info.time,
}, next_in);
if (addrs.indexOf(i.addr) === -1) {
addrs.push(i.addr);
}
}
else {
if ( !i.coinbase ) {
console.log ('WARN in TX: %s: could not parse INPUT %d', txid, i.n);
}
return next_in();
}
},
function (err) {
if (err) {
if (err.message.match(/E11000/)) {
is_new = false;
}
else {
console.log('ERR at TX %s: %s', txid, err);
return cb(err);
}
}
// Parse Outputs
async.forEachLimit(info.vout, CONCURRENCY, function(o, next_out) {
/*
* TODO Support multisigs
*/
if (o.value && o.scriptPubKey && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0]) {
Self.create({
txid : txid,
value_sat : o.valueSat,
addr : o.scriptPubKey.addresses[0], // TODO: only address 0?
index : o.n,
ts : info.time,
}, next_out);
if (addrs.indexOf(o.scriptPubKey.addresses[0]) === -1) {
addrs.push(o.scriptPubKey.addresses[0]);
}
}
else {
console.log ('WARN in TX: %s could not parse OUTPUT %d', 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', txid, err);
return cb(err);
}
}
return cb(null, addrs, is_new);
});
});
});
};
TransactionItemSchema.statics.createFromArray = function(txs, next) {
if (!txs) return next();
var inserted_txs = [];
async.forEachLimit(txs, CONCURRENCY, function(txid, cb, was_new) {
TransactionItemSchema.explodeTransactionItems( txid, function(err, addrs) {
if (err) return next(err);
if (addrs) {
async.each(addrs, function(addr){
sockets.broadcast_address_tx(addr, {'txid': txid});
});
}
if (was_new) {
inserted_txs.push(txid);
}
return cb();
});
},
function(err) {
return next(err, inserted_txs);
});
};
module.exports = mongoose.model('TransactionItem', TransactionItemSchema);