commit
01106133c6
@ -3,3 +3,10 @@ BitcoreNode:
|
||||
network: livenet
|
||||
host: localhost
|
||||
port: 8333
|
||||
LevelUp: ./db
|
||||
RPC:
|
||||
username: username
|
||||
password: password
|
||||
protocol: http
|
||||
host: 127.0.0.1
|
||||
port: 8332
|
||||
|
||||
@ -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
0
lib/indexes.js
Normal file
112
lib/services/block.js
Normal file
112
lib/services/block.js
Normal file
@ -0,0 +1,112 @@
|
||||
'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 BitcoreNode = require('../../');
|
||||
|
||||
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
|
||||
});
|
||||
};
|
||||
|
||||
var blockNotFound = function(err) {
|
||||
console.log(err);
|
||||
return Promise.reject(new BitcoreNode.errors.Blocks.NotFound());
|
||||
};
|
||||
|
||||
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 BlockService.blockRPCtoBitcore(blockData);
|
||||
|
||||
}).catch(blockNotFound);
|
||||
};
|
||||
|
||||
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(blockNotFound);
|
||||
};
|
||||
|
||||
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(blockNotFound);
|
||||
};
|
||||
|
||||
module.exports = BlockService;
|
||||
51
lib/services/transaction.js
Normal file
51
lib/services/transaction.js
Normal 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 new bitcore.Transaction(rpcResponse.result);
|
||||
};
|
||||
|
||||
TransactionService.prototype.getTransaction = function(transactionId) {
|
||||
|
||||
var self = this;
|
||||
|
||||
return Promise.try(function() {
|
||||
return self.rpc.getRawTransactionAsync(transactionId);
|
||||
}).then(function(rawTransaction) {
|
||||
return TransactionService.transactionRPCtoBitcore(rawTransaction);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = TransactionService;
|
||||
23
package.json
23
package.json
@ -45,43 +45,30 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "0.9.0",
|
||||
"bignum": "*",
|
||||
"bitcoind-rpc": "^0.2.1",
|
||||
"bitcore": "bitpay/bitcore",
|
||||
"bitcore-p2p": "bitpay/bitcore-p2p",
|
||||
"bluebird": "^2.9.12",
|
||||
"body-parser": "^1.12.0",
|
||||
"bufferput": "bitpay/node-bufferput",
|
||||
"buffertools": "*",
|
||||
"commander": "^2.3.0",
|
||||
"compression": "^1.4.1",
|
||||
"config": "^1.12.0",
|
||||
"cors": "^2.5.3",
|
||||
"cron": "^1.0.4",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"express": "4.11.1",
|
||||
"glob": "*",
|
||||
"js-yaml": "^3.2.7",
|
||||
"leveldown": "~0.10.0",
|
||||
"levelup": "~0.19.0",
|
||||
"lodash": "^2.4.1",
|
||||
"microtime": "^0.6.0",
|
||||
"mkdirp": "^0.5.0",
|
||||
"moment": "~2.5.0",
|
||||
"morgan": "^1.5.1",
|
||||
"preconditions": "^1.0.7",
|
||||
"request": "^2.48.0",
|
||||
"socket.io": "1.0.6",
|
||||
"socket.io-client": "1.0.6",
|
||||
"soop": "=0.1.5",
|
||||
"winston": "*",
|
||||
"xmlhttprequest": "~1.6.0"
|
||||
"winston": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bitcore-build": "bitpay/bitcore-build",
|
||||
"chai": "*",
|
||||
"chai": "^2.1.1",
|
||||
"gulp": "^3.8.10",
|
||||
"should": "^2.1.1",
|
||||
"sinon": "^1.10.3",
|
||||
"should": "^5.1.0",
|
||||
"sinon": "^1.13.0",
|
||||
"supertest": "^0.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
80
test/services/block.js
Normal file
80
test/services/block.js
Normal file
@ -0,0 +1,80 @@
|
||||
'use strict';
|
||||
|
||||
var sinon = require('sinon');
|
||||
var should = require('chai').should();
|
||||
var Promise = require('bluebird');
|
||||
|
||||
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'
|
||||
});
|
||||
should.exist(blockService);
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
52
test/services/transaction.js
Normal file
52
test/services/transaction.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
var sinon = require('sinon');
|
||||
var should = require('chai').should();
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
|
||||
var TransactionService = require('../../lib/services/transaction');
|
||||
|
||||
describe('TransactionService', function() {
|
||||
|
||||
var rawTransaction = '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000';
|
||||
var transactionId = '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098';
|
||||
|
||||
it('initializes correctly', function() {
|
||||
var database = 'mock';
|
||||
var rpc = 'mock';
|
||||
var service = new TransactionService({
|
||||
database: database,
|
||||
rpc: rpc
|
||||
});
|
||||
should.exist(service);
|
||||
});
|
||||
|
||||
describe('get', function() {
|
||||
|
||||
var database, rpc, service;
|
||||
|
||||
beforeEach(function() {
|
||||
database = sinon.mock();
|
||||
rpc = sinon.mock();
|
||||
rpc.getRawTransactionAsync = function(transaction) {
|
||||
return Promise.resolve({
|
||||
result: rawTransaction
|
||||
});
|
||||
};
|
||||
service = new TransactionService({
|
||||
rpc: rpc,
|
||||
database: database
|
||||
});
|
||||
});
|
||||
|
||||
it('allows the user to fetch a transaction using its hash', function(callback) {
|
||||
|
||||
service.getTransaction(transactionId).then(function(transaction) {
|
||||
transaction.hash.should.equal(transactionId);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user