Reorg db and mock services

This commit is contained in:
eordano 2015-02-25 14:36:30 -03:00
parent d704a6d143
commit 191cb0e0d1
9 changed files with 308 additions and 7 deletions

View File

@ -3,3 +3,11 @@ BitcoreNode:
network: livenet
host: localhost
port: 8333
LevelUp: ./db
RPC:
username: username
password: password
protocol: http
network: livenet
host: 127.0.0.1
port: 8332

View File

@ -1,10 +1,3 @@
'use strict';
var imports = require('soop').imports();
// to show tx outs
var OUTS_PREFIX = 'txo-'; //txo-<txid>-<n> => [addr, btc_sat]
var SPENT_PREFIX = 'txs-'; //txs-<txid(out)>-<n(out)>-<txid(in)>-<n(in)> = ts

0
lib/indexes.js Normal file
View File

115
lib/services/block.js Normal file
View File

@ -0,0 +1,115 @@
'use strict';
var LevelUp = require('levelup');
var Promise = require('bluebird');
var RPC = require('bitcoind-rpc');
var TransactionService = require('./transaction');
var bitcore = require('bitcore');
var config = require('config');
var $ = bitcore.util.preconditions;
var JSUtil = bitcore.util.js;
var _ = bitcore.deps._;
var LATEST_BLOCK = 'latest-block';
function BlockService (opts) {
opts = _.extend({}, opts);
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
this.transactionService = opts.transactionService || new TransactionService({
database: this.database,
rpc: this.rpc
});
}
BlockService.blockRPCtoBitcore = function(blockData, transactions) {
$.checkArgument(_.all(transactions, function(transaction) {
return transaction instanceof bitcore.Transaction;
}), 'All transactions must be instances of bitcore.Transaction');
return new bitcore.Block({
header: new bitcore.BlockHeader({
version: blockData.version,
prevHash: bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.previousblockhash, 'hex')
),
time: blockData.time,
nonce: blockData.nonce,
bits: new bitcore.deps.bnjs(
new bitcore.deps.Buffer(blockData.bits, 'hex')
),
merkleRoot: bitcore.util.buffer.reverse(
new bitcore.deps.Buffer(blockData.merkleRoot, 'hex')
)
}),
transactions: transactions
});
};
BlockService.prototype.getBlock = function(blockHash) {
$.checkArgument(JSUtil.isHexa(blockHash), 'Block hash must be hexa');
var blockData;
var self = this;
return Promise.try(function() {
return self.rpc.getBlockAsync(blockHash);
}).then(function(block) {
blockData = block.result;
return Promise.all(blockData.tx.map(function(txId) {
return self.transactionService.getTransaction(txId);
}));
}).then(function(transactions) {
blockData.transactions = transactions;
return Promise.resolve(BlockService.blockRPCtoBitcore(blockData));
}).catch(function(err) {
console.log(err);
return Promise.reject(err);
});
};
BlockService.prototype.getBlockByHeight = function(height) {
$.checkArgument(_.isNumber(height), 'Block height must be a number');
var self = this;
return Promise.try(function() {
return this.rpc.getBlockHash(height);
}).then(function(blockHash) {
return self.getBlock(blockHash);
}).catch(function(err) {
console.log(err);
return Promise.reject(err);
});
};
BlockService.prototype.getLatest = function() {
var self = this;
return Promise.try(function() {
return self.database.getAsync(LATEST_BLOCK);
}).then(function(blockHash) {
return self.getBlock(blockHash);
}).catch(function(err) {
console.log(err);
return Promise.reject(err);
});
};
module.exports = BlockService;

View File

@ -0,0 +1,51 @@
/**
* @file service/transaction.js
*
* This implementation stores a set of indexes so quick queries are possible.
* An "index" for the purposes of this explanation is a structure for a set
* of keys to the LevelDB key/value store so that both the key and values can be
* sequentially accesed, which is a fast operation on LevelDB.
*
* Map of transaction to related addresses:
* * address-<address>-<ts>-<transaction>-<outputIndex> -> true (unspent)
* -> <spendTxId:inputIndex>
* * output-<transaction>-<outputIndex> -> { script, amount, spendTxId, spendIndex }
* * input-<transaction>-<inputIndex> -> { script, amount, prevTxId, outputIndex, output }
*
*/
'use strict';
var RPC = require('bitcoind-rpc');
var LevelUp = require('levelup');
var Promise = require('bluebird');
var bitcore = require('bitcore');
var config = require('config');
var _ = bitcore.deps._;
var $ = bitcore.util.preconditions;
function TransactionService (opts) {
opts = _.extend({}, opts);
this.database = opts.database || Promise.promisifyAll(new LevelUp(config.get('LevelUp')));
this.rpc = opts.rpc || Promise.promisifyAll(new RPC(config.get('RPC')));
}
TransactionService.transactionRPCtoBitcore = function(rpcResponse) {
if (rpcResponse.error) {
throw new bitcore.Error(rpcResponse.error);
}
return rpcResponse.result;
};
TransactionService.prototype.getTransaction = function(transactionId) {
var self = this;
return Promise.try(function() {
return self.rpc.getRawTransactionAsync(transactionId);
}).then(function(rawTransaction) {
return new bitcore.Transaction(TransactionService.transactionRPCtoBitcore(rawTransaction));
});
};
module.exports = TransactionService;

View File

@ -70,9 +70,11 @@
"morgan": "^1.5.1",
"preconditions": "^1.0.7",
"request": "^2.48.0",
"sequelize": "^2.0.3",
"socket.io": "1.0.6",
"socket.io-client": "1.0.6",
"soop": "=0.1.5",
"sqlite3": "^3.0.5",
"winston": "*",
"xmlhttprequest": "~1.6.0"
},

79
test/services/block.js Normal file
View File

@ -0,0 +1,79 @@
'use strict';
var sinon = require('sinon');
var should = require('chai').should();
var bitcore = require('bitcore');
var BlockService = require('../../lib/services/block');
describe('BlockService', function() {
it('initializes correctly', function() {
var database = 'database';
var rpc = 'rpc';
var txService = 'txService';
var blockService = new BlockService({
database: database,
rpc: 'rpc',
transactionService: 'txService'
});
blockService.should.exist();
blockService.database.should.equal(database);
blockService.rpc.should.equal(rpc);
blockService.transactionService.should.equal(txService);
});
describe('getBlock', function() {
var mockRpc, transactionMock, database, blockService;
beforeEach(function() {
database = sinon.mock();
mockRpc = sinon.mock();
transactionMock = sinon.mock();
mockRpc.getBlockAsync = function(block) {
return Promise.resolve({
result: {
hash: '000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd',
confirmations: 347064,
size: 215,
height: 2,
version: 1,
merkleRoot: '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5',
tx: [ '9b0fc92260312ce44e74ef369f5c66bbb85848f2eddd5a7a1cde251e54ccfdd5' ],
time: 1231469744,
nonce: 1639830024,
bits: '1d00ffff',
previousblockhash: '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048'
}
});
};
transactionMock.getTransaction = function(txId) {
return Promise.resolve(
'01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000'
);
};
blockService = new BlockService({
rpc: mockRpc,
transactionService: transactionMock,
database: database
});
});
it('retrieves correctly a block, uses RPC', function(callback) {
var hash = '000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd';
blockService.getBlock(hash).then(function(block) {
block.hash.should.equal(hash);
callback();
});
});
});
});

View File

@ -0,0 +1,53 @@
'use strict';
var sinon = require('sinon');
var should = require('chai').should();
var Sequelize = require('sequelize');
var bitcore = require('bitcore');
var TransactionService = require('../../lib/services/transaction');
describe('TransactionService', function() {
var service = new TransactionService();
var schema = sinon.stub();
var rawTransaction = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000';
var transactionId = '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098';
schema.Transaction = {};
schema.Transaction.find = sinon.stub();
var transactionResult = sinon.stub();
transactionResult.getDataValue = function() { return rawTransaction; };
it('initializes correctly', function() {
(new TransactionService()).should.exist();
});
describe('get', function() {
it('allows the user to fetch a transaction using its hash', function(callback) {
schema.Transaction.find.onFirstCall().returns({
then: function(f) {
return {
then: function(g) {
return g(f(transactionResult));
}
};
}
});
service.getTransaction(schema, transactionId).then(function(transaction) {
transaction.should.be.an.instanceof(bitcore.Transaction);
transaction.toString().should.equal(rawTransaction);
callback();
});
});
it('fails on a non-string argument', function() {
(function() {
return service.getTransaction();
}).should.throw(bitcore.errors.InvalidArgument);
});
});
});