From 3c34718774e1df0192a2efe962c7b55032d69271 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 10 Jul 2015 17:40:54 -0300 Subject: [PATCH] add unconfirmed inputs banner --- app/controllers/transactions.js | 9 +- lib/BlockDb.js | 305 +++++++++++++++++++------------- lib/TransactionDb.js | 1 - package.json | 2 +- 4 files changed, 187 insertions(+), 130 deletions(-) diff --git a/app/controllers/transactions.js b/app/controllers/transactions.js index d8c5644..bb16219 100644 --- a/app/controllers/transactions.js +++ b/app/controllers/transactions.js @@ -59,10 +59,15 @@ exports.transaction = function(req, res, next, txid) { tDb.fromIdWithInfo(txid, function(err, tx) { if (err || ! tx) return common.handleErrors(err, res); - else { + + bdb.fillVinConfirmations(tx.info, function(err) { + if (err) + return common.handleErrors(err, res); + req.transaction = tx.info; return next(); - } + }); + }); }; diff --git a/lib/BlockDb.js b/lib/BlockDb.js index b229f14..41c5b86 100644 --- a/lib/BlockDb.js +++ b/lib/BlockDb.js @@ -1,35 +1,37 @@ 'use strict'; -var imports = require('soop').imports(); -var TIMESTAMP_PREFIX = 'bts-'; // bts- => -var PREV_PREFIX = 'bpr-'; // bpr- => -var NEXT_PREFIX = 'bne-'; // bne- => -var MAIN_PREFIX = 'bma-'; // bma- => (0 is unconnected) -var TIP = 'bti-'; // bti = : last block on the chain -var LAST_FILE_INDEX = 'file-'; // last processed file index +var imports = require('soop').imports(); +var TIMESTAMP_PREFIX = 'bts-'; // bts- => +var PREV_PREFIX = 'bpr-'; // bpr- => +var NEXT_PREFIX = 'bne-'; // bne- => +var MAIN_PREFIX = 'bma-'; // bma- => (0 is unconnected) +var TIP = 'bti-'; // bti = : last block on the chain +var LAST_FILE_INDEX = 'file-'; // last processed file index // txid - blockhash mapping (only for confirmed txs, ONLY FOR BEST BRANCH CHAIN) var IN_BLK_PREFIX = 'btx-'; //btx- = -var MAX_OPEN_FILES = 500; -var CONCURRENCY = 5; +var MAX_OPEN_FILES = 500; +var CONCURRENCY = 5; var DFLT_REQUIRED_CONFIRMATIONS = 1; /** -* Module dependencies. -*/ -var levelup = require('levelup'), - config = require('../config/config'); -var db = imports.db || levelup(config.leveldb + '/blocks',{maxOpenFiles: MAX_OPEN_FILES} ); + * Module dependencies. + */ +var levelup = require('levelup'), + config = require('../config/config'); +var db = imports.db || levelup(config.leveldb + '/blocks', { + maxOpenFiles: MAX_OPEN_FILES +}); var Rpc = imports.rpc || require('./Rpc'); -var async = require('async'); +var async = require('async'); var logger = require('./logger').logger; -var info = logger.info; +var info = logger.info; var BlockDb = function(opts) { - this.txDb = require('./TransactionDb').default(); + this.txDb = require('./TransactionDb').default(); this.safeConfirmations = config.safeConfirmations || DEFAULT_SAFE_CONFIRMATIONS; BlockDb.super(this, arguments); }; @@ -41,8 +43,10 @@ BlockDb.prototype.close = function(cb) { BlockDb.prototype.drop = function(cb) { var path = config.leveldb + '/blocks'; db.close(function() { - require('leveldown').destroy(path, function () { - db = levelup(path,{maxOpenFiles: MAX_OPEN_FILES} ); + require('leveldown').destroy(path, function() { + db = levelup(path, { + maxOpenFiles: MAX_OPEN_FILES + }); return cb(); }); }); @@ -51,31 +55,27 @@ BlockDb.prototype.drop = function(cb) { BlockDb.prototype._addBlockScript = function(b, height) { var time_key = TIMESTAMP_PREFIX + - ( b.time || Math.round(new Date().getTime() / 1000) ); + (b.time || Math.round(new Date().getTime() / 1000)); - return [ - { - type: 'put', - key: time_key, - value: b.hash, - }, - { - type: 'put', - key: MAIN_PREFIX + b.hash, - value: height, - }, - { - type: 'put', - key:PREV_PREFIX + b.hash, - value: b.previousblockhash, - }, - ]; + return [{ + type: 'put', + key: time_key, + value: b.hash, + }, { + type: 'put', + key: MAIN_PREFIX + b.hash, + value: height, + }, { + type: 'put', + key: PREV_PREFIX + b.hash, + value: b.previousblockhash, + }, ]; }; BlockDb.prototype._delTxsScript = function(txs) { - var dbScript =[]; + var dbScript = []; - for(var ii in txs){ + for (var ii in txs) { dbScript.push({ type: 'del', key: IN_BLK_PREFIX + txs[ii], @@ -85,13 +85,13 @@ BlockDb.prototype._delTxsScript = function(txs) { }; BlockDb.prototype._addTxsScript = function(txs, hash, height) { - var dbScript =[]; + var dbScript = []; - for(var ii in txs){ + for (var ii in txs) { dbScript.push({ type: 'put', key: IN_BLK_PREFIX + txs[ii], - value: hash+':'+height, + value: hash + ':' + height, }); } return dbScript; @@ -99,51 +99,53 @@ BlockDb.prototype._addTxsScript = function(txs, hash, height) { // Returns blockHash and height for a given txId (If the tx is on the MAIN chain). BlockDb.prototype.getBlockForTx = function(txId, cb) { - db.get(IN_BLK_PREFIX + txId,function (err, val) { + db.get(IN_BLK_PREFIX + txId, function(err, val) { if (err && err.notFound) return cb(); if (err) return cb(err); var v = val.split(':'); - return cb(err,v[0],parseInt(v[1])); + return cb(err, v[0], parseInt(v[1])); }); }; BlockDb.prototype._changeBlockHeight = function(hash, height, cb) { var self = this; - var dbScript1 = this._setHeightScript(hash,height); + var dbScript1 = this._setHeightScript(hash, height); logger.log('Getting TXS FROM %s to set it Main', hash); this.fromHashWithInfo(hash, function(err, bi) { if (!bi || !bi.info || !bi.info.tx) - throw new Error('unable to get info for block:'+ hash); + throw new Error('unable to get info for block:' + hash); var dbScript2; - if (height>=0) { + if (height >= 0) { dbScript2 = self._addTxsScript(bi.info.tx, hash, height); logger.info('\t%s %d Txs', 'Confirming', bi.info.tx.length); } else { dbScript2 = self._delTxsScript(bi.info.tx); logger.info('\t%s %d Txs', 'Unconfirming', bi.info.tx.length); } - db.batch(dbScript2.concat(dbScript1),cb); + db.batch(dbScript2.concat(dbScript1), cb); }); }; BlockDb.prototype.setBlockMain = function(hash, height, cb) { - this._changeBlockHeight(hash,height,cb); + this._changeBlockHeight(hash, height, cb); }; BlockDb.prototype.setBlockNotMain = function(hash, cb) { - this._changeBlockHeight(hash,-1,cb); + this._changeBlockHeight(hash, -1, cb); }; // adds a block (and its txs). Does not update Next pointer in // the block prev to the new block, nor TIP pointer // BlockDb.prototype.add = function(b, height, cb) { - var txs = typeof b.tx[0] === 'string' ? b.tx : b.tx.map( function(o){ return o.txid; }); + var txs = typeof b.tx[0] === 'string' ? b.tx : b.tx.map(function(o) { + return o.txid; + }); - var dbScript = this._addBlockScript(b,height); + var dbScript = this._addBlockScript(b, height); dbScript = dbScript.concat(this._addTxsScript(txs, b.hash, height)); this.txDb.addMany(b.tx, function(err) { if (err) return cb(err); @@ -153,9 +155,9 @@ BlockDb.prototype.add = function(b, height, cb) { BlockDb.prototype.getTip = function(cb) { - if (this.cachedTip){ + if (this.cachedTip) { var v = this.cachedTip.split(':'); - return cb(null,v[0], parseInt(v[1])); + return cb(null, v[0], parseInt(v[1])); } var self = this; @@ -163,7 +165,7 @@ BlockDb.prototype.getTip = function(cb) { if (!val) return cb(); self.cachedTip = val; var v = val.split(':'); - return cb(err,v[0], parseInt(v[1])); + return cb(err, v[0], parseInt(v[1])); }); }; @@ -177,8 +179,8 @@ BlockDb.prototype.setTip = function(hash, height, cb) { BlockDb.prototype.getDepth = function(hash, cb) { var v = this.cachedTip.split(':'); if (!v) throw new Error('getDepth called with not cachedTip'); - this.getHeight(hash, function(err,h){ - return cb(err,parseInt(v[1]) - h); + this.getHeight(hash, function(err, h) { + return cb(err, parseInt(v[1]) - h); }); }; @@ -190,9 +192,12 @@ BlockDb.prototype.setPrev = function(hash, prevHash, cb) { }; BlockDb.prototype.getPrev = function(hash, cb) { - db.get(PREV_PREFIX + hash, function(err,val) { - if (err && err.notFound) { err = null; val = null;} - return cb(err,val); + db.get(PREV_PREFIX + hash, function(err, val) { + if (err && err.notFound) { + err = null; + val = null; + } + return cb(err, val); }); }; @@ -208,28 +213,37 @@ BlockDb.prototype.setLastFileIndex = function(idx, cb) { }; BlockDb.prototype.getLastFileIndex = function(cb) { - db.get(LAST_FILE_INDEX, function(err,val) { - if (err && err.notFound) { err = null; val = null;} - return cb(err,val); + db.get(LAST_FILE_INDEX, function(err, val) { + if (err && err.notFound) { + err = null; + val = null; + } + return cb(err, val); }); }; BlockDb.prototype.getNext = function(hash, cb) { - db.get(NEXT_PREFIX + hash, function(err,val) { - if (err && err.notFound) { err = null; val = null;} - return cb(err,val); + db.get(NEXT_PREFIX + hash, function(err, val) { + if (err && err.notFound) { + err = null; + val = null; + } + return cb(err, val); }); }; BlockDb.prototype.getHeight = function(hash, cb) { db.get(MAIN_PREFIX + hash, function(err, val) { - if (err && err.notFound) { err = null; val = 0;} - return cb(err,parseInt(val)); + if (err && err.notFound) { + err = null; + val = 0; + } + return cb(err, parseInt(val)); }); }; BlockDb.prototype._setHeightScript = function(hash, height) { - logger.log('setHeight: %s #%d', hash,height); + logger.log('setHeight: %s #%d', hash, height); return ([{ type: 'put', key: MAIN_PREFIX + hash, @@ -247,14 +261,17 @@ BlockDb.prototype.setNext = function(hash, nextHash, cb) { BlockDb.prototype.countConnected = function(cb) { var c = 0; console.log('Counting connected blocks. This could take some minutes'); - db.createReadStream({start: MAIN_PREFIX, end: MAIN_PREFIX + '~' }) - .on('data', function (data) { + db.createReadStream({ + start: MAIN_PREFIX, + end: MAIN_PREFIX + '~' + }) + .on('data', function(data) { if (data.value !== 0) c++; }) - .on('error', function (err) { + .on('error', function(err) { return cb(err); }) - .on('end', function () { + .on('end', function() { return cb(null, c); }); }; @@ -262,7 +279,7 @@ BlockDb.prototype.countConnected = function(cb) { // .has() return true orphans also BlockDb.prototype.has = function(hash, cb) { var k = PREV_PREFIX + hash; - db.get(k, function (err) { + db.get(k, function(err) { var ret = true; if (err && err.notFound) { err = null; @@ -282,7 +299,7 @@ BlockDb.prototype.fromHashWithInfo = function(hash, cb) { self.getHeight(hash, function(err, height) { if (err) return cb(err); - info.isMainChain = height>=0 ? true : false; + info.isMainChain = height >= 0 ? true : false; return cb(null, { hash: hash, @@ -295,30 +312,30 @@ BlockDb.prototype.fromHashWithInfo = function(hash, cb) { BlockDb.prototype.getBlocksByDate = function(start_ts, end_ts, limit, cb) { var list = []; var opts = { - start: TIMESTAMP_PREFIX + end_ts, //Inverted since list is reversed + start: TIMESTAMP_PREFIX + end_ts, //Inverted since list is reversed end: TIMESTAMP_PREFIX + start_ts, limit: limit, reverse: 1, }; db.createReadStream(opts) - .on('data', function (data) { + .on('data', function(data) { var k = data.key.split('-'); list.push({ ts: k[1], hash: data.value, }); }) - .on('error', function (err) { + .on('error', function(err) { return cb(err); }) - .on('end', function () { + .on('end', function() { return cb(null, list.reverse()); }); }; BlockDb.prototype.blockIndex = function(height, cb) { - return Rpc.blockIndex(height,cb); + return Rpc.blockIndex(height, cb); }; BlockDb.prototype._fillConfirmationsOneSpent = function(o, chainHeight, cb) { @@ -331,11 +348,11 @@ BlockDb.prototype._fillConfirmationsOneSpent = function(o, chainHeight, cb) { // Only one will be confirmed self.getBlockForTx(oi.txid, function(err, hash, height) { if (err) return; - if (height>=0) { + if (height >= 0) { o.spentTxId = oi.txid; o.index = oi.index; o.spentIsConfirmed = chainHeight >= height; - o.spentConfirmations = chainHeight - height +1; + o.spentConfirmations = chainHeight - height + 1; } return e_c(); }); @@ -343,63 +360,93 @@ BlockDb.prototype._fillConfirmationsOneSpent = function(o, chainHeight, cb) { } else { self.getBlockForTx(o.spentTxId, function(err, hash, height) { if (err) return cb(err); - if (height >=0 ) { + if (height >= 0) { o.spentIsConfirmed = chainHeight >= height; - o.spentConfirmations = chainHeight - height +1; + o.spentConfirmations = chainHeight - height + 1; } return cb(); }); } }; + +BlockDb.prototype._fillConfirmationsOneVin = function(o, chainHeight, cb) { + var self = this; + + self.getBlockForTx(o.txid, function(err, hash, height) { + if (err) return cb(err); + o.isConfirmed = false; + o.confirmations = 0; + if (height >= 0) { + o.isConfirmed = chainHeight >= height; + o.confirmations = chainHeight - height + 1; + } + o.unconfirmedInput = ! o.isConfirmed; + return cb(); + }); +}; + + BlockDb.prototype._fillConfirmationsOne = function(o, chainHeight, cb) { var self = this; self.getBlockForTx(o.txid, function(err, hash, height) { if (err) return cb(err); - if (height>=0) { + if (height >= 0) { o.isConfirmed = chainHeight >= height; - o.confirmations = chainHeight - height +1; - return self._fillConfirmationsOneSpent(o,chainHeight,cb); - } - else return cb(); + o.confirmations = chainHeight - height + 1; + return self._fillConfirmationsOneSpent(o, chainHeight, cb); + } else return cb(); }); }; BlockDb.prototype.fillConfirmations = function(txouts, cb) { var self = this; - this.getTip(function(err, hash, height){ - var txs = txouts.filter(function(x){ - return !x.spentIsConfirmedCached // not 100%cached - && !(x.isConfirmedCached && !x.spentTxId); // and not partial cached but not spent + this.getTip(function(err, hash, height) { + var txs = txouts.filter(function(x) { + return !x.spentIsConfirmedCached // not 100%cached + && !(x.isConfirmedCached && !x.spentTxId); // and not partial cached but not spent }); //console.log('[BlockDb.js.373:txs:]',txs.length, txs.slice(0,5)); //TODO async.eachLimit(txs, CONCURRENCY, function(txout, e_c) { - if(txout.isConfirmedCached) { - self._fillConfirmationsOneSpent(txout,height, e_c); + if (txout.isConfirmedCached) { + self._fillConfirmationsOneSpent(txout, height, e_c); } else { - self._fillConfirmationsOne(txout,height, e_c); + self._fillConfirmationsOne(txout, height, e_c); } }, cb); }); }; +BlockDb.prototype.fillVinConfirmations = function(tx, cb) { + var self = this; + this.getTip(function(err, hash, height) { + var vin = tx.vin; + if (!vin) return cb(); + + async.eachLimit(vin, CONCURRENCY, function(v, e_c) { + self._fillConfirmationsOneVin(v, height, e_c); + }, cb); + }); +}; + + /* this is only for migration scripts */ BlockDb.prototype._runScript = function(script, cb) { - db.batch(script,cb); + db.batch(script, cb); }; BlockDb.prototype.migrateV02 = function(cb) { var k = 'txb-'; var dbScript = []; - var c=0; - var c2=0; - var N=50000; + var c = 0; + var c2 = 0; + var N = 50000; this.txDb._db.createReadStream({ - start: k, - end: k + '~' - }) + start: k, + end: k + '~' + }) .on('data', function(data) { var k = data.key.split('-'); var v = data.value.split(':'); @@ -408,18 +455,18 @@ BlockDb.prototype.migrateV02 = function(cb) { key: IN_BLK_PREFIX + k[1], value: data.value, }); - if (c++>N) { - console.log('\t%dM txs processed', ((c2+=N)/1e6).toFixed(3)); - db.batch(dbScript,function () { - c=0; - dbScript=[]; + if (c++ > N) { + console.log('\t%dM txs processed', ((c2 += N) / 1e6).toFixed(3)); + db.batch(dbScript, function() { + c = 0; + dbScript = []; }); } }) .on('error', function(err) { return cb(err); }) - .on('end', function (){ + .on('end', function() { return cb(); }); @@ -433,32 +480,38 @@ BlockDb.prototype.migrateV02cleanup = function(cb) { var k = 'txb-'; var d = this.txDb._db; d.createReadStream({ - start: k, - end: k + '~' - }) - .pipe(d.createWriteStream({type:'del'})) - .on('close', function(err){ + start: k, + end: k + '~' + }) + .pipe(d.createWriteStream({ + type: 'del' + })) + .on('close', function(err) { if (err) return cb(err); console.log('## deleting tx- from txs db'); //todo var k = 'tx-'; var d = self.txDb._db; d.createReadStream({ - start: k, - end: k + '~' - }) - .pipe(d.createWriteStream({type:'del'})) - .on('close', function(err){ - if (err) return cb(err); - var k = 'txa-'; - var d = self.txDb._db; - d.createReadStream({ start: k, end: k + '~' }) - .pipe(d.createWriteStream({type:'del'})) - .on('close', cb); - }); + .pipe(d.createWriteStream({ + type: 'del' + })) + .on('close', function(err) { + if (err) return cb(err); + var k = 'txa-'; + var d = self.txDb._db; + d.createReadStream({ + start: k, + end: k + '~' + }) + .pipe(d.createWriteStream({ + type: 'del' + })) + .on('close', cb); + }); }); }; diff --git a/lib/TransactionDb.js b/lib/TransactionDb.js index 37e5073..35bada6 100644 --- a/lib/TransactionDb.js +++ b/lib/TransactionDb.js @@ -198,7 +198,6 @@ TransactionDb.prototype._fillOutpoints = function(txInfo, cb) { } txInfo.firstSeenTs = ret.ts; - i.unconfirmedInput = i.unconfirmedInput; i.addr = ret.addr; i.valueSat = ret.valueSat; i.value = ret.valueSat / util.COIN; diff --git a/package.json b/package.json index c372adb..aa8407e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "insight-bitcore-api", "description": "An open-source bitcoin blockchain API. The Insight API provides you with a convenient, powerful and simple way to query and broadcast data on the bitcoin network and build your own services with it.", - "version": "0.2.13", + "version": "0.2.14", "author": { "name": "Ryan X Charles", "email": "ryan@bitpay.com"