Merge pull request #169 from matiu/feature/opt-mongo

Feature/opt mongo
This commit is contained in:
Matias Alejo Garcia 2014-01-29 10:13:53 -08:00
commit f405f72e74
31 changed files with 988 additions and 642 deletions

View File

@ -37,13 +37,13 @@ exports.show = function(req, res) {
* Show block by Height
*/
exports.blockindex = function(req, res, next, height) {
Block.fromHeight(height, function(err, hash) {
Block.blockIndex(height, function(err, hashStr) {
if (err) {
console.log(err);
res.status(400).send('Bad Request'); // TODO
}
else {
res.jsonp(hash);
res.jsonp(hashStr);
}
});
};

View File

@ -3,7 +3,7 @@
/**
* Module dependencies.
*/
var Transaction = require('../models/Transaction');
var Transaction = require('../models/Transaction').class();
var Block = require('../models/Block');
var Address = require('../models/Address');
var async = require('async');
@ -138,32 +138,8 @@ exports.list = function(req, res, next) {
});
}
else {
Transaction
.find()
.limit(limit)
.sort('-time')
.exec(function(err, txs) {
if (err) {
res.status(500).send(err);
} else {
var txids = [];
for(var i=0;i<txs.length;i++) {
txids.push(txs[i].txid);
}
async.mapSeries(txids, getTransaction, function(err, alltxs) {
if (err) {
console.log(err);
res.status(404).send('TX not found');
}
res.jsonp({
txs: alltxs,
length: alltxs.length
});
});
}
});
res.jsonp({
txs: [],
});
}
};

View File

@ -5,7 +5,7 @@ require('classtool');
function spec() {
var async = require('async');
var TransactionItem = require('./TransactionItem');
var TransactionOut = require('./TransactionOut');
var BitcoreAddress = require('bitcore/Address').class();
var BitcoreUtil = require('bitcore/util/util');
@ -55,30 +55,44 @@ function spec() {
}
Address.prototype.update = function(next) {
var that = this;
var self = this;
async.series([
// TODO TXout!
//T
function (cb) {
TransactionItem.find({addr:that.addrStr}).sort({ts:-1}).exec(function(err,txItems){
/* function (cb) {
TransactionIn.find({addr:self.addrStr}).exec(function(err,txIn){
if (err) return cb(err);
txItems.forEach(function(txItem){
txIn.forEach(function(txItem){
// console.log(txItem.txid + ':' + txItem.ts+ ' : ' + (txItem.value_sat/parseFloat(BitcoreUtil.COIN) ) );
that.txApperances +=1;
that.balanceSat += txItem.value_sat;
that.transactions.push(txItem.txid);
if (txItem.value_sat > 0)
that.totalReceivedSat += txItem.value_sat;
else
that.totalSentSat += Math.abs(txItem.value_sat);
self.balanceSat += txItem.value_sat;
self.totalReceivedSat += txItem.value_sat;
});
return cb();
});
}
},
*/
function (cb) {
TransactionOut.find({addr:self.addrStr}).exec(function(err,txOut){
if (err) return cb(err);
txOut.forEach(function(txItem){
self.totalReceivedSat += txItem.value_sat;
self.transactions.push(txItem.txid);
if (! txItem.spendTxIdBuf) {
// unspent
self.balanceSat += txItem.value_sat;
self.txApperances +=1;
}
else {
// spent
self.totalSentSat += txItem.value_sat;
self.transactions.push(txItem.spendTxid);
self.txApperances +=2;
}
});
return cb();
});
},
], function (err) {
return next(err);
});

View File

@ -8,7 +8,7 @@ var mongoose = require('mongoose'),
RpcClient = require('bitcore/RpcClient').class(),
util = require('bitcore/util/util'),
BitcoreBlock= require('bitcore/Block').class(),
Transaction = require('./Transaction'),
TransactionOut = require('./TransactionOut'),
config = require('../../config/config')
;
@ -20,19 +20,51 @@ var BlockSchema = new Schema({
// For now we keep this as short as possible
// More fields will be propably added as we move
// forward with the UX
hash: {
type: String,
_id: {
type: Buffer,
index: true,
unique: true,
required: true,
},
time: Number,
nextBlockHash: String,
nextBlockHash: Buffer,
isOrphan: Boolean,
});
/**
* Validations
*/
BlockSchema.virtual('hash').get(function () {
return this._id;
});
BlockSchema.virtual('hash').set(function (hash) {
this._id = hash;
});
BlockSchema.virtual('hashStr').get(function () {
return this._id.toString('hex');
});
BlockSchema.virtual('hashStr').set(function (hashStr) {
if (hashStr)
this._id = new Buffer(hashStr,'hex');
else
this._id = null;
});
BlockSchema.virtual('nextBlockHashStr').get(function () {
return this.nextBlockHash.toString('hex');
});
BlockSchema.virtual('nextBlockHashStr').set(function (hashStr) {
if (hashStr)
this.nextBlockHash = new Buffer(hashStr,'hex');
else
this.nextBlockHash = null;
});
/*
BlockSchema.path('title').validate(function(title) {
@ -45,61 +77,56 @@ BlockSchema.path('title').validate(function(title) {
*/
BlockSchema.statics.customCreate = function(block, cb) {
var That= this;
var Self= this;
var BlockSchema = mongoose.model('Block', BlockSchema);
var newBlock = new That();
var newBlock = new Self();
newBlock.time = block.time ? block.time : Math.round(new Date().getTime() / 1000);
newBlock.hash = block.hash;
newBlock.nextBlockHash = block.nextBlockHash;
newBlock.hashStr = block.hash;
newBlock.nextBlockHashStr = block.nextBlockHash;
Transaction.createFromArray(block.tx, newBlock.time, function(err, inserted_txs) {
TransactionOut.createFromArray(block.tx, function(err, inserted_txs, update_addrs) {
if (err) return cb(err);
newBlock.save(function(err) {
return cb(err, newBlock, inserted_txs);
return cb(err, newBlock, inserted_txs, update_addrs);
});
});
};
BlockSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).exec(cb);
};
BlockSchema.statics.fromHeight = function(height, cb) {
BlockSchema.statics.blockIndex = function(height, cb) {
var rpc = new RpcClient(config.bitcoind);
var hash = {};
var hashStr = {};
rpc.getBlockHash(height, function(err, bh){
if (err) return cb(err);
hash.blockHash = bh.result;
cb(null, hash);
hashStr.blockHash = bh.result;
cb(null, hashStr);
});
};
BlockSchema.statics.fromHash = function(hash, cb) {
BlockSchema.statics.fromHash = function(hashStr, cb) {
var hash = new Buffer(hashStr, 'hex');
this.findOne({
hash: hash,
_id: hash,
}).exec(cb);
};
BlockSchema.statics.fromHashWithInfo = function(hash, cb) {
BlockSchema.statics.fromHashWithInfo = function(hashStr, cb) {
var That = this;
this.fromHash(hash, function(err, block) {
That.fromHash(hashStr, function(err, block) {
if (err) return cb(err);
if (!block) {
// No in mongo...but maybe in bitcoind... lets query it
block = new That();
block.hash = hash;
block.hashStr = hashStr;
block.getInfo(function(err, blockInfo) {
if (err) return cb(err);
if (!blockInfo) return cb();
@ -120,10 +147,10 @@ BlockSchema.statics.fromHashWithInfo = function(hash, cb) {
// TODO: Can we store the rpc instance in the Block object?
BlockSchema.methods.getInfo = function (next) {
var that = this;
var self = this;
var rpc = new RpcClient(config.bitcoind);
rpc.getBlock(this.hash, function(err, blockInfo) {
rpc.getBlock(self.hashStr, function(err, blockInfo) {
// Not found?
if (err && err.code === -5) return next();
@ -134,12 +161,10 @@ BlockSchema.methods.getInfo = function (next) {
* Any other way to lazy load a property in a mongoose object?
*/
that.info = blockInfo.result;
self.info = blockInfo.result;
self.info.reward = BitcoreBlock.getBlockValue(self.info.height) / util.COIN ;
that.info.reward = BitcoreBlock.getBlockValue(that.info.height) / util.COIN ;
//console.log("THAT", that);
return next(null, that.info);
return next(null, self.info);
});
};

View File

@ -1,361 +1,86 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
async = require('async'),
RpcClient = require('bitcore/RpcClient').class(),
Transaction = require('bitcore/Transaction').class(),
Address = require('bitcore/Address').class(),
BitcoreBlock = require('bitcore/Block').class(),
networks = require('bitcore/networks'),
util = require('bitcore/util/util'),
bignum = require('bignum'),
config = require('../../config/config'),
sockets = require('../controllers/socket.js'),
TransactionItem = require('./TransactionItem');
var CONCURRENCY = 5;
// TODO: use bitcore networks module
var genesisTXID = '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b';
/**
*/
var TransactionSchema = new Schema({
// For now we keep this as short as possible
// More fields will be propably added as we move
// forward with the UX
txid: {
type: String,
index: true,
unique: true,
},
/* TODO?
orphaned: {
type: Boolean,
default: false,
},
*/
time: Number,
});
/**
* Statics
*/
TransactionSchema.statics.load = function(id, cb) {
this.findOne({
_id: id
}).exec(cb);
};
require('classtool');
TransactionSchema.statics.fromId = function(txid, cb) {
this.findOne({
txid: txid,
}).exec(cb);
};
function spec() {
var util = require('bitcore/util/util'),
TransactionRpc = require('../../lib/TransactionRpc').class(),
TransactionOut = require('./TransactionOut'),
async = require('async');
var CONCURRENCY = 20;
TransactionSchema.statics.fromIdWithInfo = function(txid, cb) {
var That = this;
function Transaction() {
this.txid = null;
}
this.fromId(txid, function(err, tx) {
if (err) return cb(err);
if (!tx) {
// No in mongo...but maybe in bitcoind... lets query it
tx = new That();
tx.txid = txid;
tx.fillInfo(function(err, txInfo) {
if (err) return cb(err);
if (!txInfo) return cb();
tx.save(function(err) {
return cb(err,tx);
});
});
}
else {
tx.fillInfo(function(err) {
return cb(err,tx);
});
}
});
};
TransactionSchema.statics.createFromArray = function(txs, time, next) {
var that = this;
if (!txs) return next();
var mongo_txs = [];
async.forEachLimit(txs, CONCURRENCY, function(txid, cb) {
that.explodeTransactionItems( txid, time, function(err, addrs) {
if (err) return next(err);
if (addrs) {
async.each(addrs, function(addr){
sockets.broadcast_address_tx(addr, {'txid': txid});
});
}
that.create({txid: txid, time: time}, function(err, new_tx) {
if (err && ! err.toString().match(/E11000/)) return cb(err);
if (new_tx) {
mongo_txs.push(new_tx);
}
return cb();
});
});
},
function(err) {
return next(err, mongo_txs);
});
};
TransactionSchema.statics.explodeTransactionItems = function(txid, time, cb) {
var addrs = [];
// Is it from genesis block? (testnet==livenet)
// TODO: parse it from networks.genesisTX
if (txid === genesisTXID) return cb();
this.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) {
TransactionItem.create({
txid : txid,
value_sat : -1 * i.valueSat,
addr : i.addr,
index : i.n,
ts : 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 && !err.message.match(/E11000/) ) console.log (err);
async.forEachLimit(info.vout, CONCURRENCY, function(o, next_out) {
/*
* TODO Support multisigs
*/
if (o.value && o.scriptPubKey && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0]) {
TransactionItem.create({
txid : txid,
value_sat : o.valueSat,
addr : o.scriptPubKey.addresses[0], // TODO: only address 0?
index : o.n,
ts : 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 && ! err.toString().match(/E11000/)) return cb(err);
return cb(null, addrs);
});
});
});
};
TransactionSchema.statics.getOutpoints = function (tx, next) {
if (tx.isCoinBase()) return next();
var rpc = new RpcClient(config.bitcoind);
var network = ( config.network === 'testnet') ? networks.testnet : networks.livenet ;
async.forEachLimit(tx.ins, CONCURRENCY, function(i, cb) {
var outHash = i.getOutpointHash();
var outIndex = i.getOutpointIndex();
var outHashBase64 = outHash.reverse().toString('hex');
var c=0;
rpc.getRawTransaction(outHashBase64, function(err, txdata) {
var txin = new Transaction();
if (err || ! txdata.result) return cb( new Error('Input TX '+outHashBase64+' not found'));
var b = new Buffer(txdata.result,'hex');
txin.parse(b);
/*
*We have to parse it anyways. It will have outputs even it is a coinbase tx
if ( txin.isCoinBase() ) {
return cb();
}
*/
txin.outs.forEach( function(j) {
// console.log( c + ': ' + util.formatValue(j.v) );
if (c === outIndex) {
i.value = j.v;
// This is used for pay-to-pubkey transaction in which
// the pubkey is not provided on the input
var scriptPubKey = j.getScript();
var hash = scriptPubKey.simpleOutHash();
if (hash) {
var addr = new Address(network.addressPubkey, hash);
i.addrFromOutput = addr.toString();
}
}
c++;
});
return cb();
});
},
function(err) {
return next(err);
}
);
};
TransactionSchema.statics.queryInfo = function(txid, cb) {
var that = this;
var network = ( config.network === 'testnet') ? networks.testnet : networks.livenet ;
var rpc = new RpcClient(config.bitcoind);
rpc.getRawTransaction(txid, 1, function(err, txInfo) {
// Not found?
if (err && err.code === -5) return cb();
if (err) return cb(err);
var info = txInfo.result;
// Transaction parsing
var b = new Buffer(txInfo.result.hex,'hex');
Transaction.fromIdWithInfo = function (txid,cb) {
var tx = new Transaction();
tx.parse(b);
tx.txid = txid;
that.getOutpoints(tx, function(err) {
tx._fillInfo(function(err) {
if (err) return cb(err);
if (! tx.info ) return cb();
// Copy TX relevant values to .info
return cb(err,tx);
});
};
var c = 0;
var valueIn = bignum(0);
var valueOut = bignum(0);
if ( tx.isCoinBase() ) {
info.isCoinBase = true;
Transaction.prototype._fillInfo = function(next) {
var self = this;
TransactionRpc.getRpcInfo(self.txid, function(err, info) {
if (err) return next(err);
Transaction._fillOutpoints(info, function() {
self.info = info;
return next();
});
});
};
Transaction._fillOutpoints = function(info, cb) {
if (!info || info.isCoinBase) return cb();
var valueIn = 0;
var incompleteInputs = 0;
async.eachLimit(info.vin, CONCURRENCY, function(i, c_in) {
TransactionOut.fromTxIdN(i.txid, i.vout, function(err, out) {
if (err || !out || ! out.addr) {
console.log('Could not get TXouts in %s,%d from %s ', i.txid, i.vout, info.txid);
incompleteInputs = 1;
return c_in(); // error not scaled
}
i.addr = out.addr;
i.valueSat = out.value_sat;
i.value = out.value_sat / util.COIN;
valueIn += i.valueSat;
return c_in();
});
},
function () {
if (! incompleteInputs ) {
info.valueIn = valueIn / util.COIN;
info.fees = (valueIn - parseInt(info.valueOut * util.COIN)) / util.COIN ;
}
else {
tx.ins.forEach(function(i) {
if (i.value) {
info.vin[c].value = parseFloat(util.formatValue(i.value));
var n = util.valueToBigInt(i.value).toNumber();
info.vin[c].valueSat = n;
valueIn = valueIn.add( n );
var scriptSig = i.getScript();
var pubKey = scriptSig.simpleInPubKey();
// We check for pubKey in case a broken / strange TX.
if (pubKey) {
var pubKeyHash = util.sha256ripe160(pubKey);
var addr = new Address(network.addressPubkey, pubKeyHash);
var addrStr = addr.toString();
info.vin[c].addr = addrStr;
}
else {
if (i.addrFromOutput)
info.vin[c].addr = i.addrFromOutput;
}
}
else {
console.log('TX could not be parsed: %s,%d' ,txInfo.result.txid, c);
}
c++;
});
info.incompleteInputs = 1;
}
c=0;
tx.outs.forEach( function(i) {
var n = util.valueToBigInt(i.v).toNumber();
valueOut = valueOut.add(n);
info.vout[c].valueSat = n;
c++;
});
info.valueOut = valueOut / util.COIN;
if ( !tx.isCoinBase() ) {
info.valueIn = valueIn / util.COIN;
info.fees = (valueIn - valueOut) / util.COIN;
}
else {
var reward = BitcoreBlock.getBlockValue(info.height) / util.COIN;
info.vin[0].reward = reward;
info.valueIn = reward;
}
info.size = b.length;
return cb(null, info);
return cb();
});
});
};
};
return Transaction;
}
module.defineClass(spec);
TransactionSchema.methods.fillInfo = function(next) {
var that = this;
mongoose.model('Transaction', TransactionSchema).queryInfo(that.txid, function(err, info) {
if (err) return next(err);
that.info = info;
if (! that.info) {
return next();
}
else {
that.info.time = that.time;
return next();
}
});
};
module.exports = mongoose.model('Transaction', TransactionSchema);

View File

@ -1,54 +0,0 @@
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
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,
});
// 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;
}
}));
});
};
module.exports = mongoose.model('TransactionItem', TransactionItemSchema);

View File

@ -0,0 +1,215 @@
'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,
spendTxIdBuf: Buffer,
spendIndex: Number,
});
// 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._explodeTransactionOuts = 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();
TransactionRpc.getRpcInfo(txid, function(err, info) {
if (err || !info) return cb(err);
var bTxId = new Buffer(txid,'hex');
async.series([
// Input Outputs (mark them as spended)
function(p_c) {
if (info.isCoinBase) return p_c();
async.forEachLimit(info.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,
};
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', txid, err);
return cb(err);
}
}
return p_c();
});
},
// Parse Outputs
function(p_c) {
async.forEachLimit(info.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
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],
};
Self.update({txidBuf: bTxId, index: o.n}, data, {upsert: true}, next_out);
}
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 p_c();
});
}], function(err) {
return cb(null, addrs, is_new);
});
});
};
TransactionOutSchema.statics.createFromArray = function(txs, next) {
var Self = this;
if (!txs) return next();
var inserted_txs = [];
var updated_addrs = {};
async.forEachLimit(txs, CONCURRENCY, function(txid, cb, was_new) {
Self._explodeTransactionOuts( txid, function(err, addrs) {
if (err) return next(err);
if (was_new) {
inserted_txs.push(txid);
addrs.each(function(a) {
if ( !updated_addrs[a]) updated_addrs[a] = [];
updated_addrs[a].push(txid);
});
}
return cb();
});
},
function(err) {
return next(err, inserted_txs, updated_addrs);
});
};
module.exports = mongoose.model('TransactionOut', TransactionOutSchema);

41
dev-util/explode_tx.js Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
var mongoose= require('mongoose'),
config = require('../config/config');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var T = require('../app/models/TransactionOut');
// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var hash = process.argv[2] || '6749762ae220c10705556799dcec9bb6a54a7b881eb4b961323a3363b00db518';
mongoose.connect(config.db);
mongoose.connection.on('error', function(err) { console.log(err); });
mongoose.connection.on('open', function() {
var b = new Buffer(hash,'hex');
T.createFromArray([hash], function(err, ret) {
console.log('Err:');
console.log(err);
console.log('Ret:');
console.log(util.inspect(ret,{depth:null}));
mongoose.connection.close();
});
});

31
dev-util/get_block.js Executable file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var RpcClient = require('../node_modules/bitcore/RpcClient').class();
var config = require('../config/config');
// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var hash = process.argv[2] || 'f6c2901f39fd07f2f2e503183d76f73ecc1aee9ac9216fde58e867bc29ce674e';
hash = 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160';
var rpc = new RpcClient(config.bitcoind);
rpc.getRawTransaction( hash, 1, function(err, ret) {
console.log('Err:');
console.log(err);
console.log('Ret:');
console.log(util.inspect(ret, { depth: 10} ));
});

41
dev-util/get_outs.js Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
var mongoose= require('mongoose'),
config = require('../config/config');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var T = require('../app/models/TransactionOut');
// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var hash = process.argv[2] || 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160';
mongoose.connect(config.db);
mongoose.connection.on('error', function(err) { console.log(err); });
mongoose.connection.on('open', function() {
var b = new Buffer(hash,'hex');
T.find({txidBuf: b}, function(err, ret) {
console.log('Err:');
console.log(err);
console.log('Ret:');
console.log(util.inspect(ret,{depth:null}));
mongoose.connection.close();
});
});

39
dev-util/get_outs_addr.js Executable file
View File

@ -0,0 +1,39 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
var mongoose= require('mongoose'),
config = require('../config/config');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var T = require('../app/models/TransactionOut');
// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var hash = process.argv[2] || 'mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS';
mongoose.connect(config.db);
mongoose.connection.on('error', function(err) { console.log(err); });
mongoose.connection.on('open', function() {
T.find({addr: hash}, function(err, ret) {
console.log('Err:');
console.log(err);
console.log('Ret:');
console.log(util.inspect(ret,{depth:null}));
mongoose.connection.close();
});
});

35
dev-util/get_tx.js Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env node
'use strict';
var util = require('util');
var T = require('../app/models/Transaction').class();
var mongoose= require('mongoose'),
config = require('../config/config');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
// var hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var hash = process.argv[2] || 'e2253359458db3e732c82a43fc62f56979ff59928f25a2df34dfa443e9a41160';
mongoose.connect(config.db);
mongoose.connection.on('error', function(err) { console.log('error!', err); });
mongoose.connection.on('open', function() {
T.fromIdWithInfo(hash, function(err, ret) {
console.log('Err:');
console.log(err);
console.log('Ret:');
console.log(util.inspect(ret,{depth:null}));
mongoose.connection.close();
});
});

View File

@ -4,8 +4,11 @@ server=1
txindex=1
# Allow connections outsite localhost?
rpcallowip=192.168.1.*
rpcallowip='192.168.1.*'
#rpcallowip=192.168.1.*
#rpcallowip='192.168.1.*'
#rpcallowip=127.0.0.1
rpcallowip=*
rpcport=18332
testnet=3

View File

@ -63,7 +63,6 @@ if (!config.disableHistoricSync) {
historicSync.init({
skipDbConnection: true,
shouldBroadcast: true,
progressStep: 2,
networkName: config.network
}, function(err) {
if (err) {
@ -88,7 +87,8 @@ if (!config.disableP2pSync) {
ps.init({
skipDbConnection: true,
broadcast_txs: true,
broadcast_blocks: true
broadcast_address_tx: true,
broadcast_blocks: true,
}, function() {
ps.run();
});

View File

@ -102,7 +102,7 @@ function spec() {
p('ERROR: ' + self.error);
}
else {
self.syncPercentage = parseFloat(100 * self.syncedBlocks / self.blockChainHeight).toFixed(3);
self.syncPercentage = parseFloat(100 * (self.syncedBlocks + self.skippedBlocks) / self.blockChainHeight).toFixed(3);
if (self.syncPercentage > 100) self.syncPercentage = 100;
p(util.format('status: [%d%%] skipped: %d', self.syncPercentage, self.skippedBlocks));
@ -125,10 +125,7 @@ function spec() {
async.series([
// Already got it?
function(c) {
Block.findOne({
hash: blockHash
},
function(err, block) {
Block.fromHash(blockHash, function(err, block) {
if (err) {
p(err);
return c(err);
@ -238,8 +235,6 @@ function spec() {
},
// We are not using getBestBlockHash, because is not available in all clients
function(cb) {
if (!scanOpts.reverse) return cb();
self.rpc.getBlockCount(function(err, res) {
if (err) return cb(err);
self.blockChainHeight = res.result;
@ -304,6 +299,7 @@ function spec() {
if (!self.step) {
var step = parseInt( (self.blockChainHeight - self.syncedBlocks) / 1000);
if (self.opts.progressStep) {
@ -328,10 +324,7 @@ function spec() {
HistoricSync.prototype.smartImport = function(next) {
var self = this;
Block.findOne({
hash: self.genesis
},
function(err, b) {
Block.fromHash(self.genesis, function(err, b) {
if (err) return next(err);

View File

@ -62,7 +62,7 @@ function spec() {
if (this.verbose) {
console.log('[p2p_sync] Handle tx: ' + tx.hash);
}
this.sync.storeTxs([tx.hash], null, function(err) {
this.sync.storeTxs([tx.hash], function(err) {
if (err) {
console.log('[PeerSync.js.71:err:]',err); //TODO
console.log('[p2p_sync] Error in handle TX: ' + JSON.stringify(err));

View File

@ -7,13 +7,12 @@ function spec() {
var mongoose = require('mongoose');
var config = require('../config/config');
var Block = require('../app/models/Block');
var Transaction = require('../app/models/Transaction');
var TransactionOut = require('../app/models/TransactionOut');
var sockets = require('../app/controllers/socket.js');
var async = require('async');
function Sync() {
this.tx_count = 0;
}
Sync.prototype.init = function(opts, cb) {
@ -61,49 +60,56 @@ function spec() {
Sync.prototype.destroy = function(next) {
var self = this;
async.series([
function(b) { return self.db.collections.blocks.drop(b);},
function(b) { return self.db.collections.transactions.drop(b);},
function(b) { return self.db.collections.transactionitems.drop(b);},
function(b) { try {self.db.collections.blocks.drop(b);} catch (e) { return b(); } },
function(b) { try {self.db.collections.transactionitems.drop(b);} catch (e) { return b(); } },
function(b) { try {self.db.collections.transactionouts.drop(b);} catch (e) { return b(); } },
], next);
};
Sync.prototype.storeBlock = function(block, cb) {
var self = this;
Block.customCreate(block, function(err, block, inserted_txs){
Block.customCreate(block, function(err, block, inserted_txs, updated_addrs){
if (err) return cb(err);
if (block && self.opts.broadcast_blocks) {
sockets.broadcast_block(block);
}
if (inserted_txs && self.opts.broadcast_txs) {
inserted_txs.forEach(function(tx) {
sockets.broadcast_tx(tx);
});
}
if (inserted_txs)
self.tx_count += inserted_txs.length;
self._handleBroadcast(block, inserted_txs, updated_addrs);
return cb();
});
};
Sync.prototype.storeTxs = function(txs, inTime, cb) {
Sync.prototype._handleBroadcast = function(block, inserted_txs, updated_addrs) {
var self = this;
var time = inTime ? inTime : Math.round(new Date().getTime() / 1000);
if (block && self.opts.broadcast_blocks) {
sockets.broadcast_block(block);
}
Transaction.createFromArray(txs, time, function(err, inserted_txs) {
if (!err && inserted_txs && self.opts.broadcast_txs) {
if (inserted_txs && self.opts.broadcast_txs) {
inserted_txs.forEach(function(tx) {
sockets.broadcast_tx(tx);
});
}
if (updated_addrs && self.opts.broadcast_addresses) {
updated_addrs.forEach(function(addr, txs){
txs.forEach(function(addr, t){
sockets.broadcast_address_tx(addr, {'txid': t});
inserted_txs.forEach(function(tx) {
sockets.broadcast_tx(tx);
});
}
});
}
};
Sync.prototype.storeTxs = function(txs, cb) {
var self = this;
TransactionOut.createFromArray(txs, function(err, inserted_txs, updated_addrs) {
if (err) return cb(err);
self._handleBroadcast(null, inserted_txs, updated_addrs);
return cb(err);
});
};

69
lib/TransactionRpc.js Normal file
View File

@ -0,0 +1,69 @@
'use strict';
require('classtool');
function spec() {
var RpcClient = require('bitcore/RpcClient').class(),
// networks = require('bitcore/network'),
BitcoreTransaction = require('bitcore/Transaction').class(),
BitcoreBlock = require('bitcore/Block').class(),
util = require('bitcore/util/util'),
config = require('../config/config');
function TransactionRpc() {
this.dummy = null;
}
TransactionRpc._parseRpcResult = function(info) {
var b = new Buffer(info.hex,'hex');
var tx = new BitcoreTransaction();
tx.parse(b);
// Inputs
if (tx.isCoinBase()) {
info.isCoinBase = true;
var reward = BitcoreBlock.getBlockValue(info.height) / util.COIN;
info.vin[0].reward = reward;
info.valueIn = reward;
}
var n =0;
info.vin.forEach(function(i) {
i.n = n++;
});
// Outputs
var valueOut = 0;
info.vout.forEach( function(o) {
valueOut += o.value * util.COIN;
});
info.valueOut = valueOut / util.COIN;
info.size = b.length;
return info;
};
TransactionRpc.getRpcInfo = function(txid, cb) {
var Self = this;
var rpc = new RpcClient(config.bitcoind);
rpc.getRawTransaction(txid, 1, function(err, txInfo) {
// Not found?
if (err && err.code === -5) return cb();
if (err) return cb(err);
var info = Self._parseRpcResult(txInfo.result);
return cb(null,info);
});
};
return TransactionRpc;
}
module.defineClass(spec);

View File

@ -8,6 +8,7 @@ function($scope, $rootScope, $routeParams, $location, Global, Transaction, Trans
var pageNum = 0;
var pagesTotal = 1;
var COIN = 100000000;
var _aggregateItems = function(items) {
if (!items) return [];
@ -55,8 +56,8 @@ function($scope, $rootScope, $routeParams, $location, Global, Transaction, Trans
tmp[addr].items = [];
}
tmp[addr].valueSat += items[i].valueSat;
tmp[addr].value = tmp[addr].valueSat / 100000000;
tmp[addr].valueSat += items[i].value * COIN;
tmp[addr].value = items[i].value;
tmp[addr].items.push(items[i]);
tmp[addr].notAddr = notAddr;
tmp[addr].count++;

View File

@ -0,0 +1,70 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var mongoose = require('mongoose'),
assert = require('assert'),
fs = require('fs'),
util = require('util'),
config = require('../../config/config'),
TransactionOut = require('../../app/models/TransactionOut');
var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json'));
mongoose.connection.on('error', function(err) { console.log(err); });
describe('TransactionOut', function(){
before(function(done) {
mongoose.connect(config.db);
done();
});
after(function(done) {
mongoose.connection.close();
done();
});
txItemsValid.forEach( function(v) {
if (v.disabled) return;
it('test a exploding tx ' + v.txid, function(done) {
// Remove first
TransactionOut.removeFromTxId(v.txid, function(err) {
TransactionOut._explodeTransactionOuts(v.txid, function(err, tx) {
if (err) done(err);
TransactionOut
.fromTxId( v.txid, function(err, readItems) {
var unmatch={};
v.items.forEach(function(validItem){
unmatch[validItem.addr] =1;
});
v.items.forEach(function(validItem){
var readItem = readItems.shift();
assert.equal(readItem.addr,validItem.addr);
assert.equal(readItem.value_sat,validItem.value_sat);
assert.equal(readItem.index,validItem.index);
assert.equal(readItem.spendIndex, null);
assert.equal(readItem.spendTxIdBuf, null);
delete unmatch[validItem.addr];
});
var valid = util.inspect(v.items, { depth: null });
assert(!Object.keys(unmatch).length,'\n\tUnmatchs:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems);
done();
});
});
});
});
});
});

View File

@ -0,0 +1,68 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var mongoose = require('mongoose'),
assert = require('assert'),
fs = require('fs'),
util = require('util'),
async = require('async'),
config = require('../../config/config'),
TransactionOut = require('../../app/models/TransactionOut');
var spentValid = JSON.parse(fs.readFileSync('test/model/spent.json'));
mongoose.connection.on('error', function(err) { console.log(err); });
describe('TransactionOut Expenses', function(){
before(function(done) {
mongoose.connect(config.db);
// lets spend!
async.each(Object.keys(spentValid),
function(txid,c_out) {
async.each(spentValid[txid],
function(i,c_in) {
TransactionOut._explodeTransactionOuts(i.txid, function(err) {
return c_in();
});
},
function(err) {
console.log('Done spending ', txid); //TODO
return c_out();
}
);
},
function(err) {
console.log('[transactionouts.js.88]'); //TODO
return done();
}
);
});
after(function(done) {
mongoose.connection.close();
done();
});
Object.keys(spentValid).forEach( function(txid) {
it('test result of spending tx ' + txid, function(done) {
var s = spentValid[txid];
var c=0;
TransactionOut.fromTxId( txid, function(err, readItems) {
s.forEach( function(v) {
assert.equal(readItems[c].spendTxIdBuf.toString('hex'),v.txid);
assert.equal(readItems[c].spendIndex,v.n);
c++;
});
done();
});
});
});
});

View File

@ -5,9 +5,9 @@ process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var
assert = require('assert'),
fs = require('fs'),
mongoose= require('mongoose'),
config = require('../../config/config'),
Address = require('../../app/models/Address').class();
mongoose= require('mongoose'),
addrValid = JSON.parse(fs.readFileSync('test/model/addr.json'));
describe('Address balances', function(){
@ -27,7 +27,7 @@ describe('Address balances', function(){
console.log(v.addr + " => disabled in JSON");
}
else {
it('Info for:' + v.addr, function(done) {
it('Info for: ' + v.addr, function(done) {
this.timeout(5000);
var a = new Address(v.addr);
@ -37,10 +37,12 @@ describe('Address balances', function(){
assert.equal(v.addr, a.addrStr);
console.log("TX count:" + a.transactions.length);
if (v.balance) assert.equal(v.balance, a.balance);
if (v.totalReceived) assert.equal(v.totalReceived, a.totalReceived);
if (v.totalSent) assert.equal(v.totalSent, a.totalSent);
if (v.balance) assert.equal(v.balance, a.balance, 'balance: ' + a.balance);
if (v.totalReceived) assert.equal(v.totalReceived, a.totalReceived, 'received: ' + a.totalReceived );
if (v.totalSent) assert.equal(v.totalSent, a.totalSent, 'send: ' + a.totalSent);
if (v.txApperances)
assert.equal(v.txApperances, a.txApperances, 'txApperances: ' + a.txApperances );
if (v.transactions) {

View File

@ -1,58 +1,68 @@
[
{
"addr": "mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ",
"balance": 43.1
"balance": 43.1,
"txApperances": 19
},
{
"addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS",
"balance": 0,
"totalReceived": 50,
"totalSent": 50.0
"totalSent": 50.0,
"txApperances": 2
},
{
"addr": "muyg1K5WsHkfMVCkUXU2y7Xp5ZD6RGzCeH",
"balance": 0.38571339,
"totalReceived": 0.38571339,
"totalSent": 0
"totalSent": 0,
"txApperances": 1
},
{
"addr": "mhPEfAmeKVwT7arwMYbhwnL2TfwuWbP4r4",
"balance": 1065,
"totalReceived": 1069,
"totalSent": 4
"totalSent": 4,
"txApperances": 13
},
{
"addr": "n47CfqnKWdNwqY1UWxTmNJAqYutFxdH3zY",
"balance": 0,
"totalReceived":26.4245,
"totalSent": 26.4245
"totalSent": 26.4245,
"txApperances": 4
},
{
"addr": "mzSyyXgofoBxpr6gYcU3cV345G8hJpixRd",
"balance": 0,
"totalReceived":3.9775,
"totalSent": 3.9775
"totalSent": 3.9775,
"txApperances": 2
},
{
"addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29",
"balance": 1036.76206223,
"totalReceived": 1036.76206223,
"txApperances": 2034,
"balance": 1049.69744099,
"totalReceived": 1049.69744099,
"totalSent": 0
},
{
"addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H",
"txApperances": 13343,
"balance": 46413.0,
"totalReceived": 357130.17644359,
"totalSent": 310717.17644359
},
{
"addr": "mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM",
"txApperances": 1,
"balance": 0.01979459,
"totalReceived": 0.01979459,
"totalSent": 0,
"transactions": [ "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5" ]
},
{
"disabled":1,
"addr": "mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5",
"balance": 10580.50027254,
"totalReceived": 12157.65075053,

View File

@ -1,11 +1,12 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var TESTING_BLOCK = '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74';
var
var
mongoose= require('mongoose'),
assert = require('assert'),
config = require('../../config/config'),
@ -28,21 +29,46 @@ describe('Block fromHashWithInfo', function(){
it('should poll block\'s info from mongoose', function(done) {
var block2 = Block.fromHashWithInfo(TESTING_BLOCK, function(err, b2) {
Block.fromHashWithInfo(TESTING_BLOCK, function(err, b2) {
if (err) done(err);
assert.equal(b2.hash, TESTING_BLOCK);
var h = new Buffer(TESTING_BLOCK,'hex');
assert(b2.hashStr === TESTING_BLOCK);
assert.equal(b2.hashStr, TESTING_BLOCK);
done();
});
});
it('should poll block\'s info from bitcoind', function(done) {
var block2 = Block.fromHashWithInfo(TESTING_BLOCK, function(err, b2) {
Block.fromHashWithInfo(TESTING_BLOCK, function(err, b2) {
if (err) done(err);
assert.equal(b2.info.hash, TESTING_BLOCK);
assert.equal(b2.info.chainwork, '000000000000000000000000000000000000000000000000001b6dc969ffe847');
done();
});
});
it('hash Virtuals SET', function(done) {
var b = new Block();
b.hashStr = 'a1a2';
assert.equal(b.hash.toString('hex'),'a1a2');
b.nextBlockHashStr = 'a1a3';
assert.equal(b.nextBlockHash.toString('hex'),'a1a3');
done();
});
it('hash Virtuals GET', function(done) {
var b = new Block();
b.hash = new Buffer('a1a2','hex');
assert.equal(b.hashStr,'a1a2');
b.nextBlockHash = new Buffer('b2b1','hex');
assert.equal(b.nextBlockHashStr,'b2b1');
done();
});
});

View File

@ -0,0 +1,32 @@
{
"21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237": [
{
"txid": "bcd8da8ee847da377f8aaca92502c05e5f914c6a2452753146013b0e642a25a0",
"n": 0
},
{
"txid": "deb7bddc67e936ae49b97a97885d29e60afc6f6784f6d871f2904614a67250f5",
"n": 0
}
],
"b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee": [
{
"txid": "c0c46d6be0183f52c88afe2d649800ecdaa7594ee390c77bafbd06322e6c823d",
"n": 11
},
{
"txid": "d60e980419c5a8abd629fdea5032d561678b62e23b3fdba62b42f410c5a29560",
"n": 1
}
],
"ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b": [
{
"txid": "aa21822f1a69bc54e5a4ab60b25c09503702a821379fd2dfbb696b8ada4ce5b9",
"n": 0
},
{
"txid": "a33bd24a47ab6f23758ed09e05716f809614f2e280e5a05a317ec6d839e81225",
"n": 1
}
]
}

View File

@ -1,20 +1,16 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var
mongoose= require('mongoose'),
var mongoose= require('mongoose'),
assert = require('assert'),
config = require('../../config/config'),
Transaction = require('../../app/models/Transaction'),
TransactionItem = require('../../app/models/TransactionItem'),
fs = require('fs'),
util = require('util');
Transaction = require('../../app/models/Transaction').class();
var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json'));
mongoose.connection.on('error', function(err) { console.log(err); });
describe('Transaction', function(){
@ -28,17 +24,17 @@ describe('Transaction', function(){
mongoose.connection.close();
done();
});
it('should pool tx\'s object from mongoose', function(done) {
var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c';
var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c';
it('txid ' + txid, function(done) {
Transaction.fromIdWithInfo(txid, function(err, tx) {
if (err) done(err);
assert.equal(tx.txid, txid);
assert(!tx.info.isCoinBase);
for(var i=0; i<20; i++)
assert(parseFloat(tx.info.vin[i].value) === parseFloat(50));
assert(tx.info.vin[0].addr === 'msGKGCy2i8wbKS5Fo1LbWUTJnf1GoFFG59');
assert(tx.info.vin[1].addr === 'mfye7oHsdrHbydtj4coPXCasKad2eYSv5P');
assert(parseFloat(tx.info.vin[i].value) === parseFloat(50), 'input '+i);
assert(tx.info.vin[0].addr === 'msGKGCy2i8wbKS5Fo1LbWUTJnf1GoFFG59', 'addr 0');
assert(tx.info.vin[1].addr === 'mfye7oHsdrHbydtj4coPXCasKad2eYSv5P', 'addr 1');
done();
});
});
@ -103,12 +99,10 @@ describe('Transaction', function(){
var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608';
it('create TX on the fly ' + txid2, function(done) {
TransactionItem.remove({txid: txid2}, function(err) {
Transaction.fromIdWithInfo(txid2, function(err, tx) {
if (err) return done(err);
assert.equal(tx.info.txid, txid2);
done();
});
Transaction.fromIdWithInfo(txid2, function(err, tx) {
if (err) return done(err);
assert.equal(tx.info.txid, txid2);
done();
});
});
@ -122,43 +116,5 @@ describe('Transaction', function(){
});
});
txItemsValid.forEach( function(v) {
if (v.disabled) return;
it('test a exploding TX ' + v.txid, function(done) {
// Remove first
TransactionItem.remove({txid: v.txid}, function(err) {
var now = Math.round(new Date().getTime() / 1000);
Transaction.explodeTransactionItems(v.txid, now, function(err, tx) {
if (err) done(err);
TransactionItem
.fromTxId( v.txid, function(err, readItems) {
var unmatch={};
v.items.forEach(function(validItem){
unmatch[validItem.addr] =1;
});
v.items.forEach(function(validItem){
var readItem = readItems.shift();
assert.equal(readItem.addr,validItem.addr);
assert.equal(readItem.value_sat,validItem.value_sat);
assert.equal(readItem.index,validItem.index);
delete unmatch[validItem.addr];
});
var valid = util.inspect(v.items, { depth: null });
assert(!Object.keys(unmatch).length,
'\n\tUnmatchs:' + Object.keys(unmatch) + "\n\n" +valid + '\nvs.\n' + readItems);
done();
});
});
});
});
});
});

View File

@ -0,0 +1,67 @@
#!/usr/bin/env node
'use strict';
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var mongoose = require('mongoose'),
assert = require('assert'),
fs = require('fs'),
util = require('util'),
async = require('async'),
config = require('../../config/config'),
TransactionOut = require('../../app/models/TransactionOut');
var spentValid = JSON.parse(fs.readFileSync('test/model/spent.json'));
mongoose.connection.on('error', function(err) { console.log(err); });
describe('TransactionOut Expenses', function(){
before(function(done) {
mongoose.connect(config.db);
// lets spend!
async.each(Object.keys(spentValid),
function(txid,c_out) {
async.each(spentValid[txid],
function(i,c_in) {
TransactionOut._explodeTransactionOuts(i.txid, function(err) {
return c_in();
});
},
function(err) {
return c_out();
}
);
},
function(err) {
console.log('[transactionouts.js.88]'); //TODO
return done();
}
);
});
after(function(done) {
mongoose.connection.close();
done();
});
Object.keys(spentValid).forEach( function(txid) {
it('test result of spending tx ' + txid, function(done) {
var s = spentValid[txid];
var c=0;
TransactionOut.fromTxId( txid, function(err, readItems) {
s.forEach( function(v) {
assert.equal(readItems[c].spendTxIdBuf.toString('hex'),v.txid);
assert.equal(readItems[c].spendIndex,v.n);
c++;
});
done();
});
});
});
});

View File

@ -6,11 +6,6 @@
{
"txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237",
"items": [
{
"addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z",
"value_sat": -166224000,
"index": 0
},
{
"addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA",
"value_sat": 134574000,
@ -26,11 +21,6 @@
{
"txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee",
"items": [
{
"addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA",
"value_sat": -40790667,
"index": 0
},
{
"addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T",
"value_sat": 19300000,
@ -46,16 +36,6 @@
{
"txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b",
"items": [
{
"addr": "mzeiUi4opeheWYveXqp8ebqHyVwYGA2s3x",
"value_sat": -1225871,
"index": 0
},
{
"addr": "mtMLijHAbG8CsgBbQGajsqav9p9wKUYad5",
"value_sat": -1201823,
"index": 1
},
{
"addr": "mhqyL1nDQDo1WLH9qH8sjRjx2WwrnmAaXE",
"value_sat": 1327746,

View File

@ -1,25 +0,0 @@
#!/usr/bin/env node
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var RpcClient = require('../node_modules/bitcore/RpcClient').class();
var config = require('../config/config');
var block_hash = process.argv[2] || '0000000000b6288775bbd326bedf324ca8717a15191da58391535408205aada4';
var rpc = new RpcClient(config.bitcoind);
var block = rpc.getBestBlockHash( function(err, block) {
console.log("Err:");
console.log(err);
console.log("Block info:");
console.log(block);
});