fix conflicts. update readme.md. update block page. get data for address page from API
This commit is contained in:
commit
184fa19321
11
.ctags
Normal file
11
.ctags
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
--extra=+f
|
||||||
|
--exclude=*jquery*
|
||||||
|
--exclude=node_modules/a*
|
||||||
|
--exclude=node_modules/[c-z]*
|
||||||
|
--exclude=*grunt*
|
||||||
|
--exclude=*bower*
|
||||||
|
--exclude=.swp
|
||||||
|
--exclude=public
|
||||||
|
--links=yes
|
||||||
|
--totals=yes
|
||||||
|
|
||||||
@ -32,7 +32,9 @@
|
|||||||
"afterEach",
|
"afterEach",
|
||||||
"it",
|
"it",
|
||||||
"inject",
|
"inject",
|
||||||
"expect"
|
"expect",
|
||||||
|
"$"
|
||||||
|
|
||||||
],
|
],
|
||||||
"indent": false, // Specify indentation spacing
|
"indent": false, // Specify indentation spacing
|
||||||
"devel": true, // Allow development statements e.g. `console.log();`.
|
"devel": true, // Allow development statements e.g. `console.log();`.
|
||||||
|
|||||||
10
Gruntfile.js
10
Gruntfile.js
@ -40,11 +40,11 @@ module.exports = function(grunt) {
|
|||||||
livereload: true
|
livereload: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
test: {
|
|
||||||
// we monitor only app/models/* because we have test for models only now
|
// we monitor only app/models/* because we have test for models only now
|
||||||
files: ['test/**/*.js', 'test/*.js','app/models/*.js'],
|
// test: {
|
||||||
tasks: ['test'],
|
// files: ['test/**/*.js', 'test/*.js','app/models/*.js'],
|
||||||
}
|
// tasks: ['test'],
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
jshint: {
|
jshint: {
|
||||||
all: {
|
all: {
|
||||||
@ -66,7 +66,7 @@ module.exports = function(grunt) {
|
|||||||
options: {
|
options: {
|
||||||
file: 'server.js',
|
file: 'server.js',
|
||||||
args: [],
|
args: [],
|
||||||
ignoredFiles: ['public/**', 'test/**','util/**'],
|
ignoredFiles: ['public/**', 'test/**','util/**','lib/**'],
|
||||||
watchedExtensions: ['js'],
|
watchedExtensions: ['js'],
|
||||||
// nodeArgs: ['--debug'],
|
// nodeArgs: ['--debug'],
|
||||||
delayTime: 1,
|
delayTime: 1,
|
||||||
|
|||||||
213
HeaderDB.js
213
HeaderDB.js
@ -1,213 +0,0 @@
|
|||||||
require('classtool');
|
|
||||||
|
|
||||||
function ClassSpec(b) {
|
|
||||||
var assert = require('assert');
|
|
||||||
var fs = require('fs');
|
|
||||||
var Block = require('bitcore/Block').class();
|
|
||||||
var Deserialize = require('bitcore/Deserialize');
|
|
||||||
var Parser = require('bitcore/util/BinaryParser').class();
|
|
||||||
var coinUtil = require('bitcore/util/util');
|
|
||||||
|
|
||||||
function HeaderDB(b) {
|
|
||||||
this.network = b.network;
|
|
||||||
this.fd = null;
|
|
||||||
this.blocks = {};
|
|
||||||
this.byHeight = [];
|
|
||||||
this.bestBlock = null;
|
|
||||||
this.cached_size = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
HeaderDB.prototype.size = function() {
|
|
||||||
this.cached_size = Object.keys(this.blocks).length;
|
|
||||||
return this.cached_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
HeaderDB.prototype.locator = function(block) {
|
|
||||||
if (!block)
|
|
||||||
block = this.bestBlock;
|
|
||||||
|
|
||||||
var step = 1;
|
|
||||||
var start = 0;
|
|
||||||
var loc = [];
|
|
||||||
// see https://en.bitcoin.it/wiki/Protocol_specification#getblocks
|
|
||||||
for (var i = block.height; i > 0; i -= step, ++start) {
|
|
||||||
if (start >= 10)
|
|
||||||
step *= 2;
|
|
||||||
loc.push(this.byHeight[i]);
|
|
||||||
}
|
|
||||||
assert.equal(this.byHeight[0].toString(),
|
|
||||||
this.network.genesisBlock.hash.toString());
|
|
||||||
loc.push(this.byHeight[0]);
|
|
||||||
//console.log('Requesting more headers. Current height: ' + block.height );
|
|
||||||
return loc;
|
|
||||||
};
|
|
||||||
|
|
||||||
HeaderDB.prototype.add = function(block) {
|
|
||||||
var hash = block.calcHash();
|
|
||||||
block.hash = hash;
|
|
||||||
var curWork = Deserialize.intFromCompact(block.bits);
|
|
||||||
|
|
||||||
if (hash in this.blocks) {
|
|
||||||
var old = this.blocks[hash];
|
|
||||||
throw new Error('duplicate block (was at height ' + old.height + ')');
|
|
||||||
}
|
|
||||||
|
|
||||||
var bestChain = false;
|
|
||||||
|
|
||||||
var reorg = {
|
|
||||||
oldBest: null,
|
|
||||||
conn: 0,
|
|
||||||
disconn: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.cached_size == 0) {
|
|
||||||
if (this.network.genesisBlock.hash.toString() !=
|
|
||||||
hash.toString())
|
|
||||||
throw new Error('Invalid genesis block');
|
|
||||||
|
|
||||||
block.height = 0;
|
|
||||||
block.work = curWork;
|
|
||||||
bestChain = true;
|
|
||||||
this.cached_size++;
|
|
||||||
} else {
|
|
||||||
var prevBlock = this.blocks[block.prev_hash];
|
|
||||||
if (!prevBlock)
|
|
||||||
throw new Error('orphan block; prev not found');
|
|
||||||
|
|
||||||
block.height = prevBlock.height + 1;
|
|
||||||
block.work = prevBlock.work + curWork;
|
|
||||||
this.cached_size++;
|
|
||||||
|
|
||||||
if (block.work > this.bestBlock.work)
|
|
||||||
bestChain = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// add to by-hash index
|
|
||||||
this.blocks[hash] = block;
|
|
||||||
|
|
||||||
if (bestChain) {
|
|
||||||
var oldBest = this.bestBlock;
|
|
||||||
var newBest = block;
|
|
||||||
|
|
||||||
reorg.oldBest = oldBest;
|
|
||||||
|
|
||||||
// likely case: new best chain has greater height
|
|
||||||
if (!oldBest) {
|
|
||||||
while (newBest) {
|
|
||||||
newBest = this.blocks[newBest.prev_hash];
|
|
||||||
reorg.conn++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
while (newBest &&
|
|
||||||
(newBest.height > oldBest.height)) {
|
|
||||||
newBest = this.blocks[newBest.prev_hash];
|
|
||||||
reorg.conn++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unlikely: old best chain has greater height
|
|
||||||
while (oldBest && newBest &&
|
|
||||||
(oldBest.height > newBest.height)) {
|
|
||||||
oldBest = this.blocks[oldBest.prev_hash];
|
|
||||||
reorg.disconn++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// height matches, but still walking parallel
|
|
||||||
while (oldBest && newBest && (oldBest != newBest)) {
|
|
||||||
newBest = this.blocks[newBest.prev_hash];
|
|
||||||
reorg.conn++;
|
|
||||||
|
|
||||||
oldBest = this.blocks[oldBest.prev_hash];
|
|
||||||
reorg.disconn++;
|
|
||||||
}
|
|
||||||
|
|
||||||
var shuf = (reorg.conn > reorg.disconn) ?
|
|
||||||
reorg.conn : reorg.disconn;
|
|
||||||
|
|
||||||
// reorg analyzed, updated best-chain pointer
|
|
||||||
this.bestBlock = block;
|
|
||||||
|
|
||||||
// update by-height index
|
|
||||||
var ptr = block;
|
|
||||||
var updated = [];
|
|
||||||
for (var idx = block.height;
|
|
||||||
idx > (block.height - shuf); idx--) {
|
|
||||||
if (idx < 0)
|
|
||||||
break;
|
|
||||||
var update = [ idx, ptr ];
|
|
||||||
updated.push(update);
|
|
||||||
ptr = this.blocks[ptr.prev_hash];
|
|
||||||
}
|
|
||||||
|
|
||||||
updated.reverse();
|
|
||||||
|
|
||||||
for (var i = 0; i < updated.length; i++) {
|
|
||||||
var update = updated[i];
|
|
||||||
var idx = update[0];
|
|
||||||
var ptr = update[1];
|
|
||||||
|
|
||||||
if (idx < this.byHeight.length)
|
|
||||||
this.byHeight[idx] = ptr.hash;
|
|
||||||
else
|
|
||||||
this.byHeight.push(ptr.hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reorg;
|
|
||||||
};
|
|
||||||
|
|
||||||
HeaderDB.prototype.addBuf = function(buf) {
|
|
||||||
var block = new Block();
|
|
||||||
var parser = new Parser(buf);
|
|
||||||
block.parse(parser, true);
|
|
||||||
this.add(block);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
HeaderDB.prototype.readFile = function(filename) {
|
|
||||||
var fd = fs.openSync(filename, 'r');
|
|
||||||
var stats = fs.fstatSync(fd);
|
|
||||||
if (stats.size % 80 != 0)
|
|
||||||
throw new Error('Corrupted header db');
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
var buf = new Buffer(80);
|
|
||||||
var bread = fs.readSync(fd, buf, 0, 80, null);
|
|
||||||
if (bread < 80)
|
|
||||||
break;
|
|
||||||
|
|
||||||
this.addBuf(buf);
|
|
||||||
|
|
||||||
if ( ! ( this.cached_size % 1000 )) {
|
|
||||||
console.log('\tblock...' + this.cached_size ) ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.closeSync(fd);
|
|
||||||
};
|
|
||||||
|
|
||||||
HeaderDB.prototype.writeFile = function(filename) {
|
|
||||||
var block = this.bestBlock;
|
|
||||||
var data = [];
|
|
||||||
while (block) {
|
|
||||||
var s = block.getHeader();
|
|
||||||
data.push(s);
|
|
||||||
block = this.blocks[block.prev_hash];
|
|
||||||
}
|
|
||||||
|
|
||||||
data.reverse();
|
|
||||||
|
|
||||||
var fd = fs.openSync(filename, 'w');
|
|
||||||
|
|
||||||
data.forEach(function(datum) {
|
|
||||||
fs.writeSync(fd, datum, 0, 80, null);
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.closeSync(fd);
|
|
||||||
};
|
|
||||||
|
|
||||||
return HeaderDB;
|
|
||||||
};
|
|
||||||
module.defineClass(ClassSpec);
|
|
||||||
|
|
||||||
19
README.md
19
README.md
@ -69,6 +69,8 @@ $ npm install -g bower
|
|||||||
$ ln -s <path_to>/node-bufferput bufferput
|
$ ln -s <path_to>/node-bufferput bufferput
|
||||||
|
|
||||||
Get bitcore from github repository:
|
Get bitcore from github repository:
|
||||||
|
|
||||||
|
Get bitcore from github repository:
|
||||||
|
|
||||||
$ git clone https://github.com/bitpay/bitcore.git
|
$ git clone https://github.com/bitpay/bitcore.git
|
||||||
|
|
||||||
@ -95,6 +97,12 @@ $ npm install -g bower
|
|||||||
|
|
||||||
Check utils/sync.js --help for options.
|
Check utils/sync.js --help for options.
|
||||||
|
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
A REST API is provided at /api. The entry points are:
|
||||||
|
|
||||||
|
|
||||||
### Blocks
|
### Blocks
|
||||||
```
|
```
|
||||||
/api/block/[:hash]
|
/api/block/[:hash]
|
||||||
@ -120,13 +128,13 @@ All configuration is specified in the [config](config/) folder, particularly the
|
|||||||
|
|
||||||
There is a bitcoind configuration sample at:
|
There is a bitcoind configuration sample at:
|
||||||
```
|
```
|
||||||
etc/mystery/bitcoin.conf
|
etc/bitcoind/bitcoin.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to use a external bitcoind server set BITCOIND_HOST / BITCOIND_PORT enviroment variables. Make sure that bitcoind is configured to accept incomming connections using 'rpcallowip' decribed in https://en.bitcoin.it/wiki/Running_Bitcoin.
|
If you want to use a external bitcoind server set BITCOIND_HOST / BITCOIND_PORT enviroment variables. Make sure that bitcoind is configured to accept incomming connections using 'rpcallowip' decribed in https://en.bitcoin.it/wiki/Running_Bitcoin.
|
||||||
|
|
||||||
|
|
||||||
### Environmental Settings
|
### Environment Variables Settings
|
||||||
|
|
||||||
There are three environments provided by default, __development__, __test__, and __production__. Each of these environments has the following configuration options:
|
There are three environments provided by default, __development__, __test__, and __production__. Each of these environments has the following configuration options:
|
||||||
* __db__ - This is the name of the MongoDB database to use, and is set by default to __mystery-dev__ for the development environment.
|
* __db__ - This is the name of the MongoDB database to use, and is set by default to __mystery-dev__ for the development environment.
|
||||||
@ -140,6 +148,13 @@ If you are using node instead of grunt, it is very similar:
|
|||||||
|
|
||||||
$ NODE_ENV=test node server
|
$ NODE_ENV=test node server
|
||||||
|
|
||||||
|
|
||||||
|
### Development enviroment
|
||||||
|
To run mystery locally for development:
|
||||||
|
|
||||||
|
$ NODE_ENV=development grunt
|
||||||
|
|
||||||
|
|
||||||
## Github
|
## Github
|
||||||
[Mystery](https://github.com/bitpay/mystery)
|
[Mystery](https://github.com/bitpay/mystery)
|
||||||
|
|
||||||
|
|||||||
37
app/controllers/addresses.js
Normal file
37
app/controllers/addresses.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Address = require('../models/Address');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find block by hash ...
|
||||||
|
*/
|
||||||
|
exports.address = function(req, res, next, addr) {
|
||||||
|
var a = Address.new(addr);
|
||||||
|
|
||||||
|
a.update(function(err) {
|
||||||
|
if (err && !a.totalReceivedSat) {
|
||||||
|
console.log(err);
|
||||||
|
res.status(404).send('Invalid address');
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
req.address = a;
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show block
|
||||||
|
*/
|
||||||
|
exports.show = function(req, res) {
|
||||||
|
if (req.address) {
|
||||||
|
res.jsonp(req.address);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@ -14,10 +14,14 @@ var mongoose = require('mongoose'),
|
|||||||
*/
|
*/
|
||||||
exports.block = function(req, res, next, hash) {
|
exports.block = function(req, res, next, hash) {
|
||||||
Block.fromHashWithInfo(hash, function(err, block) {
|
Block.fromHashWithInfo(hash, function(err, block) {
|
||||||
if (err) return next(err);
|
if (err && !block) {
|
||||||
if (!block) return next(new Error('Failed to load block ' + hash));
|
console.log(err);
|
||||||
|
res.status(404).send('Not found');
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
req.block = block.info;
|
req.block = block.info;
|
||||||
next();
|
return next();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -26,7 +30,9 @@ exports.block = function(req, res, next, hash) {
|
|||||||
* Show block
|
* Show block
|
||||||
*/
|
*/
|
||||||
exports.show = function(req, res) {
|
exports.show = function(req, res) {
|
||||||
res.jsonp(req.block);
|
if (req.block) {
|
||||||
|
res.jsonp(req.block);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
127
app/models/Address.js
Normal file
127
app/models/Address.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
require('classtool');
|
||||||
|
|
||||||
|
|
||||||
|
function spec() {
|
||||||
|
var async = require('async');
|
||||||
|
var TransactionItem = require('./TransactionItem');
|
||||||
|
var BitcoreAddress = require('bitcore/Address').class();
|
||||||
|
var BitcoreUtil = require('bitcore/util/util');
|
||||||
|
|
||||||
|
function Address(addrStr) {
|
||||||
|
this.balanceSat = 0;
|
||||||
|
this.totalReceivedSat = 0;
|
||||||
|
this.totalSentSat = 0;
|
||||||
|
this.txApperances = 0;
|
||||||
|
|
||||||
|
// TODO store only txids? +index? +all?
|
||||||
|
this.transactions = [];
|
||||||
|
|
||||||
|
var a = new BitcoreAddress(addrStr);
|
||||||
|
try {
|
||||||
|
a.validate();
|
||||||
|
this.addrStr = addrStr;
|
||||||
|
} catch(e){
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Address.prototype.__defineGetter__('balance', function(){
|
||||||
|
|
||||||
|
console.log('#################### '+this.balanceSat);
|
||||||
|
|
||||||
|
|
||||||
|
return this.balanceSat / BitcoreUtil.COIN;
|
||||||
|
});
|
||||||
|
|
||||||
|
Address.prototype.update = function(next) {
|
||||||
|
|
||||||
|
if (! this.addrStr) {
|
||||||
|
return next(new Error('Invalid or undefined address string'));
|
||||||
|
}
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
async.series([
|
||||||
|
// TODO TXout!
|
||||||
|
//T
|
||||||
|
function (cb) {
|
||||||
|
TransactionItem.find({addr:that.addrStr}, function(err,txItems){
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
txItems.forEach(function(txItem){
|
||||||
|
|
||||||
|
// console.log(txItem.txid + ' : ' + txItem.value_sat);
|
||||||
|
that.txApperances +=1;
|
||||||
|
that.balanceSat += txItem.value_sat;
|
||||||
|
|
||||||
|
that.transactions.push(txItem.txid);
|
||||||
|
|
||||||
|
if (txItem.value_sat > 0)
|
||||||
|
that.totalSentSat += txItem.value_sat;
|
||||||
|
else
|
||||||
|
that.totalReceivedSat += Math.abs(txItem.value_sat);
|
||||||
|
});
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
], function (err) {
|
||||||
|
return next(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return Address;
|
||||||
|
}
|
||||||
|
module.defineClass(spec);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addr Schema Idea for moogose. Not used now.
|
||||||
|
*
|
||||||
|
var AddressSchema = new Schema({
|
||||||
|
|
||||||
|
// For now we keep this as short as possible
|
||||||
|
// More fields will be propably added as we move
|
||||||
|
// forward with the UX
|
||||||
|
addr: {
|
||||||
|
type: String,
|
||||||
|
index: true,
|
||||||
|
unique: true,
|
||||||
|
},
|
||||||
|
inputs: [{
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: 'TransactionItem' //Edit: I'd put the schema. Silly me.
|
||||||
|
}],
|
||||||
|
output: [{
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: 'TransactionItem' //Edit: I'd put the schema. Silly me.
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
AddressSchema.statics.load = function(id, cb) {
|
||||||
|
this.findOne({
|
||||||
|
_id: id
|
||||||
|
}).exec(cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AddressSchema.statics.fromAddr = function(hash, cb) {
|
||||||
|
this.findOne({
|
||||||
|
hash: hash,
|
||||||
|
}).exec(cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
AddressSchema.statics.fromAddrWithInfo = function(hash, cb) {
|
||||||
|
this.fromHash(hash, function(err, addr) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
if (!addr) { return cb(new Error('Addr not found')); }
|
||||||
|
// TODO
|
||||||
|
// addr.getInfo(function(err) { return cb(err,addr); } );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = mongoose.model('Address', AddressSchema);
|
||||||
|
*/
|
||||||
|
|
||||||
@ -13,7 +13,8 @@ var mongoose = require('mongoose'),
|
|||||||
networks = require('bitcore/networks'),
|
networks = require('bitcore/networks'),
|
||||||
util = require('bitcore/util/util'),
|
util = require('bitcore/util/util'),
|
||||||
bignum = require('bignum'),
|
bignum = require('bignum'),
|
||||||
config = require('../../config/config');
|
config = require('../../config/config'),
|
||||||
|
TransactionItem = require('./TransactionItem');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,6 +28,15 @@ var TransactionSchema = new Schema({
|
|||||||
index: true,
|
index: true,
|
||||||
unique: true,
|
unique: true,
|
||||||
},
|
},
|
||||||
|
processed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
orphaned: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,19 +56,37 @@ TransactionSchema.statics.fromId = function(txid, cb) {
|
|||||||
}).exec(cb);
|
}).exec(cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
TransactionSchema.statics.fromIdWithInfo = function(txid, cb) {
|
|
||||||
|
|
||||||
// TODO Should we go to mongoDB first? Now, no extra information is stored at mongo.
|
TransactionSchema.statics.fromIdWithInfo = function(txid, cb) {
|
||||||
|
var That = this;
|
||||||
|
|
||||||
this.fromId(txid, function(err, tx) {
|
this.fromId(txid, function(err, tx) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
if (!tx) { return cb(new Error('TX not found')); }
|
if (!tx) {
|
||||||
|
// No in mongo...but maybe in bitcoind... lets query it
|
||||||
|
tx = new That();
|
||||||
|
|
||||||
tx.queryInfo(function(err) { return cb(err,tx); } );
|
tx.txid = txid;
|
||||||
|
tx.queryInfo(function(err, txInfo) {
|
||||||
|
|
||||||
|
if (!txInfo)
|
||||||
|
return cb(new Error('TX not found1'));
|
||||||
|
|
||||||
|
tx.save(function(err) {
|
||||||
|
return cb(err,tx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tx.queryInfo(function(err) {
|
||||||
|
return cb(err,tx);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
TransactionSchema.statics.createFromArray = function(txs, next) {
|
TransactionSchema.statics.createFromArray = function(txs, next) {
|
||||||
var that = this;
|
var that = this;
|
||||||
if (!txs) return next();
|
if (!txs) return next();
|
||||||
@ -79,11 +107,72 @@ TransactionSchema.statics.createFromArray = function(txs, next) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TransactionSchema.statics.explodeTransactionItems = function(txid, cb) {
|
||||||
|
|
||||||
|
this.fromIdWithInfo(txid, function(err, t) {
|
||||||
|
if (err || !t) return cb(err);
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
t.info.vin.forEach( function(i){
|
||||||
|
i.n = index++;
|
||||||
|
});
|
||||||
|
|
||||||
|
async.each(t.info.vin, function(i, next_in) {
|
||||||
|
if (i.addr && i.value) {
|
||||||
|
|
||||||
|
//console.log("Creating IN %s %d", i.addr, i.valueSat);
|
||||||
|
TransactionItem.create({
|
||||||
|
txid : t.txid,
|
||||||
|
value_sat : -1 * i.valueSat,
|
||||||
|
addr : i.addr,
|
||||||
|
index : i.n,
|
||||||
|
ts : t.info.time,
|
||||||
|
}, next_in);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( !i.coinbase ) {
|
||||||
|
console.log ('TX: %s,%d could not parse INPUT', t.txid, i.n);
|
||||||
|
}
|
||||||
|
return next_in();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
if (err) console.log (err);
|
||||||
|
async.each(t.info.vout, function(o, next_out) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO Support multisigs
|
||||||
|
*/
|
||||||
|
if (o.value && o.scriptPubKey && o.scriptPubKey.addresses && o.scriptPubKey.addresses[0]) {
|
||||||
|
//console.log("Creating OUT %s %d", o.scriptPubKey.addresses[0], o.valueSat);
|
||||||
|
TransactionItem.create({
|
||||||
|
txid : t.txid,
|
||||||
|
value_sat : o.valueSat,
|
||||||
|
addr : o.scriptPubKey.addresses[0],
|
||||||
|
index : o.n,
|
||||||
|
ts : t.info.time,
|
||||||
|
}, next_out);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log ('TX: %s,%d could not parse OUTPUT', t.txid, o.n);
|
||||||
|
return next_out();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
return cb(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TransactionSchema.methods.fillInputValues = function (tx, next) {
|
TransactionSchema.methods.fillInputValues = function (tx, next) {
|
||||||
|
|
||||||
if (tx.isCoinBase()) return next();
|
if (tx.isCoinBase()) return next();
|
||||||
|
|
||||||
if (! this.rpc) this.rpc = new RpcClient(config.bitcoind);
|
if (! this.rpc) this.rpc = new RpcClient(config.bitcoind);
|
||||||
|
var network = ( config.network === 'testnet') ? networks.testnet : networks.livenet ;
|
||||||
|
|
||||||
var that = this;
|
var that = this;
|
||||||
async.each(tx.ins, function(i, cb) {
|
async.each(tx.ins, function(i, cb) {
|
||||||
@ -95,21 +184,31 @@ TransactionSchema.methods.fillInputValues = function (tx, next) {
|
|||||||
var c=0;
|
var c=0;
|
||||||
that.rpc.getRawTransaction(outHashBase64, function(err, txdata) {
|
that.rpc.getRawTransaction(outHashBase64, function(err, txdata) {
|
||||||
var txin = new Transaction();
|
var txin = new Transaction();
|
||||||
|
|
||||||
if (err || ! txdata.result) return cb( new Error('Input TX '+outHashBase64+' not found'));
|
if (err || ! txdata.result) return cb( new Error('Input TX '+outHashBase64+' not found'));
|
||||||
|
|
||||||
var b = new Buffer(txdata.result,'hex');
|
var b = new Buffer(txdata.result,'hex');
|
||||||
txin.parse(b);
|
txin.parse(b);
|
||||||
|
|
||||||
|
/*
|
||||||
if ( txin.isCoinBase() ) {
|
*We have to parse it anyways. It will have outputs even it is a coinbase tx
|
||||||
return cb();
|
if ( txin.isCoinBase() ) {
|
||||||
}
|
return cb();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
txin.outs.forEach( function(j) {
|
txin.outs.forEach( function(j) {
|
||||||
// console.log( c + ': ' + util.formatValue(j.v) );
|
// console.log( c + ': ' + util.formatValue(j.v) );
|
||||||
if (c === outIndex) {
|
if (c === outIndex) {
|
||||||
i.value = j.v;
|
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++;
|
c++;
|
||||||
});
|
});
|
||||||
@ -154,29 +253,41 @@ TransactionSchema.methods.queryInfo = function (next) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tx.ins.forEach(function(i) {
|
tx.ins.forEach(function(i) {
|
||||||
|
if (i.value) {
|
||||||
|
that.info.vin[c].value = util.formatValue(i.value);
|
||||||
|
var n = util.valueToBigInt(i.value).toNumber();
|
||||||
|
that.info.vin[c].valueSat = n;
|
||||||
|
valueIn = valueIn.add( n );
|
||||||
|
|
||||||
that.info.vin[c].value = util.formatValue(i.value);
|
var scriptSig = i.getScript();
|
||||||
var n = util.valueToBigInt(i.value).toNumber();
|
var pubKey = scriptSig.simpleInPubKey();
|
||||||
valueIn = valueIn.add( n );
|
|
||||||
|
|
||||||
var scriptSig = i.getScript();
|
// We check for pubKey in case a broken / strange TX.
|
||||||
var pubKey = scriptSig.simpleInPubKey();
|
if (pubKey) {
|
||||||
|
var pubKeyHash = util.sha256ripe160(pubKey);
|
||||||
// We check for pubKey in case a broken / strange TX.
|
var addr = new Address(network.addressPubkey, pubKeyHash);
|
||||||
if (pubKey) {
|
var addrStr = addr.toString();
|
||||||
var pubKeyHash = util.sha256ripe160(pubKey);
|
that.info.vin[c].addr = addrStr;
|
||||||
var addr = new Address(network.addressPubkey, pubKeyHash);
|
}
|
||||||
var addrStr = addr.toString();
|
else {
|
||||||
that.info.vin[c].addr = addrStr;
|
if (i.addrFromOutput)
|
||||||
|
that.info.vin[c].addr = i.addrFromOutput;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('TX could not be parsed: %s,%d' ,txInfo.result.txid, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
c++;
|
c++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c=0;
|
||||||
tx.outs.forEach( function(i) {
|
tx.outs.forEach( function(i) {
|
||||||
var n = util.valueToBigInt(i.v).toNumber();
|
var n = util.valueToBigInt(i.v).toNumber();
|
||||||
valueOut = valueOut.add(n);
|
valueOut = valueOut.add(n);
|
||||||
|
|
||||||
|
that.info.vout[c].valueSat = n;
|
||||||
|
c++;
|
||||||
});
|
});
|
||||||
|
|
||||||
that.info.valueOut = valueOut / util.COIN;
|
that.info.valueOut = valueOut / util.COIN;
|
||||||
|
|||||||
54
app/models/TransactionItem.js
Normal file
54
app/models/TransactionItem.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
'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);
|
||||||
@ -25,6 +25,7 @@ script(type='text/javascript', src='/js/directives.js')
|
|||||||
script(type='text/javascript', src='/js/filters.js')
|
script(type='text/javascript', src='/js/filters.js')
|
||||||
|
|
||||||
//Application Services
|
//Application Services
|
||||||
|
script(type='text/javascript', src='/js/services/address.js')
|
||||||
script(type='text/javascript', src='/js/services/transactions.js')
|
script(type='text/javascript', src='/js/services/transactions.js')
|
||||||
script(type='text/javascript', src='/js/services/blocks.js')
|
script(type='text/javascript', src='/js/services/blocks.js')
|
||||||
script(type='text/javascript', src='/js/services/global.js')
|
script(type='text/javascript', src='/js/services/global.js')
|
||||||
|
|||||||
@ -3,13 +3,16 @@ head
|
|||||||
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
|
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
|
||||||
meta(name='viewport', content='width=device-width,initial-scale=1.0')
|
meta(name='viewport', content='width=device-width,initial-scale=1.0')
|
||||||
|
|
||||||
title Mystery
|
title= appName+' - '+title
|
||||||
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
|
meta(http-equiv='Content-type', content='text/html;charset=UTF-8')
|
||||||
meta(name="keywords", content="node.js, express, mongoose, mongodb, angularjs")
|
meta(name="keywords", content="node.js, express, mongoose, mongodb, angularjs")
|
||||||
meta(name="description", content="Mystery")
|
meta(name="description", content="Mystery")
|
||||||
|
|
||||||
link(href='/img/icons/favicon.ico', rel='shortcut icon', type='image/x-icon')
|
link(href='/img/icons/favicon.ico', rel='shortcut icon', type='image/x-icon')
|
||||||
|
|
||||||
link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap.min.css')
|
link(rel='stylesheet', href='/lib/bootstrap/dist/css/bootstrap.min.css')
|
||||||
link(rel='stylesheet', href='/css/common.css')
|
link(rel='stylesheet', href='/css/common.css')
|
||||||
|
|
||||||
|
script(src='/socket.io/socket.io.js')
|
||||||
|
script(src='/lib/jquery/jquery.js')
|
||||||
|
|
||||||
|
|||||||
@ -2,3 +2,4 @@ extends layouts/default
|
|||||||
|
|
||||||
block content
|
block content
|
||||||
section.container(data-ng-view)
|
section.container(data-ng-view)
|
||||||
|
|
||||||
|
|||||||
13
app/views/sockets/main.js
Normal file
13
app/views/sockets/main.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Transaction = require('../../models/Transaction');
|
||||||
|
|
||||||
|
module.exports = function(app, io) {
|
||||||
|
io.set('log level', 1); // reduce logging
|
||||||
|
io.sockets.on('connection', function(socket) {
|
||||||
|
Transaction.findOne(function(err, tx) {
|
||||||
|
socket.emit('tx', tx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
6
config/env/development.js
vendored
6
config/env/development.js
vendored
@ -6,9 +6,9 @@ module.exports = {
|
|||||||
name: "Mystery - Development"
|
name: "Mystery - Development"
|
||||||
},
|
},
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
user: 'mystery',
|
protocol: process.env.BITCOIND_PROTO || 'http',
|
||||||
pass: 'real_mystery',
|
user: process.env.BITCOIND_USER || 'mystery',
|
||||||
protocol: 'http',
|
pass: process.env.BITCOIND_PASS || 'real_mystery',
|
||||||
host: process.env.BITCOIND_HOST || '127.0.0.1',
|
host: process.env.BITCOIND_HOST || '127.0.0.1',
|
||||||
port: process.env.BITCOIND_PORT || '18332',
|
port: process.env.BITCOIND_PORT || '18332',
|
||||||
},
|
},
|
||||||
|
|||||||
11
config/env/test.js
vendored
11
config/env/test.js
vendored
@ -1,16 +1,17 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
db: "mongodb://localhost/mystery-dev",
|
db: "mongodb://localhost/mystery-test",
|
||||||
app: {
|
app: {
|
||||||
name: "Mystery - Test"
|
name: "Mystery - Test"
|
||||||
},
|
},
|
||||||
|
port: '3301',
|
||||||
bitcoind: {
|
bitcoind: {
|
||||||
user: 'mystery',
|
protocol: process.env.BITCOIND_PROTO || 'http',
|
||||||
pass: 'real_mystery',
|
user: process.env.BITCOIND_USER || 'mystery',
|
||||||
protocol: 'http',
|
pass: process.env.BITCOIND_PASS || 'real_mystery',
|
||||||
host: process.env.BITCOIND_HOST || '127.0.0.1',
|
host: process.env.BITCOIND_HOST || '127.0.0.1',
|
||||||
port: process.env.BITCOIND_PORT || '8332',
|
port: process.env.BITCOIND_PORT || '18332',
|
||||||
},
|
},
|
||||||
network: 'testnet',
|
network: 'testnet',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,8 @@
|
|||||||
* Module dependencies.
|
* Module dependencies.
|
||||||
*/
|
*/
|
||||||
var express = require('express'),
|
var express = require('express'),
|
||||||
config = require('./config');
|
helpers = require('view-helpers'),
|
||||||
|
config = require('./config');
|
||||||
|
|
||||||
module.exports = function(app, passport, db) {
|
module.exports = function(app, passport, db) {
|
||||||
app.set('showStackError', true);
|
app.set('showStackError', true);
|
||||||
@ -36,6 +37,9 @@ module.exports = function(app, passport, db) {
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.methodOverride());
|
app.use(express.methodOverride());
|
||||||
|
|
||||||
|
//dynamic helpers
|
||||||
|
app.use(helpers(config.app.name));
|
||||||
|
|
||||||
//routes should be at the last
|
//routes should be at the last
|
||||||
app.use(app.router);
|
app.use(app.router);
|
||||||
|
|
||||||
@ -66,4 +70,4 @@ module.exports = function(app, passport, db) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,4 +19,8 @@ module.exports = function(app) {
|
|||||||
|
|
||||||
app.param('txid', transactions.transaction);
|
app.param('txid', transactions.transaction);
|
||||||
|
|
||||||
|
var addresses = require('../app/controllers/addresses');
|
||||||
|
app.get('/api/addr/:addr', addresses.show);
|
||||||
|
app.param('addr', addresses.address);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
121
lib/PeerSync.js
Normal file
121
lib/PeerSync.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
'use strict';
|
||||||
|
require('classtool');
|
||||||
|
|
||||||
|
function spec() {
|
||||||
|
var fs = require('fs');
|
||||||
|
var CoinConst = require('bitcore/const');
|
||||||
|
var coinUtil = require('bitcore/util/util');
|
||||||
|
var Sync = require('./Sync').class();
|
||||||
|
var Peer = require('bitcore/Peer').class();
|
||||||
|
|
||||||
|
var peerdb_fn = 'peerdb.json';
|
||||||
|
|
||||||
|
function PeerSync() {}
|
||||||
|
PeerSync.prototype.init = function(config) {
|
||||||
|
|
||||||
|
var network = config && (config.network || "testnet");
|
||||||
|
|
||||||
|
this.peerdb = undefined;
|
||||||
|
this.sync = new Sync({
|
||||||
|
networkName: network
|
||||||
|
});
|
||||||
|
this.sync.init(config);
|
||||||
|
|
||||||
|
this.PeerManager = require('bitcore/PeerManager').createClass({
|
||||||
|
config: {
|
||||||
|
network: network
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.load_peers();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.load_peers = function() {
|
||||||
|
try {
|
||||||
|
this.peerdb = JSON.parse(fs.readFileSync(peerdb_fn));
|
||||||
|
} catch(d) {
|
||||||
|
console.warn('Unable to read peer db', peerdb_fn, 'creating new one.');
|
||||||
|
this.peerdb = [{
|
||||||
|
ipv4: '127.0.0.1',
|
||||||
|
port: 18333
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
fs.writeFileSync(peerdb_fn, JSON.stringify(this.peerdb));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.handle_inv = function(info) {
|
||||||
|
// TODO: should limit the invs to objects we haven't seen yet
|
||||||
|
var invs = info.message.invs;
|
||||||
|
invs.forEach(function(inv) {
|
||||||
|
console.log('[p2p_sync] Handle inv for a ' + CoinConst.MSG.to_str(inv.type));
|
||||||
|
});
|
||||||
|
// this is not needed right now, but it's left in case
|
||||||
|
// we need to store more info in the future
|
||||||
|
info.conn.sendGetData(invs);
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.handle_tx = function(info) {
|
||||||
|
var tx = info.message.tx.getStandardizedObject();
|
||||||
|
console.log('[p2p_sync] Handle tx: ' + tx.hash);
|
||||||
|
this.sync.storeTxs([tx.hash], function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log('[p2p_sync] Error in handle TX: ' + err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.handle_block = function(info) {
|
||||||
|
var self = this;
|
||||||
|
var block = info.message.block;
|
||||||
|
var now = Math.round(new Date().getTime() / 1000);
|
||||||
|
var blockHash = coinUtil.formatHashFull(block.calcHash());
|
||||||
|
console.log('[p2p_sync] Handle block: ' + blockHash);
|
||||||
|
this.sync.storeBlock({
|
||||||
|
'hash': blockHash,
|
||||||
|
'time': now
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
console.log('[p2p_sync] Error in handle Block: ' + err);
|
||||||
|
} else {
|
||||||
|
// if no errors importing block, import the transactions
|
||||||
|
var hashes = block.txs.map(function(tx) {
|
||||||
|
return coinUtil.formatHashFull(tx.hash);
|
||||||
|
});
|
||||||
|
self.sync.storeTxs(hashes, function() {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.handle_connected = function(data) {
|
||||||
|
var peerman = data.pm;
|
||||||
|
var peers_n = peerman.peers.length;
|
||||||
|
console.log('[p2p_sync] Connected to ' + peers_n + ' peer' + (peers_n !== 1 ? 's': ''));
|
||||||
|
};
|
||||||
|
|
||||||
|
PeerSync.prototype.run = function() {
|
||||||
|
var self = this;
|
||||||
|
var peerman = new this.PeerManager();
|
||||||
|
|
||||||
|
this.peerdb.forEach(function(datum) {
|
||||||
|
var peer = new Peer(datum.ipv4, datum.port);
|
||||||
|
peerman.addPeer(peer);
|
||||||
|
});
|
||||||
|
|
||||||
|
peerman.on('connection', function(conn) {
|
||||||
|
conn.on('inv', self.handle_inv.bind(self));
|
||||||
|
conn.on('block', self.handle_block.bind(self));
|
||||||
|
conn.on('tx', self.handle_tx.bind(self));
|
||||||
|
});
|
||||||
|
peerman.on('connect', self.handle_connected.bind(self));
|
||||||
|
|
||||||
|
peerman.start();
|
||||||
|
};
|
||||||
|
return PeerSync;
|
||||||
|
|
||||||
|
}
|
||||||
|
module.defineClass(spec);
|
||||||
|
|
||||||
115
lib/Sync.js
115
lib/Sync.js
@ -2,23 +2,20 @@
|
|||||||
|
|
||||||
require('classtool');
|
require('classtool');
|
||||||
|
|
||||||
/* We dont sync any contents from TXs, only their IDs are stored */
|
|
||||||
|
|
||||||
var isSyncTxEnabled = 0;
|
|
||||||
|
|
||||||
function spec() {
|
function spec() {
|
||||||
var mongoose = require('mongoose');
|
var mongoose = require('mongoose');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
var RpcClient = require('bitcore/RpcClient').class();
|
||||||
var RpcClient = require('bitcore/RpcClient').class();
|
var networks = require('bitcore/networks');
|
||||||
var networks = require('bitcore/networks');
|
var async = require('async');
|
||||||
var async = require('async');
|
var config = require('../config/config');
|
||||||
|
var Block = require('../app/models/Block');
|
||||||
var config = require('../config/config');
|
var Transaction = require('../app/models/Transaction');
|
||||||
var Block = require('../app/models/Block');
|
var TransactionItem = require('../app/models/TransactionItem');
|
||||||
var Transaction = require('../app/models/Transaction');
|
|
||||||
|
|
||||||
function Sync(config) {
|
function Sync(config) {
|
||||||
|
this.tx_count =0;
|
||||||
this.network = config.networkName === 'testnet' ? networks.testnet: networks.livenet;
|
this.network = config.networkName === 'testnet' ? networks.testnet: networks.livenet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +33,7 @@ function spec() {
|
|||||||
if (blockInfo.result.height % 1000 === 0) {
|
if (blockInfo.result.height % 1000 === 0) {
|
||||||
var h = blockInfo.result.height,
|
var h = blockInfo.result.height,
|
||||||
d = blockInfo.result.confirmations;
|
d = blockInfo.result.confirmations;
|
||||||
progress_bar('height', h, h + d);
|
progress_bar(util.format('Height [txs:%d]',that.tx_count), h, h + d);
|
||||||
}
|
}
|
||||||
|
|
||||||
that.storeBlock(blockInfo.result, function(err) {
|
that.storeBlock(blockInfo.result, function(err) {
|
||||||
@ -62,8 +59,23 @@ function spec() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Sync.prototype.storeTxs = function(txs, cb) {
|
Sync.prototype.storeTxs = function(txids, cb) {
|
||||||
Transaction.createFromArray(txs, cb);
|
var that=this;
|
||||||
|
Transaction.createFromArray(txids, function(err) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
async.each(txids, function(txid, next) {
|
||||||
|
|
||||||
|
// This will trigger an RPC call
|
||||||
|
Transaction.explodeTransactionItems( txid, function(err) {
|
||||||
|
that.tx_count++;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
return cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Sync.prototype.syncBlocks = function(reindex, cb) {
|
Sync.prototype.syncBlocks = function(reindex, cb) {
|
||||||
@ -151,9 +163,50 @@ function spec() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Sync.prototype.init = function(opts) {
|
|
||||||
|
|
||||||
mongoose.connect(config.db);
|
// Not used
|
||||||
|
Sync.prototype.processTXs = function(reindex, cb) {
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
|
||||||
|
console.log('Syncing TXs...');
|
||||||
|
|
||||||
|
var filter = reindex ? {} : { processed: false } ;
|
||||||
|
|
||||||
|
Transaction.find(filter, function(err, txs) {
|
||||||
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
var read = 0,
|
||||||
|
pull = 0,
|
||||||
|
proc = 0,
|
||||||
|
total = txs.length;
|
||||||
|
|
||||||
|
console.log('\tneed to pull %d txs', total);
|
||||||
|
|
||||||
|
if (!total) return cb();
|
||||||
|
|
||||||
|
async.each(txs, function(tx, next) {
|
||||||
|
if (read++ % 1000 === 0) progress_bar('read', read, total);
|
||||||
|
|
||||||
|
if (!tx.txid) {
|
||||||
|
console.log('NO TXID skipping...', tx);
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will trigger an RPC call
|
||||||
|
Transaction.explodeTransactionItems( tx.txid, function(err) {
|
||||||
|
if (proc++ % 1000 === 0) progress_bar('\tproc', pull, total);
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cb);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Sync.prototype.init = function(opts) {
|
||||||
|
if (!(opts && opts.skip_db_connection)) {
|
||||||
|
mongoose.connect(config.db);
|
||||||
|
}
|
||||||
this.db = mongoose.connection;
|
this.db = mongoose.connection;
|
||||||
this.rpc = new RpcClient(config.bitcoind);
|
this.rpc = new RpcClient(config.bitcoind);
|
||||||
|
|
||||||
@ -182,6 +235,15 @@ function spec() {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
function(cb) {
|
||||||
|
if (opts.destroy) {
|
||||||
|
console.log('Deleting TXItems...');
|
||||||
|
that.db.collections.transactionitems.drop(cb);
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
function(cb) {
|
function(cb) {
|
||||||
if (!opts.skip_blocks) {
|
if (!opts.skip_blocks) {
|
||||||
that.syncBlocks(opts.reindex, cb);
|
that.syncBlocks(opts.reindex, cb);
|
||||||
@ -189,14 +251,27 @@ function spec() {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
/* Exploding happens on block insertion
|
||||||
function(cb) {
|
function(cb) {
|
||||||
if (isSyncTxEnabled && ! opts.skip_txs) {
|
if (! opts.skip_txs) {
|
||||||
|
that.processTXs(opts.reindex, cb);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/* We dont sync any contents from TXs, only their IDs are stored
|
||||||
|
function(cb) {
|
||||||
|
if (! opts.skip_txs) {
|
||||||
that.syncTXs(opts.reindex, cb);
|
that.syncTXs(opts.reindex, cb);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return cb();
|
return cb();
|
||||||
}
|
}
|
||||||
}], function(err) {
|
}
|
||||||
|
*/
|
||||||
|
], function(err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
6
p2p.js
6
p2p.js
@ -1,4 +1,8 @@
|
|||||||
#! /usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
|
||||||
|
>>>>>>> 71e1c718ac8f5eb89acedb4f91f2207ec463808b
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|||||||
@ -59,7 +59,10 @@
|
|||||||
"grunt-concurrent": "~0.4.2",
|
"grunt-concurrent": "~0.4.2",
|
||||||
"grunt-nodemon": "~0.1.2",
|
"grunt-nodemon": "~0.1.2",
|
||||||
"grunt-mocha-test": "~0.8.1",
|
"grunt-mocha-test": "~0.8.1",
|
||||||
"should": "~2.1.1"
|
"should": "~2.1.1",
|
||||||
|
"view-helpers": "latest",
|
||||||
|
"socket.io": "~0.9.16"
|
||||||
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"grunt-contrib-watch": "latest",
|
"grunt-contrib-watch": "latest",
|
||||||
|
|||||||
@ -19,7 +19,7 @@ angular.module('mystery').config(['$routeProvider',
|
|||||||
when('/blocks-date/:blockDate', {
|
when('/blocks-date/:blockDate', {
|
||||||
templateUrl: 'views/blocks/list.html'
|
templateUrl: 'views/blocks/list.html'
|
||||||
}).
|
}).
|
||||||
when('/address/:address', {
|
when('/address/:addrStr', {
|
||||||
templateUrl: 'views/address.html'
|
templateUrl: 'views/address.html'
|
||||||
}).
|
}).
|
||||||
otherwise({
|
otherwise({
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('mystery.address').controller('AddressController', ['$scope', function ($scope) {
|
angular.module('mystery.address').controller('AddressController', ['$scope', '$routeParams', '$location', 'Global', 'Address', function ($scope, $routeParams, $location, Global, Address) {
|
||||||
|
/*
|
||||||
//example data
|
|
||||||
$scope.address = '1JmTTDcksW7A6GN7JnxuXkMAXsVN9zmgm1';
|
$scope.address = '1JmTTDcksW7A6GN7JnxuXkMAXsVN9zmgm1';
|
||||||
$scope.hash160 = '77ad7d08aaa9cf489ea4e468eaeb892b85f71e27';
|
$scope.hash160 = '77ad7d08aaa9cf489ea4e468eaeb892b85f71e27';
|
||||||
$scope.transactions = [
|
$scope.transactions = [
|
||||||
@ -17,4 +16,15 @@ angular.module('mystery.address').controller('AddressController', ['$scope', fun
|
|||||||
amount: 0.1
|
amount: 0.1
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
*/
|
||||||
|
$scope.global = Global;
|
||||||
|
|
||||||
|
$scope.findOne = function() {
|
||||||
|
Address.get({
|
||||||
|
addrStr: $routeParams.addrStr
|
||||||
|
}, function(address) {
|
||||||
|
$scope.address = address;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@ -20,6 +20,4 @@ angular.module('mystery.blocks').controller('BlocksController', ['$scope', '$rou
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// for avoid warning. please remove when you use Blocks
|
|
||||||
$scope.blocks = Blocks;
|
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@ -1,6 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('mystery.system').controller('IndexController', ['$scope', 'Global', 'Index', function ($scope, Global, Index) {
|
angular.module('mystery.system').controller('IndexController', ['$scope', 'Global', 'Index', function($scope, Global, Index) {
|
||||||
$scope.global = Global;
|
$scope.global = Global;
|
||||||
$scope.index = Index;
|
$scope.index = Index;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var socket = io.connect('http://localhost');
|
||||||
|
socket.on('tx', function(data) {
|
||||||
|
var tx = data;
|
||||||
|
console.log('Transaction received! '+tx.txid);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|||||||
8
public/js/services/address.js
Normal file
8
public/js/services/address.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular.module('mystery.address').factory('Address', ['$resource', function($resource) {
|
||||||
|
return $resource('/api/addr/:addrStr', {
|
||||||
|
addrStr: '@addStr'
|
||||||
|
});
|
||||||
|
}]);
|
||||||
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
<section data-ng-controller="AddressController">
|
<section data-ng-controller="AddressController" data-ng-init="findOne()">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>
|
<h1>
|
||||||
Address
|
Address
|
||||||
<small>{{address}}</small>
|
<small>Addresses are identifiers which you use to send bitcoins to another person.</small>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-9">
|
<div class="col-lg-9">
|
||||||
@ -10,29 +10,30 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Address</td>
|
<td>Address</td>
|
||||||
<td><a href="#!/address/{{address}}">{{address}}</a></td>
|
<td><a href="/#!/address/{{address}}">{{address.addrStr}}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Hash160</td>
|
<td>Total Received</td>
|
||||||
<td><a href="#!/address/{{hash160}}">{{hash160}}</a></td>
|
<td>{{address.totalReceivedSat / 100000000}} BTC</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Total Output</td>
|
<td>Total Sent</td>
|
||||||
<td>1 BTC</td>
|
<td>{{address.totalSentSat / 100000000}} BTC</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Total Input</td>
|
<td>Final Balance</td>
|
||||||
<td>0.2 BTC</td>
|
<td>{{address.balanceSat / 100000000}} BTC</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Current balance</td>
|
<td>No. Transactions</td>
|
||||||
<td>10.2 BTC</td>
|
<td>{{address.txApperances}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<qrcode size="200" data="{{address}}"></qrcode>
|
<qrcode size="200" data="{{address.addrStr}}"></qrcode>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h3>
|
<h3>
|
||||||
@ -49,10 +50,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr data-ng-repeat="transaction in transactions">
|
<tr data-ng-repeat="transaction in address.transactions">
|
||||||
<td><a href="#!/tx/{{transaction.hash}}">{{transaction.hash}}</a></td>
|
<td><a href="/#!/tx/{{transaction}}">{{transaction}}</a></td>
|
||||||
<td>{{transaction.time | date:'medium'}}</td>
|
<td>--</td>
|
||||||
<td>{{transaction.amount}} BTC</td>
|
<td>--</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -52,7 +52,12 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" style="text-align: right;">
|
<td colspan="3" style="text-align: right;">
|
||||||
<button type="button" class="btn btn-primary">{{tx.confirmations}} Confirmations</button>
|
<button data-ng-show="tx.confirmations" type="button" class="btn btn-primary">
|
||||||
|
{{tx.confirmations}} Confirmations
|
||||||
|
</button>
|
||||||
|
<button data-ng-show="!tx.confirmations" type="button" class="btn btn-danger">
|
||||||
|
Unconfirmed Transaction!
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-success">{{tx.valueOut}} BTC</button>
|
<button type="button" class="btn btn-success">{{tx.valueOut}} BTC</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
41
server.js
41
server.js
@ -1,23 +1,25 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
var express = require('express'),
|
|
||||||
fs = require('fs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main application entry file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Load configurations
|
//Load configurations
|
||||||
//Set the node enviornment variable if not set before
|
//Set the node enviornment variable if not set before
|
||||||
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
//Initializing system variables
|
/**
|
||||||
var config = require('./config/config'),
|
* Module dependencies.
|
||||||
|
*/
|
||||||
|
var express = require('express'),
|
||||||
|
fs = require('fs'),
|
||||||
|
PeerSync = require('./lib/PeerSync').class(),
|
||||||
mongoose = require('mongoose');
|
mongoose = require('mongoose');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application entry file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//Initializing system variables
|
||||||
|
var config = require('./config/config');
|
||||||
|
|
||||||
//Bootstrap db connection
|
//Bootstrap db connection
|
||||||
var db = mongoose.connect(config.db);
|
var db = mongoose.connect(config.db);
|
||||||
|
|
||||||
@ -38,6 +40,14 @@ var walk = function(path) {
|
|||||||
};
|
};
|
||||||
walk(models_path);
|
walk(models_path);
|
||||||
|
|
||||||
|
// p2p_sync process
|
||||||
|
var ps = new PeerSync();
|
||||||
|
ps.init({
|
||||||
|
skip_db_connection: true
|
||||||
|
});
|
||||||
|
ps.run();
|
||||||
|
|
||||||
|
// express app
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
//express settings
|
//express settings
|
||||||
@ -46,9 +56,14 @@ require('./config/express')(app, db);
|
|||||||
//Bootstrap routes
|
//Bootstrap routes
|
||||||
require('./config/routes')(app);
|
require('./config/routes')(app);
|
||||||
|
|
||||||
|
// socket.io
|
||||||
|
var server = require('http').createServer(app);
|
||||||
|
var io = require('socket.io').listen(server);
|
||||||
|
require('./app/views/sockets/main.js')(app,io);
|
||||||
|
|
||||||
//Start the app by listening on <port>
|
//Start the app by listening on <port>
|
||||||
var port = process.env.PORT || config.port;
|
var port = process.env.PORT || config.port;
|
||||||
app.listen(port);
|
server.listen(port);
|
||||||
console.log('Express app started on port ' + port);
|
console.log('Express app started on port ' + port);
|
||||||
|
|
||||||
//expose app
|
//expose app
|
||||||
|
|||||||
54
test/model/addr.js
Normal file
54
test/model/addr.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
var
|
||||||
|
assert = require('assert'),
|
||||||
|
fs = require('fs'),
|
||||||
|
config = require('../../config/config'),
|
||||||
|
Address = require('../../app/models/Address').class();
|
||||||
|
mongoose= require('mongoose'),
|
||||||
|
addrValid = JSON.parse(fs.readFileSync('test/model/addr.json'));
|
||||||
|
|
||||||
|
describe('Address update', function(){
|
||||||
|
|
||||||
|
before(function(done) {
|
||||||
|
mongoose.connect(config.db);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function(done) {
|
||||||
|
mongoose.connection.close();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
addrValid.forEach( function(v) {
|
||||||
|
if (v.disabled) {
|
||||||
|
console.log(v.addr + " => disabled in JSON");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it('should retrieve the correct info for:' + v.addr, function(done) {
|
||||||
|
this.timeout(5000);
|
||||||
|
|
||||||
|
var a = new Address(v.addr);
|
||||||
|
|
||||||
|
a.update(function(err) {
|
||||||
|
if (err) done(err);
|
||||||
|
|
||||||
|
assert.equal(v.addr, a.addrStr);
|
||||||
|
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.transactions) {
|
||||||
|
v.transactions.forEach( function(tx) {
|
||||||
|
assert(tx in a.inTransactions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
93
test/model/addr.json
Normal file
93
test/model/addr.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mp3Rzxx9s1A21SY3sjJ3CQoa2Xjph7e5eS",
|
||||||
|
"balance": 0,
|
||||||
|
"totalReceived": 50,
|
||||||
|
"totalSent": 50.0
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"addr": "mgqvRGJMwR9JU5VhJ3x9uX9MTkzTsmmDgQ",
|
||||||
|
"balance": 43.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled":1,
|
||||||
|
"addr": "mzW2hdZN2um7WBvTDerdahKqRgj3md9C29",
|
||||||
|
"balance": 910.39522682,
|
||||||
|
"totalReceived": 910.39522682,
|
||||||
|
"totalSent": 0
|
||||||
|
}
|
||||||
|
,
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"disabled":1,
|
||||||
|
"addr": "mjRmkmYzvZN3cA3aBKJgYJ65epn3WCG84H",
|
||||||
|
"balance": 46413.0,
|
||||||
|
"totalReceived": 357130.17644359,
|
||||||
|
"totalSent": 310717.17644359
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled":1,
|
||||||
|
"addr": "mgKY35SXqxFpcKK3Dq9mW9919N7wYXvcFM",
|
||||||
|
"balance": 0.01979459,
|
||||||
|
"totalReceived": 0.01979459,
|
||||||
|
"totalSent": 0,
|
||||||
|
"transactions": [ "91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5" ]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"disabled":1,
|
||||||
|
"addr": "mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5",
|
||||||
|
"balance": 10580.50027254,
|
||||||
|
"totalReceived": 12157.65075053,
|
||||||
|
"totalSent": 1577.15047799,
|
||||||
|
"transactions": [
|
||||||
|
"91800d80bb4c69b238c9bfd94eb5155ab821e6b25cae5c79903d12853bbb4ed5",
|
||||||
|
"f6e80d4fd1a2377406856c67d0cee5ac7e5120993ff97e617ca9aac33b4c6b1e",
|
||||||
|
"bc27f31caae86750b126d9b09e969362b85b7c15f41421387d682064544bf7e7",
|
||||||
|
"2cd6a1cb26880276fbc9851396f1bd8081cb2b9107ff6921e8fd65ed2df3df79",
|
||||||
|
"8bea41f573bccb7b648bc0b1bbfeba8a96da05b1d819ff4a33d39fbcd334ecfd",
|
||||||
|
"cb0d55c37acc57f759255193673e13858b5ab3d8fdfa7ee8b25f9964bdaa11e3",
|
||||||
|
"7b007aeace2299d27b6bb6c24d0a8040d6a87e4c2601216c34d226462b75f915",
|
||||||
|
"a9f40fbaecd2b28a05405e28b95566d7b3bd8ac38a2853debd72517f2994c6fc",
|
||||||
|
"4123255b7678e37c168b9e929927760bc5d9363b0c78ec61a7b4a78b2a07adab",
|
||||||
|
"cb3760529c2684c32047a2fddf0e2534c9241e5d72011aac4a8982e0c7b46df3",
|
||||||
|
"e8d00d8cc744381233dbc95e2d657345084dfb6df785b81285183f4c89b678d4",
|
||||||
|
"7a748364255c5b64979d9d3da35ea0fbef0114e0d7f96fccd5bea76f6d19f06b",
|
||||||
|
"d0b7e087040f67ef9bd9f21ccf53d1b5410400351d949cabf127caf28a6e7add",
|
||||||
|
"209f97873265652b83922921148cad92d7e048c6822e4864e984753e04181470",
|
||||||
|
"3a4af7755d3061ecced2f3707c2623534104f08aa73a52ca243d7ddecf5fe86d",
|
||||||
|
"4a4b5c8d464a77814ed35a37e2a28e821d467a803761427c057f67823309b725",
|
||||||
|
"d85f5265618fb694c3ea3ca6f73eba93df8a644bc1c7286cec2fbc2fbf7d895e",
|
||||||
|
"0d2c778ed9976b52792c941cac126bda37d3b1453082022d5e36ac401be3b249",
|
||||||
|
"daf03d666047ca0b5340b4a0027f8562b7c5bac87dca3727093b5393176a541a",
|
||||||
|
"a0dc03a870e589ea51e3d3a8aed0d34f4f1ae6844acad26dae48fe523b26e764",
|
||||||
|
"3df1a50e2e5d8525f04bd21a66bad824364a975449fa24fd5c2537d0f713919b",
|
||||||
|
"7bc26c1f3b4ab5ca57677593d28d13bff468a658f4d5efc379c1612554cf668e",
|
||||||
|
"ded4cbc9c52fd5599b6a93f89a79cde9aeb5a7f8f56732bb67ae9554325b3666",
|
||||||
|
"91224a219196a3f6e6f40ad2137b13fe54109e57aaed7527ea34aa903e6b8313",
|
||||||
|
"ee899a182bbb75e98ef14d83489e631dd66a8c5059dc8255692dd8ca9efba01f",
|
||||||
|
"0a61590c7548bd4f6a0df1575b268057e5e3e295a44eaeeb1dfbd01332c585ed",
|
||||||
|
"d56c22950ad2924f404b5b0baa6e49b0df1aaf09d1947842aed9d0178958eb9d",
|
||||||
|
"c6b5368c5a256141894972fbd02377b3894aa0df7c35fab5e0eca90de064fdc1",
|
||||||
|
"158e1f9c3f8ec44e88052cadef74e8eb99fbad5697d0b005ba48c933f7d96816",
|
||||||
|
"7f6191c0f4e3040901ef0d5d6e76af4f16423061ca1347524c86205e35d904d9",
|
||||||
|
"2c2e20f976b98a0ca76c57eca3653699b60c1bd9503cc9cc2fb755164a679a26",
|
||||||
|
"59bc81733ff0eaf2b106a70a655e22d2cdeff80ada27b937693993bf0c22e9ea",
|
||||||
|
"7da38b66fb5e8582c8be85abecfd744a6de89e738dd5f3aaa0270b218ec424eb",
|
||||||
|
"393d51119cdfbf0a308c0bbde2d4c63546c0961022bad1503c4bbaed0638c837",
|
||||||
|
"4518868741817ae6757fd98de27693b51fad100e89e5206b9bbf798aeebb804c",
|
||||||
|
"c58bce14de1e3016504babd8bbe8175207d75074134a2548a71743fa3e56c58d",
|
||||||
|
"6e69ec4a97515a8fd424f123a5fc1fdfd3c3adcd741292cbc09c09a2cc433bea",
|
||||||
|
"0e15f2498362050e5ceb6157d0fbf820fdcaf936e447207d433ee7701d7b99c2",
|
||||||
|
"a3789e113041db907a1217ddb5c3aaf0eff905cc3d913e68d977e1ab4d19acea",
|
||||||
|
"80b460922faf0ad1e8b8a55533654c9a9f3039bfff0fff2bcf8536b8adf95939"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -8,12 +8,16 @@ var
|
|||||||
mongoose= require('mongoose'),
|
mongoose= require('mongoose'),
|
||||||
assert = require('assert'),
|
assert = require('assert'),
|
||||||
config = require('../../config/config'),
|
config = require('../../config/config'),
|
||||||
Transaction = require('../../app/models/Transaction');
|
Transaction = require('../../app/models/Transaction'),
|
||||||
|
TransactionItem = require('../../app/models/TransactionItem'),
|
||||||
|
fs = require('fs'),
|
||||||
|
util = require('util');
|
||||||
|
|
||||||
|
|
||||||
|
var txItemsValid = JSON.parse(fs.readFileSync('test/model/txitems.json'));
|
||||||
mongoose.connection.on('error', function(err) { console.log(err); });
|
mongoose.connection.on('error', function(err) { console.log(err); });
|
||||||
|
|
||||||
describe('Transaction fromIdWithInfo', function(){
|
describe('Transaction', function(){
|
||||||
|
|
||||||
before(function(done) {
|
before(function(done) {
|
||||||
mongoose.connect(config.db);
|
mongoose.connect(config.db);
|
||||||
@ -24,22 +28,36 @@ describe('Transaction fromIdWithInfo', function(){
|
|||||||
mongoose.connection.close();
|
mongoose.connection.close();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
it('should pool tx\'s object from mongoose', function(done) {
|
||||||
|
var txid = '7e621eeb02874ab039a8566fd36f4591e65eca65313875221842c53de6907d6c';
|
||||||
|
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');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should pool tx\'s object from mongoose', function(done) {
|
it('should pool tx\'s object from mongoose', function(done) {
|
||||||
var test_txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237';
|
var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237';
|
||||||
Transaction.fromIdWithInfo(test_txid, function(err, tx) {
|
Transaction.fromIdWithInfo(txid, function(err, tx) {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
assert.equal(tx.txid, test_txid);
|
assert.equal(tx.txid, txid);
|
||||||
assert(!tx.info.isCoinBase);
|
assert(!tx.info.isCoinBase);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pool tx\'s info from bitcoind', function(done) {
|
it('should pool tx\'s info from bitcoind', function(done) {
|
||||||
var test_txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237';
|
var txid = '21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237';
|
||||||
Transaction.fromIdWithInfo(test_txid, function(err, tx) {
|
Transaction.fromIdWithInfo(txid, function(err, tx) {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
assert.equal(tx.info.txid, test_txid);
|
assert.equal(tx.info.txid, txid);
|
||||||
assert.equal(tx.info.blockhash, '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74');
|
assert.equal(tx.info.blockhash, '000000000185678d3d7ecc9962c96418174431f93fe20bf216d5565272423f74');
|
||||||
assert.equal(tx.info.valueOut, 1.66174);
|
assert.equal(tx.info.valueOut, 1.66174);
|
||||||
assert.equal(tx.info.feeds, 0.0005 );
|
assert.equal(tx.info.feeds, 0.0005 );
|
||||||
@ -49,28 +67,85 @@ describe('Transaction fromIdWithInfo', function(){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('test a coinbase TX 2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399', function(done) {
|
var txid1 = '2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399';
|
||||||
var test_txid2 = '2a104bab1782e9b6445583296d4a0ecc8af304e4769ceb64b890e8219c562399';
|
it('test a coinbase TX ' + txid1, function(done) {
|
||||||
Transaction.fromIdWithInfo(test_txid2, function(err, tx) {
|
Transaction.fromIdWithInfo(txid1, function(err, tx) {
|
||||||
if (err) done(err);
|
if (err) done(err);
|
||||||
assert(tx.info.isCoinBase);
|
assert(tx.info.isCoinBase);
|
||||||
assert.equal(tx.info.txid, test_txid2);
|
assert.equal(tx.info.txid, txid1);
|
||||||
assert(!tx.info.feeds);
|
assert(!tx.info.feeds);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
var txid22 = '666';
|
||||||
|
it('test unexisting TX ' + txid22, function(done) {
|
||||||
|
Transaction.fromIdWithInfo(txid22, function(err, tx) {
|
||||||
|
if (err && err.toString().match(/TX.not.found/)) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('test a broken TX 64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608', function(done) {
|
var txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608';
|
||||||
var test_txid2 = '64496d005faee77ac5a18866f50af6b8dd1f60107d6795df34c402747af98608';
|
it('test a broken TX ' + txid2, function(done) {
|
||||||
Transaction.fromIdWithInfo(test_txid2, function(err, tx) {
|
Transaction.fromIdWithInfo(txid2, function(err, tx) {
|
||||||
if (err) done(err);
|
if (err) return done(err);
|
||||||
assert.equal(tx.info.txid, test_txid2);
|
assert.equal(tx.info.txid, txid2);
|
||||||
assert.equal(tx.info.vin[0].addr, null);
|
assert.equal(tx.info.vin[0].addr, 'n1JagbRWBDi6VMvG7HfZmXX74dB9eiHJzU');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
Transaction.explodeTransactionItems(v.txid, 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
72
test/model/txitems.json
Normal file
72
test/model/txitems.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"disabled": 1,
|
||||||
|
"txid": "75c5ffe6dc2eb0f6bd011a08c041ef115380ccd637d859b379506a0dca4c26fc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txid": "21798ddc9664ac0ef618f52b151dda82dafaf2e26d2bbef6cdaf55a6957ca237",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"addr": "mwcFwXv2Yquy4vJA4nnNLAbHVjrPdC8Q1Z",
|
||||||
|
"value_sat": -166224000,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA",
|
||||||
|
"value_sat": 134574000,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "n28wb1cRGxPtfmsenYKFfsvnZ6kRapx3jF",
|
||||||
|
"value_sat": 31600000,
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txid": "b633a6249d4a2bc123e7f8a151cae2d4afd17aa94840009f8697270c7818ceee",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"addr": "mzjLe62faUqCSjkwQkwPAL5nYyR8K132fA",
|
||||||
|
"value_sat": -40790667,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mhfQJUSissP6nLM5pz6DxHfctukrrLct2T",
|
||||||
|
"value_sat": 19300000,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mzcDhbL877ES3MGftWnc3EuTSXs3WXDDML",
|
||||||
|
"value_sat": 21440667,
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"txid": "ca2f42e44455b8a84434de139efea1fe2c7d71414a8939e0a20f518849085c3b",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"addr": "mzeiUi4opeheWYveXqp8ebqHyVwYGA2s3x",
|
||||||
|
"value_sat": -1225871,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mtMLijHAbG8CsgBbQGajsqav9p9wKUYad5",
|
||||||
|
"value_sat": -1201823,
|
||||||
|
"index": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mhqyL1nDQDo1WLH9qH8sjRjx2WwrnmAaXE",
|
||||||
|
"value_sat": 1327746,
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"addr": "mkGrySSnxcqRbtPCisApj3zXCQVmUUWbf1",
|
||||||
|
"value_sat": 1049948,
|
||||||
|
"index": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
33
util/find_ref.sh
Executable file
33
util/find_ref.sh
Executable file
@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
FIND='find';
|
||||||
|
|
||||||
|
##if [[ "$OSTYPE" =~ "darwin" ]]
|
||||||
|
##then
|
||||||
|
## FIND='gfind'
|
||||||
|
##fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ -z "$1" ]
|
||||||
|
then
|
||||||
|
echo "$0 : find functions references "
|
||||||
|
echo "Usage $0 function_name "
|
||||||
|
exit;
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXTRA=''
|
||||||
|
|
||||||
|
|
||||||
|
CMD="grep -rnH"
|
||||||
|
|
||||||
|
if [ "$2" != '--nocolor' ]
|
||||||
|
then
|
||||||
|
CMD="$CMD --color=always"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
$FIND -L . -name \*.json -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \
|
||||||
|
-o -name \*.html -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \
|
||||||
|
-o -name \*.jade -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} + \
|
||||||
|
-o -name \*.js -not -wholename \*node_modules\* -not -wholename \*public/lib\* -exec $CMD "$1" {} +
|
||||||
|
|
||||||
20
util/p2p.js
Executable file
20
util/p2p.js
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#! /usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
|
||||||
|
var PeerSync = require('../lib/PeerSync').class();
|
||||||
|
|
||||||
|
var PROGRAM_VERSION = '0.1';
|
||||||
|
var program = require('commander');
|
||||||
|
|
||||||
|
program
|
||||||
|
.version(PROGRAM_VERSION)
|
||||||
|
.option('-N --network [testnet]', 'Set bitcoin network [testnet]', 'testnet')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
var ps = new PeerSync();
|
||||||
|
ps.init(program);
|
||||||
|
ps.run();
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user