wip
This commit is contained in:
parent
b3ed843057
commit
5a6d8760b7
2
.gitignore
vendored
2
.gitignore
vendored
@ -27,6 +27,6 @@ bin/SHA256SUMS.asc
|
|||||||
regtest/data/node1/regtest
|
regtest/data/node1/regtest
|
||||||
regtest/data/node2/regtest
|
regtest/data/node2/regtest
|
||||||
regtest/data/node3/regtest
|
regtest/data/node3/regtest
|
||||||
bitcore-node.json
|
bitcore-node.json*
|
||||||
*.bak
|
*.bak
|
||||||
*.orig
|
*.orig
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var assert = require('assert');
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoreNode = require('../');
|
|
||||||
var db = require('../lib/services/db');
|
|
||||||
var config = {
|
|
||||||
"network": "livenet",
|
|
||||||
"port": 3001,
|
|
||||||
"datadir": "/Users/chrisk/.bwdb/",
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"name": "bitcoind",
|
|
||||||
"config": {
|
|
||||||
"connect": [
|
|
||||||
{
|
|
||||||
"rpcport": 8332,
|
|
||||||
"rpcuser": "bitcoin",
|
|
||||||
"rpcpassword": "local321",
|
|
||||||
"zmqpubrawtx": "tcp://127.0.0.1:28332"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"module": require('../lib/services/bitcoind')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "db",
|
|
||||||
"config": {},
|
|
||||||
"module": db
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "transaction",
|
|
||||||
"config": {},
|
|
||||||
"module": require('../lib/services/transaction')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "address",
|
|
||||||
"config": {},
|
|
||||||
"module": require('../lib/services/address')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "timestamp",
|
|
||||||
"config": {},
|
|
||||||
"module": require('../lib/services/timestamp')
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"servicesConfig": {
|
|
||||||
"bitcoind": {
|
|
||||||
"connect": [
|
|
||||||
{
|
|
||||||
"rpcport": 8332,
|
|
||||||
"rpcuser": "bitcoin",
|
|
||||||
"rpcpassword": "local321",
|
|
||||||
"zmqpubrawtx": "tcp://127.0.0.1:28332"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"path": "/Users/chrisk/source/zzbitcore_node/bitcore-node.json"
|
|
||||||
}
|
|
||||||
db.prototype.sync = function(){};
|
|
||||||
var node = new BitcoreNode.Node(config);
|
|
||||||
node.start(function(err) {
|
|
||||||
if(err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var addresses = [ '1MfDRRVVKXUe5KNVZzu8CBzUZDHTTYZM94' ];
|
|
||||||
async.series([function(next) {
|
|
||||||
node.services.address.getUnspentOutputs(addresses, false, function(err, results) {
|
|
||||||
if(err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
console.log(results);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, function(next) {
|
|
||||||
node.services.address.getAddressHistory(addresses, false, function(err, results) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
console.log(results);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}], function(err) {
|
|
||||||
node.stop(function(err) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,485 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// To run the tests: $ mocha -R spec regtest/bitcoind.js
|
|
||||||
|
|
||||||
var path = require('path');
|
|
||||||
var index = require('..');
|
|
||||||
var log = index.log;
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BN = bitcore.crypto.BN;
|
|
||||||
var async = require('async');
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var bitcoind;
|
|
||||||
|
|
||||||
/* jshint unused: false */
|
|
||||||
var should = chai.should();
|
|
||||||
var assert = chai.assert;
|
|
||||||
var sinon = require('sinon');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var transactionData = [];
|
|
||||||
var blockHashes = [];
|
|
||||||
var utxos;
|
|
||||||
var client;
|
|
||||||
var coinbasePrivateKey;
|
|
||||||
var privateKey = bitcore.PrivateKey();
|
|
||||||
var destKey = bitcore.PrivateKey();
|
|
||||||
|
|
||||||
describe('Bitcoind Functionality', function() {
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
// Add the regtest network
|
|
||||||
bitcore.Networks.enableRegtest();
|
|
||||||
var regtestNetwork = bitcore.Networks.get('regtest');
|
|
||||||
|
|
||||||
var datadir = __dirname + '/data';
|
|
||||||
|
|
||||||
rimraf(datadir + '/regtest', function(err) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitcoind = require('../').services.Bitcoin({
|
|
||||||
spawn: {
|
|
||||||
datadir: datadir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcoind')
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
network: regtestNetwork,
|
|
||||||
getNetworkName: function() {
|
|
||||||
return 'regtest';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bitcoind.on('error', function(err) {
|
|
||||||
log.error('error="%s"', err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info('Waiting for Bitcoin Core to initialize...');
|
|
||||||
|
|
||||||
bitcoind.start(function() {
|
|
||||||
log.info('Bitcoind started');
|
|
||||||
|
|
||||||
client = new BitcoinRPC({
|
|
||||||
protocol: 'http',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30331,
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
});
|
|
||||||
|
|
||||||
log.info('Generating 100 blocks...');
|
|
||||||
|
|
||||||
// Generate enough blocks so that the initial coinbase transactions
|
|
||||||
// can be spent.
|
|
||||||
|
|
||||||
setImmediate(function() {
|
|
||||||
client.generate(150, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
blockHashes = response.result;
|
|
||||||
|
|
||||||
log.info('Preparing test data...');
|
|
||||||
|
|
||||||
// Get all of the unspent outputs
|
|
||||||
client.listUnspent(0, 150, function(err, response) {
|
|
||||||
utxos = response.result;
|
|
||||||
|
|
||||||
async.mapSeries(utxos, function(utxo, next) {
|
|
||||||
async.series([
|
|
||||||
function(finished) {
|
|
||||||
// Load all of the transactions for later testing
|
|
||||||
client.getTransaction(utxo.txid, function(err, txresponse) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// add to the list of transactions for testing later
|
|
||||||
transactionData.push(txresponse.result.hex);
|
|
||||||
finished();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(finished) {
|
|
||||||
// Get the private key for each utxo
|
|
||||||
client.dumpPrivKey(utxo.address, function(err, privresponse) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
utxo.privateKeyWIF = privresponse.result;
|
|
||||||
finished();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
], next);
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
bitcoind.node.stopping = true;
|
|
||||||
bitcoind.stop(function(err, result) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks by hash', function() {
|
|
||||||
|
|
||||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
bitcoind.getBlock(blockHashes[i], function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
block.hash.should.equal(blockHashes[i]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks as buffers', function() {
|
|
||||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
bitcoind.getRawBlock(blockHashes[i], function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
(block instanceof Buffer).should.equal(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get errors as error instances', function() {
|
|
||||||
it('will wrap an rpc into a javascript error', function(done) {
|
|
||||||
bitcoind.client.getBlock(1000000000, function(err, response) {
|
|
||||||
var error = bitcoind._wrapRPCError(err);
|
|
||||||
(error instanceof Error).should.equal(true);
|
|
||||||
error.message.should.equal(err.message);
|
|
||||||
error.code.should.equal(err.code);
|
|
||||||
should.exist(error.stack);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks by height', function() {
|
|
||||||
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
// add the genesis block
|
|
||||||
var height = i + 1;
|
|
||||||
bitcoind.getBlock(i + 1, function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
block.hash.should.equal(blockHashes[i]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will get error with number greater than tip', function(done) {
|
|
||||||
bitcoind.getBlock(1000000000, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
err.code.should.equal(-8);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get transactions by hash', function() {
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('for tx ' + i, function(done) {
|
|
||||||
var txhex = transactionData[i];
|
|
||||||
var tx = new bitcore.Transaction();
|
|
||||||
tx.fromString(txhex);
|
|
||||||
bitcoind.getTransaction(tx.hash, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will return error if the transaction does not exist', function(done) {
|
|
||||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
|
||||||
bitcoind.getTransaction(txid, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get transactions as buffers', function() {
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('for tx ' + i, function(done) {
|
|
||||||
var txhex = transactionData[i];
|
|
||||||
var tx = new bitcore.Transaction();
|
|
||||||
tx.fromString(txhex);
|
|
||||||
bitcoind.getRawTransaction(tx.hash, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
response.should.be.instanceOf(Buffer);
|
|
||||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will return error if the transaction does not exist', function(done) {
|
|
||||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
|
||||||
bitcoind.getRawTransaction(txid, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get block header', function() {
|
|
||||||
var expectedWork = new BN(6);
|
|
||||||
[1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generate block ' + i, function(done) {
|
|
||||||
bitcoind.getBlockHeader(blockHashes[i], function(err, blockIndex) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(blockIndex);
|
|
||||||
should.exist(blockIndex.chainWork);
|
|
||||||
var work = new BN(blockIndex.chainWork, 'hex');
|
|
||||||
work.toString(16).should.equal(expectedWork.toString(16));
|
|
||||||
expectedWork = expectedWork.add(new BN(2));
|
|
||||||
should.exist(blockIndex.prevHash);
|
|
||||||
blockIndex.hash.should.equal(blockHashes[i]);
|
|
||||||
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
|
||||||
blockIndex.height.should.equal(i + 1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get null prevHash for the genesis block', function(done) {
|
|
||||||
bitcoind.getBlockHeader(0, function(err, header) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(header);
|
|
||||||
should.equal(header.prevHash, undefined);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get error for block not found', function(done) {
|
|
||||||
bitcoind.getBlockHeader('notahash', function(err, header) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get block index by height', function() {
|
|
||||||
var expectedWork = new BN(6);
|
|
||||||
[2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generate block ' + i, function() {
|
|
||||||
bitcoind.getBlockHeader(i, function(err, header) {
|
|
||||||
should.exist(header);
|
|
||||||
should.exist(header.chainWork);
|
|
||||||
var work = new BN(header.chainWork, 'hex');
|
|
||||||
work.toString(16).should.equal(expectedWork.toString(16));
|
|
||||||
expectedWork = expectedWork.add(new BN(2));
|
|
||||||
should.exist(header.prevHash);
|
|
||||||
header.hash.should.equal(blockHashes[i - 1]);
|
|
||||||
header.prevHash.should.equal(blockHashes[i - 2]);
|
|
||||||
header.height.should.equal(i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get error with number greater than tip', function(done) {
|
|
||||||
bitcoind.getBlockHeader(100000, function(err, header) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('send transaction functionality', function() {
|
|
||||||
|
|
||||||
it('will not error and return the transaction hash', function(done) {
|
|
||||||
|
|
||||||
// create and sign the transaction
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[0]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[0].amount * 1e8 - 1000);
|
|
||||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[0].privateKeyWIF));
|
|
||||||
|
|
||||||
// test sending the transaction
|
|
||||||
bitcoind.sendTransaction(tx.serialize(), function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
hash.should.equal(tx.hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will throw an error if an unsigned transaction is sent', function(done) {
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[1]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000);
|
|
||||||
bitcoind.sendTransaction(tx.uncheckedSerialize(), function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
(err instanceof Error).should.equal(true);
|
|
||||||
should.not.exist(hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will throw an error for unexpected types (tx decode failed)', function(done) {
|
|
||||||
var garbage = new Buffer('abcdef', 'hex');
|
|
||||||
bitcoind.sendTransaction(garbage, function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
should.not.exist(hash);
|
|
||||||
var num = 23;
|
|
||||||
bitcoind.sendTransaction(num, function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
(err instanceof Error).should.equal(true);
|
|
||||||
should.not.exist(hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will emit "tx" events', function(done) {
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[2]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[2].amount * 1e8 - 1000);
|
|
||||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[2].privateKeyWIF));
|
|
||||||
|
|
||||||
var serialized = tx.serialize();
|
|
||||||
|
|
||||||
bitcoind.once('tx', function(buffer) {
|
|
||||||
buffer.toString('hex').should.equal(serialized);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
bitcoind.sendTransaction(serialized, function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(hash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fee estimation', function() {
|
|
||||||
it('will estimate fees', function(done) {
|
|
||||||
bitcoind.estimateFee(1, function(err, fees) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
fees.should.equal(-1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('tip updates', function() {
|
|
||||||
it('will get an event when the tip is new', function(done) {
|
|
||||||
this.timeout(4000);
|
|
||||||
bitcoind.on('tip', function(height) {
|
|
||||||
if (height === 151) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.generate(1, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get detailed transaction', function() {
|
|
||||||
it('should include details for coinbase tx', function(done) {
|
|
||||||
bitcoind.getDetailedTransaction(utxos[0].txid, function(err, tx) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(tx.height);
|
|
||||||
tx.height.should.be.a('number');
|
|
||||||
should.exist(tx.blockTimestamp);
|
|
||||||
should.exist(tx.blockHash);
|
|
||||||
tx.coinbase.should.equal(true);
|
|
||||||
tx.version.should.equal(1);
|
|
||||||
tx.hex.should.be.a('string');
|
|
||||||
tx.locktime.should.equal(0);
|
|
||||||
tx.feeSatoshis.should.equal(0);
|
|
||||||
tx.outputSatoshis.should.equal(50 * 1e8);
|
|
||||||
tx.inputSatoshis.should.equal(0);
|
|
||||||
tx.inputs.length.should.equal(1);
|
|
||||||
tx.outputs.length.should.equal(1);
|
|
||||||
should.equal(tx.inputs[0].prevTxId, null);
|
|
||||||
should.equal(tx.inputs[0].outputIndex, null);
|
|
||||||
tx.inputs[0].script.should.be.a('string');
|
|
||||||
should.equal(tx.inputs[0].scriptAsm, null);
|
|
||||||
should.equal(tx.inputs[0].address, null);
|
|
||||||
should.equal(tx.inputs[0].satoshis, null);
|
|
||||||
tx.outputs[0].satoshis.should.equal(50 * 1e8);
|
|
||||||
tx.outputs[0].script.should.be.a('string');
|
|
||||||
tx.outputs[0].scriptAsm.should.be.a('string');
|
|
||||||
tx.outputs[0].spentTxId.should.be.a('string');
|
|
||||||
tx.outputs[0].spentIndex.should.equal(0);
|
|
||||||
tx.outputs[0].spentHeight.should.be.a('number');
|
|
||||||
tx.outputs[0].address.should.be.a('string');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#getInfo', function() {
|
|
||||||
it('will get information', function(done) {
|
|
||||||
bitcoind.getInfo(function(err, info) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
info.network.should.equal('regtest');
|
|
||||||
should.exist(info);
|
|
||||||
should.exist(info.version);
|
|
||||||
should.exist(info.blocks);
|
|
||||||
should.exist(info.timeOffset);
|
|
||||||
should.exist(info.connections);
|
|
||||||
should.exist(info.difficulty);
|
|
||||||
should.exist(info.testnet);
|
|
||||||
should.exist(info.relayFee);
|
|
||||||
should.exist(info.errors);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
231
regtest/block.js
231
regtest/block.js
@ -1,231 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var expect = chai.expect;
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var path = require('path');
|
|
||||||
var Utils = require('./utils');
|
|
||||||
var constants = require('../lib/constants');
|
|
||||||
var zmq = require('zmq');
|
|
||||||
|
|
||||||
var debug = true;
|
|
||||||
var extraDebug = true;
|
|
||||||
var bitcoreDataDir = '/tmp/testtmpfs/bitcore';
|
|
||||||
var bitcoinDataDir = '/tmp/testtmpfs/bitcoin';
|
|
||||||
|
|
||||||
var rpcConfig = {
|
|
||||||
protocol: 'http',
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '58332',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcoin = {
|
|
||||||
args: {
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
listen: 1,
|
|
||||||
regtest: 1,
|
|
||||||
server: 1,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
rpcport: rpcConfig.port
|
|
||||||
},
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: [
|
|
||||||
'p2p',
|
|
||||||
'db',
|
|
||||||
'web',
|
|
||||||
'block',
|
|
||||||
'timestamp',
|
|
||||||
'block-test'
|
|
||||||
],
|
|
||||||
servicesConfig: {
|
|
||||||
p2p: {
|
|
||||||
peers: [
|
|
||||||
{
|
|
||||||
ip: { v4: '127.0.0.1' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'block-test': {
|
|
||||||
requirePath: path.resolve(__dirname + '/test_bus.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoin: bitcoin,
|
|
||||||
bitcoinDataDir: bitcoinDataDir,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
rpc: new BitcoinRPC(rpcConfig),
|
|
||||||
blockHeight: 0,
|
|
||||||
initialHeight: 150,
|
|
||||||
path: '/test/info',
|
|
||||||
errorFilter: function(req, res) {
|
|
||||||
try {
|
|
||||||
var info = JSON.parse(res);
|
|
||||||
if (info.result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var utils = new Utils(opts);
|
|
||||||
|
|
||||||
var subSocket;
|
|
||||||
var blocks = [];
|
|
||||||
|
|
||||||
function processMessages(topic, message) {
|
|
||||||
var topicStr = topic.toString();
|
|
||||||
if (topicStr === 'block/block') {
|
|
||||||
return blocks.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupZmqSubscriber(callback) {
|
|
||||||
|
|
||||||
subSocket = zmq.socket('sub');
|
|
||||||
subSocket.on('connect', function(fd, endPoint) {
|
|
||||||
if (debug) {
|
|
||||||
console.log('ZMQ connected to:', endPoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
subSocket.on('disconnect', function(fd, endPoint) {
|
|
||||||
if (debug) {
|
|
||||||
console.log('ZMQ disconnect:', endPoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
subSocket.monitor(100, 0);
|
|
||||||
subSocket.connect('tcp://127.0.0.1:38332');
|
|
||||||
subSocket.subscribe('block');
|
|
||||||
subSocket.on('message', processMessages);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Block Operations', function() {
|
|
||||||
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
describe('Sync Block Headers', function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
utils.cleanup(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils),
|
|
||||||
utils.waitForBitcoinReady.bind(utils),
|
|
||||||
utils.startBitcoreNode.bind(utils),
|
|
||||||
utils.waitForBitcoreNode.bind(utils),
|
|
||||||
setupZmqSubscriber
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.only('should be able to get historical blocks from the network', function(done) {
|
|
||||||
var filter = { startHash: constants.BITCOIN_GENESIS_HASH.regtest };
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/p2p/blocks?filter=' + JSON.stringify(filter),
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
expect(blocks.length).to.equal(150);
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sync block hashes as keys and heights as values', function(done) {
|
|
||||||
|
|
||||||
//async.timesLimit(opts.initialHeight, 12, function(n, next) {
|
|
||||||
// utils.queryBitcoreNode(Object.assign({
|
|
||||||
// path: '/test/block/hash/' + n
|
|
||||||
// }, bitcore.httpOpts), function(err, res) {
|
|
||||||
|
|
||||||
// if(err) {
|
|
||||||
// return done(err);
|
|
||||||
// }
|
|
||||||
// res = JSON.parse(res);
|
|
||||||
// expect(res.height).to.equal(n);
|
|
||||||
// expect(res.hash.length).to.equal(64);
|
|
||||||
// next(null, res.hash);
|
|
||||||
// });
|
|
||||||
//}, function(err, hashes) {
|
|
||||||
|
|
||||||
// if(err) {
|
|
||||||
// return done(err);
|
|
||||||
// }
|
|
||||||
// self.hashes = hashes;
|
|
||||||
// done();
|
|
||||||
|
|
||||||
//});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sync block heights as keys and hashes as values', function(done) {
|
|
||||||
async.timesLimit(opts.initialHeight, 12, function(n, next) {
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/block/height/' + self.hashes[n]
|
|
||||||
}, bitcore.httpOpts), function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
res = JSON.parse(res);
|
|
||||||
expect(res.height).to.equal(n);
|
|
||||||
expect(res.hash).to.equal(self.hashes[n]);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,183 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var path = require('path');
|
|
||||||
var async = require('async');
|
|
||||||
var spawn = require('child_process').spawn;
|
|
||||||
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var chai = require('chai');
|
|
||||||
var should = chai.should();
|
|
||||||
|
|
||||||
var index = require('..');
|
|
||||||
var log = index.log;
|
|
||||||
log.debug = function() {};
|
|
||||||
var BitcoreNode = index.Node;
|
|
||||||
var BitcoinService = index.services.Bitcoin;
|
|
||||||
|
|
||||||
describe('Bitcoin Cluster', function() {
|
|
||||||
var node;
|
|
||||||
var daemons = [];
|
|
||||||
var execPath = path.resolve(__dirname, '../bin/bitcoind');
|
|
||||||
var nodesConf = [
|
|
||||||
{
|
|
||||||
datadir: path.resolve(__dirname, './data/node1'),
|
|
||||||
conf: path.resolve(__dirname, './data/node1/bitcoin.conf'),
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
rpcport: 30521,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30611',
|
|
||||||
zmqpubhashblock: 'tcp://127.0.0.1:30611'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
datadir: path.resolve(__dirname, './data/node2'),
|
|
||||||
conf: path.resolve(__dirname, './data/node2/bitcoin.conf'),
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
rpcport: 30522,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30622',
|
|
||||||
zmqpubhashblock: 'tcp://127.0.0.1:30622'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
datadir: path.resolve(__dirname, './data/node3'),
|
|
||||||
conf: path.resolve(__dirname, './data/node3/bitcoin.conf'),
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
rpcport: 30523,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30633',
|
|
||||||
zmqpubhashblock: 'tcp://127.0.0.1:30633'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
log.info('Starting 3 bitcoind daemons');
|
|
||||||
this.timeout(60000);
|
|
||||||
async.each(nodesConf, function(nodeConf, next) {
|
|
||||||
var opts = [
|
|
||||||
'--regtest',
|
|
||||||
'--datadir=' + nodeConf.datadir,
|
|
||||||
'--conf=' + nodeConf.conf
|
|
||||||
];
|
|
||||||
|
|
||||||
rimraf(path.resolve(nodeConf.datadir, './regtest'), function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var process = spawn(execPath, opts, {stdio: 'inherit'});
|
|
||||||
|
|
||||||
var client = new BitcoinRPC({
|
|
||||||
protocol: 'http',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: nodeConf.rpcport,
|
|
||||||
user: nodeConf.rpcuser,
|
|
||||||
pass: nodeConf.rpcpassword
|
|
||||||
});
|
|
||||||
|
|
||||||
daemons.push(process);
|
|
||||||
|
|
||||||
async.retry({times: 10, interval: 5000}, function(ready) {
|
|
||||||
client.getInfo(ready);
|
|
||||||
}, next);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
this.timeout(10000);
|
|
||||||
setTimeout(function() {
|
|
||||||
async.each(daemons, function(process, next) {
|
|
||||||
process.once('exit', next);
|
|
||||||
process.kill('SIGINT');
|
|
||||||
}, done);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('step 1: will connect to three bitcoind daemons', function(done) {
|
|
||||||
this.timeout(20000);
|
|
||||||
var configuration = {
|
|
||||||
network: 'regtest',
|
|
||||||
services: [
|
|
||||||
{
|
|
||||||
name: 'bitcoind',
|
|
||||||
module: BitcoinService,
|
|
||||||
config: {
|
|
||||||
connect: [
|
|
||||||
{
|
|
||||||
rpchost: '127.0.0.1',
|
|
||||||
rpcport: 30521,
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30611'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rpchost: '127.0.0.1',
|
|
||||||
rpcport: 30522,
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30622'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
rpchost: '127.0.0.1',
|
|
||||||
rpcport: 30523,
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:30633'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
var regtest = bitcore.Networks.get('regtest');
|
|
||||||
should.exist(regtest);
|
|
||||||
|
|
||||||
node = new BitcoreNode(configuration);
|
|
||||||
|
|
||||||
node.on('error', function(err) {
|
|
||||||
log.error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.on('ready', function() {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
node.start(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('step 2: receive block events', function(done) {
|
|
||||||
this.timeout(10000);
|
|
||||||
node.services.bitcoind.once('tip', function(height) {
|
|
||||||
height.should.equal(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
node.generateBlock(1, function(err, hashes) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(hashes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('step 3: get blocks', function(done) {
|
|
||||||
async.times(3, function(n, next) {
|
|
||||||
node.getBlock(1, function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
3
regtest/data/.gitignore
vendored
3
regtest/data/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
.lock
|
|
||||||
blocks
|
|
||||||
regtest
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
server=1
|
|
||||||
whitelist=127.0.0.1
|
|
||||||
txindex=1
|
|
||||||
addressindex=1
|
|
||||||
timestampindex=1
|
|
||||||
spentindex=1
|
|
||||||
zmqpubrawtx=tcp://127.0.0.1:30332
|
|
||||||
zmqpubhashblock=tcp://127.0.0.1:30332
|
|
||||||
rpcallowip=127.0.0.1
|
|
||||||
rpcport=30331
|
|
||||||
rpcuser=bitcoin
|
|
||||||
rpcpassword=local321
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICDTCCAXYCCQCsGf/7CM97gDANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJV
|
|
||||||
UzELMAkGA1UECBMCR0ExDDAKBgNVBAcTA2ZvbzEhMB8GA1UEChMYSW50ZXJuZXQg
|
|
||||||
V2lkZ2l0cyBQdHkgTHRkMB4XDTE1MDgyNjE3NTAwOFoXDTE1MDkyNTE3NTAwOFow
|
|
||||||
SzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkdBMQwwCgYDVQQHEwNmb28xITAfBgNV
|
|
||||||
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOB
|
|
||||||
jQAwgYkCgYEA7SXnPpKk+0qXTTa42jzvu/C/gdGby0arY50bE2A+epI7FlI5YqKd
|
|
||||||
hQWoNRpuehF3jH6Ij3mbeeImtTA7TaUYlgHKpn63xfJ0cRj55+6vqq09nDxf0Lm9
|
|
||||||
IpTbgllu1l+SHtSuzFBVtGuNRSqObf8gD5XCD5lWK1vXHQ6PFSnAakMCAwEAATAN
|
|
||||||
BgkqhkiG9w0BAQUFAAOBgQBNARLDgsw7NCBVkn57AEgwZptxeyvFWlGZCd0BmYIX
|
|
||||||
ZFk7T1OQDwn7GlHry2IBswI0QRi076RQ4oJq+fg2O3XdFvEYV0cyypW7AxrnYTHP
|
|
||||||
m1h2xr6Y5vhxFKP8DxpAxST27DHbR18YvTD+IGtp2UjLj646587N0MWxt8vmaU3c
|
|
||||||
og==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIICXwIBAAKBgQDtJec+kqT7SpdNNrjaPO+78L+B0ZvLRqtjnRsTYD56kjsWUjli
|
|
||||||
op2FBag1Gm56EXeMfoiPeZt54ia1MDtNpRiWAcqmfrfF8nRxGPnn7q+qrT2cPF/Q
|
|
||||||
ub0ilNuCWW7WX5Ie1K7MUFW0a41FKo5t/yAPlcIPmVYrW9cdDo8VKcBqQwIDAQAB
|
|
||||||
AoGBAOzYFxyCPu2OMI/4ICQuCcwtBEa2Ph+Fo/Rn2ru+OogV9Zc0ZYWiHSnWXYkz
|
|
||||||
rbSSL1CMqvyIGoRfHgOFeSTxxxtVyRo5LayI6Ce6V04yUFua16uo6hMX5bfGKZ9d
|
|
||||||
uq/HCDmdQvgifxUFpTpoSencuxwVCSYstMqjGfpobc5nxN2RAkEA+23BWNyA2qcq
|
|
||||||
yqSplgTQO0laXox9ksr1k2mJB2HtG3GNs1kapP+Z8AVtn/Mf9Va0hgbSnqpF9fll
|
|
||||||
1xpBqfidSQJBAPF1rizAm7xP7AeRRET36YKjZCVayA/g4rp7kecrNJTTupJIuxHr
|
|
||||||
JUlOTtXEWjIVcutSM7bP7bytPv4SAaApBysCQQDEZhqnCC+bHQO/IVrbNc1W0ljG
|
|
||||||
DGY22VV1HfYND0CAtHXkx9CZXJPpusPEMs0e/uiq3P9/MzDNEFCt8vOiCvMJAkEA
|
|
||||||
65oDIKGzk/R7/wpMjetEyva5AgXpjizFrmZigCjVPp61voT/G8XQ9Q1WuRjFVXc+
|
|
||||||
UcU8tpV+iIqXG3vgYDGITwJBANb0NFFF8QsygbENtad1tw1C/hNabHk8n9hu+Z8+
|
|
||||||
OSzEMlP7SsvddPaqusGydhTUxazoG3s4kEh5WmCWKgZGKO0=
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
server=1
|
|
||||||
whitelist=127.0.0.1
|
|
||||||
txindex=1
|
|
||||||
addressindex=1
|
|
||||||
timestampindex=1
|
|
||||||
spentindex=1
|
|
||||||
addnode=127.0.0.1:30432
|
|
||||||
addnode=127.0.0.1:30433
|
|
||||||
port=30431
|
|
||||||
rpcport=30521
|
|
||||||
zmqpubrawtx=tcp://127.0.0.1:30611
|
|
||||||
zmqpubhashblock=tcp://127.0.0.1:30611
|
|
||||||
rpcallowip=127.0.0.1
|
|
||||||
rpcuser=bitcoin
|
|
||||||
rpcpassword=local321
|
|
||||||
keypool=3
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
server=1
|
|
||||||
whitelist=127.0.0.1
|
|
||||||
txindex=1
|
|
||||||
addressindex=1
|
|
||||||
timestampindex=1
|
|
||||||
spentindex=1
|
|
||||||
addnode=127.0.0.1:30431
|
|
||||||
addnode=127.0.0.1:30433
|
|
||||||
port=30432
|
|
||||||
rpcport=30522
|
|
||||||
zmqpubrawtx=tcp://127.0.0.1:30622
|
|
||||||
zmqpubhashblock=tcp://127.0.0.1:30622
|
|
||||||
rpcallowip=127.0.0.1
|
|
||||||
rpcuser=bitcoin
|
|
||||||
rpcpassword=local321
|
|
||||||
keypool=3
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
server=1
|
|
||||||
whitelist=127.0.0.1
|
|
||||||
txindex=1
|
|
||||||
addressindex=1
|
|
||||||
timestampindex=1
|
|
||||||
spentindex=1
|
|
||||||
addnode=127.0.0.1:30431
|
|
||||||
addnode=127.0.0.1:30432
|
|
||||||
port=30433
|
|
||||||
rpcport=30523
|
|
||||||
zmqpubrawtx=tcp://127.0.0.1:30633
|
|
||||||
zmqpubhashblock=tcp://127.0.0.1:30633
|
|
||||||
rpcallowip=127.0.0.1
|
|
||||||
rpcuser=bitcoin
|
|
||||||
rpcpassword=local321
|
|
||||||
keypool=3
|
|
||||||
228
regtest/db.js
228
regtest/db.js
@ -1,228 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var expect = chai.expect;
|
|
||||||
var async = require('async');
|
|
||||||
var path = require('path');
|
|
||||||
var Utils = require('./utils');
|
|
||||||
var zmq = require('zmq');
|
|
||||||
var http = require('http');
|
|
||||||
var blocks = require('../test/data/blocks.json');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var Block = bitcore.Block;
|
|
||||||
var BufferUtil = bitcore.util.buffer;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Bitcoind does not need to be started or run
|
|
||||||
*/
|
|
||||||
|
|
||||||
var debug = true;
|
|
||||||
var bitcoreDataDir = '/tmp/bitcore';
|
|
||||||
var pubSocket;
|
|
||||||
var rpcServer;
|
|
||||||
|
|
||||||
function setupFakeRpcServer() {
|
|
||||||
rpcServer = http.createServer();
|
|
||||||
rpcServer.listen(48332, '127.0.0.1');
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupFakeZmq() {
|
|
||||||
pubSocket = zmq.socket('pub');
|
|
||||||
pubSocket.bind('tcp://127.0.0.1:38332');
|
|
||||||
}
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: [
|
|
||||||
'bitcoind',
|
|
||||||
'db',
|
|
||||||
'web',
|
|
||||||
'block',
|
|
||||||
'reorg-test',
|
|
||||||
'timestamp'
|
|
||||||
],
|
|
||||||
servicesConfig: {
|
|
||||||
bitcoind: {
|
|
||||||
connect: [
|
|
||||||
{
|
|
||||||
rpcconnect: '127.0.0.1',
|
|
||||||
rpcport: 48332,
|
|
||||||
rpcuser: 'bitcoin',
|
|
||||||
rpcpassword: 'local321',
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:38332'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'reorg-test': { requirePath: path.resolve(__dirname + '/test_web.js') }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
blockHeight: 0
|
|
||||||
};
|
|
||||||
var utils = new Utils(opts);
|
|
||||||
|
|
||||||
var genesis = new Block(new Buffer(blocks.genesis, 'hex'));
|
|
||||||
var block1 = new Block(new Buffer(blocks.block1a, 'hex'));
|
|
||||||
var block2 = new Block(new Buffer(blocks.block1b, 'hex'));
|
|
||||||
var rawGenesis = blocks.genesis;
|
|
||||||
var rawBlock1 = blocks.block1a;
|
|
||||||
var rawBlock2 = blocks.block1b;
|
|
||||||
var genesisHash = genesis.hash;
|
|
||||||
var genesisHeader = {
|
|
||||||
height: 0,
|
|
||||||
hash: genesis.hash,
|
|
||||||
previousblockhash: new Array(65).join('0')
|
|
||||||
};
|
|
||||||
var block1Header = {
|
|
||||||
height: 1,
|
|
||||||
hash: block1.header.hash,
|
|
||||||
previousblockhash: BufferUtil.reverse(block1.header.prevHash).toString('hex')
|
|
||||||
};
|
|
||||||
var block2Header = {
|
|
||||||
height: 1,
|
|
||||||
hash: block2.header.hash,
|
|
||||||
previousblockhash: BufferUtil.reverse(block2.header.prevHash).toString('hex')
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function publishBlockHash(rawBlockHex, callback) {
|
|
||||||
|
|
||||||
pubSocket.send([ 'rawblock', new Buffer(rawBlockHex, 'hex') ]);
|
|
||||||
|
|
||||||
var httpOpts = utils.getHttpOpts({ path: '/info' });
|
|
||||||
|
|
||||||
// we don't know exactly when all the blockhandlers will complete after the "tip" event
|
|
||||||
// so we must wait an indeterminate time to check on the current tip
|
|
||||||
setTimeout(function() {
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(httpOpts, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var block = Block.fromString(rawBlockHex);
|
|
||||||
expect(block.hash).equal(JSON.parse(res).dbhash);
|
|
||||||
callback();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('DB Operations', function() {
|
|
||||||
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
describe('DB Reorg', function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var responses = [
|
|
||||||
genesisHash,
|
|
||||||
genesisHeader,
|
|
||||||
rawGenesis,
|
|
||||||
block1Header,
|
|
||||||
block2Header
|
|
||||||
];
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
pubSocket.close();
|
|
||||||
rpcServer.close();
|
|
||||||
bitcore.process.kill();
|
|
||||||
setTimeout(done, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
|
|
||||||
var responseCount = 0;
|
|
||||||
|
|
||||||
setupFakeRpcServer();
|
|
||||||
|
|
||||||
rpcServer.on('request', function(req, res) {
|
|
||||||
var data = '';
|
|
||||||
|
|
||||||
req.on('data', function(chunk) {
|
|
||||||
data += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('end', function() {
|
|
||||||
var body = JSON.parse(data);
|
|
||||||
if (debug) {
|
|
||||||
console.log('request', body);
|
|
||||||
}
|
|
||||||
var response = JSON.stringify({ result: responses[responseCount++], count: responseCount });
|
|
||||||
if (debug) {
|
|
||||||
console.log('response', response, 'id: ', body.id);
|
|
||||||
}
|
|
||||||
res.write(response);
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
setupFakeZmq();
|
|
||||||
|
|
||||||
utils.startBitcoreNode(function() {
|
|
||||||
utils.waitForBitcoreNode(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should reorg when needed', function(done) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
_______________________________________________________
|
|
||||||
| | | | |
|
|
||||||
| Genesis | Block 1a | Block 1b | Result |
|
|
||||||
| _______ ________ |
|
|
||||||
| | |_____| |___________________ORPHANED |
|
|
||||||
| |_______| |________| |
|
|
||||||
| | ________ ________ |
|
|
||||||
| |______________________| |____| | |
|
|
||||||
| |________| |________| |
|
|
||||||
|_______________________________________________________|
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
|
|
||||||
publishBlockHash.bind(self, rawBlock1),
|
|
||||||
publishBlockHash.bind(self, rawBlock2),
|
|
||||||
function(next) {
|
|
||||||
utils.opts.blockHeight++;
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
utils.waitForBitcoreNode.bind(utils)
|
|
||||||
|
|
||||||
], done);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
758
regtest/node.js
758
regtest/node.js
@ -1,758 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// To run the tests: $ mocha -R spec regtest/node.js
|
|
||||||
|
|
||||||
var path = require('path');
|
|
||||||
var index = require('..');
|
|
||||||
var async = require('async');
|
|
||||||
var log = index.log;
|
|
||||||
log.debug = function() {};
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var node;
|
|
||||||
|
|
||||||
var should = chai.should();
|
|
||||||
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var index = require('..');
|
|
||||||
var Transaction = bitcore.Transaction;
|
|
||||||
var BitcoreNode = index.Node;
|
|
||||||
var BitcoinService = index.services.Bitcoin;
|
|
||||||
var testWIF = 'cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG';
|
|
||||||
var testKey;
|
|
||||||
var client;
|
|
||||||
|
|
||||||
var outputForIsSpentTest1;
|
|
||||||
var unspentOutputSpentTxId;
|
|
||||||
|
|
||||||
describe('Node Functionality', function() {
|
|
||||||
|
|
||||||
var regtest;
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
this.timeout(20000);
|
|
||||||
|
|
||||||
var datadir = __dirname + '/data';
|
|
||||||
|
|
||||||
testKey = bitcore.PrivateKey(testWIF);
|
|
||||||
|
|
||||||
rimraf(datadir + '/regtest', function(err) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
var configuration = {
|
|
||||||
network: 'regtest',
|
|
||||||
services: [
|
|
||||||
{
|
|
||||||
name: 'bitcoind',
|
|
||||||
module: BitcoinService,
|
|
||||||
config: {
|
|
||||||
spawn: {
|
|
||||||
datadir: datadir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcoind')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
node = new BitcoreNode(configuration);
|
|
||||||
|
|
||||||
regtest = bitcore.Networks.get('regtest');
|
|
||||||
should.exist(regtest);
|
|
||||||
|
|
||||||
node.on('error', function(err) {
|
|
||||||
log.error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
node.start(function(err) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
client = new BitcoinRPC({
|
|
||||||
protocol: 'http',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: 30331,
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
});
|
|
||||||
|
|
||||||
var syncedHandler = function() {
|
|
||||||
if (node.services.bitcoind.height === 150) {
|
|
||||||
node.services.bitcoind.removeListener('synced', syncedHandler);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
node.services.bitcoind.on('synced', syncedHandler);
|
|
||||||
|
|
||||||
client.generate(150, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
this.timeout(20000);
|
|
||||||
node.stop(function(err, result) {
|
|
||||||
if(err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Bus Functionality', function() {
|
|
||||||
it('subscribes and unsubscribes to an event on the bus', function(done) {
|
|
||||||
var bus = node.openBus();
|
|
||||||
var blockExpected;
|
|
||||||
var blockReceived;
|
|
||||||
bus.subscribe('bitcoind/hashblock');
|
|
||||||
bus.on('bitcoind/hashblock', function(data) {
|
|
||||||
bus.unsubscribe('bitcoind/hashblock');
|
|
||||||
if (blockExpected) {
|
|
||||||
data.should.be.equal(blockExpected);
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
blockReceived = data;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.generate(1, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
if (blockReceived) {
|
|
||||||
blockReceived.should.be.equal(response.result[0]);
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
blockExpected = response.result[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Address Functionality', function() {
|
|
||||||
var address;
|
|
||||||
var unspentOutput;
|
|
||||||
before(function(done) {
|
|
||||||
this.timeout(10000);
|
|
||||||
address = testKey.toAddress(regtest).toString();
|
|
||||||
var startHeight = node.services.bitcoind.height;
|
|
||||||
node.services.bitcoind.on('tip', function(height) {
|
|
||||||
if (height === startHeight + 3) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
client.generate(3, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should be able to get the balance of the test address', function(done) {
|
|
||||||
node.getAddressBalance(address, false, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
data.balance.should.equal(10 * 1e8);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('can get unspent outputs for address', function(done) {
|
|
||||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.length.should.equal(1);
|
|
||||||
unspentOutput = outputForIsSpentTest1 = results[0];
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('correctly give the history for the address', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 0,
|
|
||||||
to: 10,
|
|
||||||
queryMempool: false
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var items = results.items;
|
|
||||||
items.length.should.equal(1);
|
|
||||||
var info = items[0];
|
|
||||||
should.exist(info.addresses[address]);
|
|
||||||
info.addresses[address].outputIndexes.length.should.equal(1);
|
|
||||||
info.addresses[address].outputIndexes[0].should.be.within(0, 1);
|
|
||||||
info.addresses[address].inputIndexes.should.deep.equal([]);
|
|
||||||
info.satoshis.should.equal(10 * 1e8);
|
|
||||||
info.confirmations.should.equal(3);
|
|
||||||
info.tx.blockTimestamp.should.be.a('number');
|
|
||||||
info.tx.feeSatoshis.should.be.within(950, 4000);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('correctly give the summary for the address', function(done) {
|
|
||||||
var options = {
|
|
||||||
queryMempool: false
|
|
||||||
};
|
|
||||||
node.getAddressSummary(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalReceived.should.equal(1000000000);
|
|
||||||
results.totalSpent.should.equal(0);
|
|
||||||
results.balance.should.equal(1000000000);
|
|
||||||
should.not.exist(results.unconfirmedBalance);
|
|
||||||
results.appearances.should.equal(1);
|
|
||||||
should.not.exist(results.unconfirmedAppearances);
|
|
||||||
results.txids.length.should.equal(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('History', function() {
|
|
||||||
|
|
||||||
this.timeout(20000);
|
|
||||||
|
|
||||||
var testKey2;
|
|
||||||
var address2;
|
|
||||||
var testKey3;
|
|
||||||
var address3;
|
|
||||||
var testKey4;
|
|
||||||
var address4;
|
|
||||||
var testKey5;
|
|
||||||
var address5;
|
|
||||||
var testKey6;
|
|
||||||
var address6;
|
|
||||||
var tx2Amount;
|
|
||||||
var tx2Hash;
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
/* jshint maxstatements: 50 */
|
|
||||||
|
|
||||||
// Finished once all blocks have been mined
|
|
||||||
var startHeight = node.services.bitcoind.height;
|
|
||||||
node.services.bitcoind.on('tip', function(height) {
|
|
||||||
if (height === startHeight + 5) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
testKey2 = bitcore.PrivateKey.fromWIF('cNfF4jXiLHQnFRsxaJyr2YSGcmtNYvxQYSakNhuDGxpkSzAwn95x');
|
|
||||||
address2 = testKey2.toAddress(regtest).toString();
|
|
||||||
|
|
||||||
testKey3 = bitcore.PrivateKey.fromWIF('cVTYQbaFNetiZcvxzXcVMin89uMLC43pEBMy2etgZHbPPxH5obYt');
|
|
||||||
address3 = testKey3.toAddress(regtest).toString();
|
|
||||||
|
|
||||||
testKey4 = bitcore.PrivateKey.fromWIF('cPNQmfE31H2oCUFqaHpfSqjDibkt7XoT2vydLJLDHNTvcddCesGw');
|
|
||||||
address4 = testKey4.toAddress(regtest).toString();
|
|
||||||
|
|
||||||
testKey5 = bitcore.PrivateKey.fromWIF('cVrzm9gCmnzwEVMGeCxY6xLVPdG3XWW97kwkFH3H3v722nb99QBF');
|
|
||||||
address5 = testKey5.toAddress(regtest).toString();
|
|
||||||
|
|
||||||
testKey6 = bitcore.PrivateKey.fromWIF('cPfMesNR2gsQEK69a6xe7qE44CZEZavgMUak5hQ74XDgsRmmGBYF');
|
|
||||||
address6 = testKey6.toAddress(regtest).toString();
|
|
||||||
|
|
||||||
var tx = new Transaction();
|
|
||||||
tx.from(unspentOutput);
|
|
||||||
tx.to(address, 1 * 1e8);
|
|
||||||
tx.to(address, 2 * 1e8);
|
|
||||||
tx.to(address, 0.5 * 1e8);
|
|
||||||
tx.to(address, 3 * 1e8);
|
|
||||||
tx.fee(10000);
|
|
||||||
tx.change(address);
|
|
||||||
tx.sign(testKey);
|
|
||||||
|
|
||||||
unspentOutputSpentTxId = tx.id;
|
|
||||||
|
|
||||||
function mineBlock(next) {
|
|
||||||
client.generate(1, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(response);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
node.sendTransaction(tx.serialize(), function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.generate(1, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(response);
|
|
||||||
|
|
||||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
|
||||||
/* jshint maxstatements: 50 */
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.length.should.equal(5);
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
function(next) {
|
|
||||||
var tx2 = new Transaction();
|
|
||||||
tx2Amount = results[0].satoshis - 10000;
|
|
||||||
tx2.from(results[0]);
|
|
||||||
tx2.to(address2, tx2Amount);
|
|
||||||
tx2.change(address);
|
|
||||||
tx2.sign(testKey);
|
|
||||||
tx2Hash = tx2.hash;
|
|
||||||
node.sendTransaction(tx2.serialize(), function(err) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
mineBlock(next);
|
|
||||||
});
|
|
||||||
}, function(next) {
|
|
||||||
var tx3 = new Transaction();
|
|
||||||
tx3.from(results[1]);
|
|
||||||
tx3.to(address3, results[1].satoshis - 10000);
|
|
||||||
tx3.change(address);
|
|
||||||
tx3.sign(testKey);
|
|
||||||
node.sendTransaction(tx3.serialize(), function(err) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
mineBlock(next);
|
|
||||||
});
|
|
||||||
}, function(next) {
|
|
||||||
var tx4 = new Transaction();
|
|
||||||
tx4.from(results[2]);
|
|
||||||
tx4.to(address4, results[2].satoshis - 10000);
|
|
||||||
tx4.change(address);
|
|
||||||
tx4.sign(testKey);
|
|
||||||
node.sendTransaction(tx4.serialize(), function(err) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
mineBlock(next);
|
|
||||||
});
|
|
||||||
}, function(next) {
|
|
||||||
var tx5 = new Transaction();
|
|
||||||
tx5.from(results[3]);
|
|
||||||
tx5.from(results[4]);
|
|
||||||
tx5.to(address5, results[3].satoshis - 10000);
|
|
||||||
tx5.to(address6, results[4].satoshis - 10000);
|
|
||||||
tx5.change(address);
|
|
||||||
tx5.sign(testKey);
|
|
||||||
node.sendTransaction(tx5.serialize(), function(err) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
mineBlock(next);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
], function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('five addresses', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address2,
|
|
||||||
address3,
|
|
||||||
address4,
|
|
||||||
address5,
|
|
||||||
address6
|
|
||||||
];
|
|
||||||
var options = {};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(4);
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(4);
|
|
||||||
history[0].tx.height.should.equal(159);
|
|
||||||
history[0].confirmations.should.equal(1);
|
|
||||||
history[1].tx.height.should.equal(158);
|
|
||||||
should.exist(history[1].addresses[address4]);
|
|
||||||
history[2].tx.height.should.equal(157);
|
|
||||||
should.exist(history[2].addresses[address3]);
|
|
||||||
history[3].tx.height.should.equal(156);
|
|
||||||
should.exist(history[3].addresses[address2]);
|
|
||||||
history[3].satoshis.should.equal(tx2Amount);
|
|
||||||
history[3].tx.hash.should.equal(tx2Hash);
|
|
||||||
history[3].confirmations.should.equal(4);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('five addresses (limited by height)', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address2,
|
|
||||||
address3,
|
|
||||||
address4,
|
|
||||||
address5,
|
|
||||||
address6
|
|
||||||
];
|
|
||||||
var options = {
|
|
||||||
start: 158,
|
|
||||||
end: 157
|
|
||||||
};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(2);
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(2);
|
|
||||||
history[0].tx.height.should.equal(158);
|
|
||||||
history[0].confirmations.should.equal(2);
|
|
||||||
history[1].tx.height.should.equal(157);
|
|
||||||
should.exist(history[1].addresses[address3]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('five addresses (limited by height 155 to 154)', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address2,
|
|
||||||
address3,
|
|
||||||
address4,
|
|
||||||
address5,
|
|
||||||
address6
|
|
||||||
];
|
|
||||||
var options = {
|
|
||||||
start: 157,
|
|
||||||
end: 156
|
|
||||||
};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(2);
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(2);
|
|
||||||
history[0].tx.height.should.equal(157);
|
|
||||||
history[1].tx.height.should.equal(156);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('five addresses (paginated by index)', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address2,
|
|
||||||
address3,
|
|
||||||
address4,
|
|
||||||
address5,
|
|
||||||
address6
|
|
||||||
];
|
|
||||||
var options = {
|
|
||||||
from: 0,
|
|
||||||
to: 3
|
|
||||||
};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(4);
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(3);
|
|
||||||
history[0].tx.height.should.equal(159);
|
|
||||||
history[0].confirmations.should.equal(1);
|
|
||||||
history[1].tx.height.should.equal(158);
|
|
||||||
should.exist(history[1].addresses[address4]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('one address with sending and receiving', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address
|
|
||||||
];
|
|
||||||
var options = {};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(6);
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(6);
|
|
||||||
history[0].tx.height.should.equal(159);
|
|
||||||
history[0].addresses[address].inputIndexes.should.deep.equal([0, 1]);
|
|
||||||
history[0].addresses[address].outputIndexes.should.deep.equal([2]);
|
|
||||||
history[0].confirmations.should.equal(1);
|
|
||||||
history[1].tx.height.should.equal(158);
|
|
||||||
history[2].tx.height.should.equal(157);
|
|
||||||
history[3].tx.height.should.equal(156);
|
|
||||||
history[4].tx.height.should.equal(155);
|
|
||||||
history[4].satoshis.should.equal(-10000);
|
|
||||||
history[4].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]);
|
|
||||||
history[4].addresses[address].inputIndexes.should.deep.equal([0]);
|
|
||||||
history[5].tx.height.should.equal(152);
|
|
||||||
history[5].satoshis.should.equal(10 * 1e8);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('summary for an address (sending and receiving)', function(done) {
|
|
||||||
node.getAddressSummary(address, {}, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalReceived.should.equal(2000000000);
|
|
||||||
results.totalSpent.should.equal(1999990000);
|
|
||||||
results.balance.should.equal(10000);
|
|
||||||
results.unconfirmedBalance.should.equal(0);
|
|
||||||
results.appearances.should.equal(6);
|
|
||||||
results.unconfirmedAppearances.should.equal(0);
|
|
||||||
results.txids.length.should.equal(6);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('total transaction count (sending and receiving)', function(done) {
|
|
||||||
var addresses = [
|
|
||||||
address
|
|
||||||
];
|
|
||||||
var options = {};
|
|
||||||
node.getAddressHistory(addresses, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.totalCount.should.equal(6);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Pagination', function() {
|
|
||||||
it('from 0 to 1', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 0,
|
|
||||||
to: 1
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(159);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('from 1 to 2', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 1,
|
|
||||||
to: 2
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(158);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('from 2 to 3', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 2,
|
|
||||||
to: 3
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(157);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('from 3 to 4', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 3,
|
|
||||||
to: 4
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(156);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('from 4 to 5', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 4,
|
|
||||||
to: 5
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(155);
|
|
||||||
history[0].satoshis.should.equal(-10000);
|
|
||||||
history[0].addresses[address].outputIndexes.should.deep.equal([0, 1, 2, 3, 4]);
|
|
||||||
history[0].addresses[address].inputIndexes.should.deep.equal([0]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('from 5 to 6', function(done) {
|
|
||||||
var options = {
|
|
||||||
from: 5,
|
|
||||||
to: 6
|
|
||||||
};
|
|
||||||
node.getAddressHistory(address, options, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
var history = results.items;
|
|
||||||
history.length.should.equal(1);
|
|
||||||
history[0].tx.height.should.equal(152);
|
|
||||||
history[0].satoshis.should.equal(10 * 1e8);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Mempool Index', function() {
|
|
||||||
var unspentOutput;
|
|
||||||
before(function(done) {
|
|
||||||
node.getAddressUnspentOutputs(address, false, function(err, results) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
results.length.should.equal(1);
|
|
||||||
unspentOutput = results[0];
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will update the mempool index after new tx', function(done) {
|
|
||||||
var memAddress = bitcore.PrivateKey().toAddress(node.network).toString();
|
|
||||||
var tx = new Transaction();
|
|
||||||
tx.from(unspentOutput);
|
|
||||||
tx.to(memAddress, unspentOutput.satoshis - 1000);
|
|
||||||
tx.fee(1000);
|
|
||||||
tx.sign(testKey);
|
|
||||||
|
|
||||||
node.services.bitcoind.sendTransaction(tx.serialize(), function(err, hash) {
|
|
||||||
node.getAddressTxids(memAddress, {}, function(err, txids) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
txids.length.should.equal(1);
|
|
||||||
txids[0].should.equal(hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Orphaned Transactions', function() {
|
|
||||||
this.timeout(8000);
|
|
||||||
var orphanedTransaction;
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
var count;
|
|
||||||
var invalidatedBlockHash;
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
function(next) {
|
|
||||||
client.sendToAddress(testKey.toAddress(regtest).toString(), 10, function(err) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
client.generate(1, next);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
client.getBlockCount(function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
count = response.result;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
client.getBlockHash(count, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
invalidatedBlockHash = response.result;
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
client.getBlock(invalidatedBlockHash, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
orphanedTransaction = response.result.tx[1];
|
|
||||||
should.exist(orphanedTransaction);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
client.invalidateBlock(invalidatedBlockHash, next);
|
|
||||||
}
|
|
||||||
], function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will not show confirmation count for orphaned transaction', function(done) {
|
|
||||||
// This test verifies that in the situation that the transaction is not in the mempool and
|
|
||||||
// is included in an orphaned block transaction index that the confirmation count will be unconfirmed.
|
|
||||||
node.getDetailedTransaction(orphanedTransaction, function(err, data) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(data);
|
|
||||||
should.exist(data.height);
|
|
||||||
data.height.should.equal(-1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
417
regtest/p2p.js
417
regtest/p2p.js
@ -1,417 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var expect = chai.expect;
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var path = require('path');
|
|
||||||
var Utils = require('./utils');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var zmq = require('zmq');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var Transaction = bitcore.Transaction;
|
|
||||||
var Block = bitcore.Block;
|
|
||||||
var BlockHeader = bitcore.BlockHeader;
|
|
||||||
var constants = require('../lib/constants');
|
|
||||||
var debug = true;
|
|
||||||
var extraDebug = true;
|
|
||||||
//advisable to use a tmpfs here, much easier on NAND-based disks and good for performance
|
|
||||||
var bitcoreDataDir = '/tmp/testtmpfs/bitcore';
|
|
||||||
// to do this on Linux: sudo mount -t tmpfs -o size=512m tmpfs /tmp/testtmpfs
|
|
||||||
var bitcoinDataDir = '/tmp/testtmpfs/bitcoin';
|
|
||||||
|
|
||||||
var rpcConfig = {
|
|
||||||
protocol: 'http',
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '58332',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcoin = {
|
|
||||||
args: {
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
listen: 1,
|
|
||||||
regtest: 1,
|
|
||||||
server: 1,
|
|
||||||
listenonion: 0,
|
|
||||||
whitelist: '127.0.0.1',
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
rpcport: rpcConfig.port
|
|
||||||
},
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: ['p2p', 'test-p2p', 'web'],
|
|
||||||
servicesConfig: {
|
|
||||||
p2p: {
|
|
||||||
peers: [
|
|
||||||
{
|
|
||||||
ip: { v4: '127.0.0.1' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'test-p2p': {
|
|
||||||
requirePath: path.resolve(__dirname + '/test_bus.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (debug && extraDebug) {
|
|
||||||
bitcoin.args.printtoconsole = 1;
|
|
||||||
bitcoin.args.debug = 1;
|
|
||||||
bitcoin.args.logips = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoin: bitcoin,
|
|
||||||
bitcoinDataDir: bitcoinDataDir,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
rpc: new BitcoinRPC(rpcConfig),
|
|
||||||
walletPassphrase: 'test',
|
|
||||||
txCount: 0,
|
|
||||||
blockHeight: 0,
|
|
||||||
walletPrivKeys: [],
|
|
||||||
initialTxs: [],
|
|
||||||
fee: 100000,
|
|
||||||
feesReceived: 0,
|
|
||||||
satoshisSent: 0,
|
|
||||||
walletId: crypto.createHash('sha256').update('test').digest('hex'),
|
|
||||||
satoshisReceived: 0,
|
|
||||||
initialHeight: 110,
|
|
||||||
path: '/test/info',
|
|
||||||
errorFilter: function(err, res) {
|
|
||||||
try {
|
|
||||||
var info = JSON.parse(res);
|
|
||||||
if (info.result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var utils = new Utils(opts);
|
|
||||||
|
|
||||||
var subSocket;
|
|
||||||
var txs = [];
|
|
||||||
var blocks = [];
|
|
||||||
var headers = [];
|
|
||||||
var startingBlockHash;
|
|
||||||
var count = 0;
|
|
||||||
function processMessages(topic, message) {
|
|
||||||
var topicStr = topic.toString();
|
|
||||||
if (topicStr === 'transaction') {
|
|
||||||
return txs.push(message);
|
|
||||||
} else if (topicStr === 'block') {
|
|
||||||
count++;
|
|
||||||
return blocks.push(message);
|
|
||||||
} else if (topicStr === 'headers') {
|
|
||||||
return headers.push(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupZmqSubscriber(callback) {
|
|
||||||
|
|
||||||
subSocket = zmq.socket('sub');
|
|
||||||
subSocket.on('connect', function(fd, endPoint) {
|
|
||||||
if (debug) {
|
|
||||||
console.log('ZMQ connected to:', endPoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
subSocket.on('disconnect', function(fd, endPoint) {
|
|
||||||
if (debug) {
|
|
||||||
console.log('ZMQ disconnect:', endPoint);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
subSocket.monitor(100, 0);
|
|
||||||
subSocket.connect('tcp://127.0.0.1:38332');
|
|
||||||
subSocket.subscribe('transaction');
|
|
||||||
subSocket.subscribe('block');
|
|
||||||
subSocket.subscribe('headers');
|
|
||||||
subSocket.on('message', processMessages);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('P2P Operations', function() {
|
|
||||||
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
utils.cleanup(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils),
|
|
||||||
utils.waitForBitcoinReady.bind(utils),
|
|
||||||
utils.unlockWallet.bind(utils),
|
|
||||||
utils.setupInitialTxs.bind(utils),
|
|
||||||
utils.startBitcoreNode.bind(utils),
|
|
||||||
utils.waitForBitcoreNode.bind(utils),
|
|
||||||
setupZmqSubscriber,
|
|
||||||
utils.sendTxs.bind(utils, false)
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Mempool', function() {
|
|
||||||
it('should send new transactions as they are broadcasted by our trusted peer (unsoliticted)', function(done) {
|
|
||||||
|
|
||||||
var initialTxs = {};
|
|
||||||
|
|
||||||
utils.opts.initialTxs.map(function(tx) {
|
|
||||||
initialTxs[tx.hash] = true;
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
for(; i < utils.opts.initialTxs.length; i++) {
|
|
||||||
var tx = new Transaction(txs[i]);
|
|
||||||
expect(initialTxs[tx.hash]).to.equal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(txs.length).to.equal(i);
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should connect to the p2p network and stream the mempool to clients', function(done) {
|
|
||||||
// this tricky because if the p2p service has already asked for the data
|
|
||||||
// from a particular peer, it will not ask again until the inv hash is dropped
|
|
||||||
// from its lru cache. So, to fake this out, I will clear this cache manually
|
|
||||||
txs.length = 0;
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/mempool',
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
var initialTxs = {};
|
|
||||||
|
|
||||||
utils.opts.initialTxs.map(function(tx) {
|
|
||||||
initialTxs[tx.hash] = true;
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
for(; i < utils.opts.initialTxs.length; i++) {
|
|
||||||
var tx = new Transaction(txs[i]);
|
|
||||||
expect(initialTxs[tx.hash]).to.equal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(txs.length).to.equal(i);
|
|
||||||
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to set a mempool filter and only get back what is NOT in the filter', function(done) {
|
|
||||||
var newTx = txs.shift();
|
|
||||||
newTx = new Transaction(newTx);
|
|
||||||
var argTxs = txs.map(function(rawTx) {
|
|
||||||
var tx = new Transaction(rawTx);
|
|
||||||
return tx.hash;
|
|
||||||
});
|
|
||||||
txs.length = 0;
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/mempool?filter=' + JSON.stringify(argTxs)
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
|
|
||||||
var tx = new Transaction(txs[0]);
|
|
||||||
expect(newTx.hash).to.equal(tx.hash);
|
|
||||||
expect(txs.length).to.equal(1);
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Block', function() {
|
|
||||||
|
|
||||||
it('should get blocks when they are relayed to us', function(done) {
|
|
||||||
opts.rpc.generate(1, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
startingBlockHash = res.result[0];
|
|
||||||
setTimeout(function() {
|
|
||||||
expect(blocks.length).to.equal(1);
|
|
||||||
var block = new Block(blocks[0]);
|
|
||||||
expect(startingBlockHash).to.equal(block.hash);
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to get historical blocks', function(done) {
|
|
||||||
|
|
||||||
blocks.length = 0;
|
|
||||||
var filter = { startHash: constants.BITCOIN_GENESIS_HASH.regtest };
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/blocks?filter=' + JSON.stringify(filter),
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
expect(blocks.length).to.equal(utils.opts.blockHeight + 1);
|
|
||||||
var lastBlock = new Block(blocks[blocks.length - 1]);
|
|
||||||
expect(startingBlockHash).to.equal(lastBlock.hash);
|
|
||||||
done();
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Block Headers', function() {
|
|
||||||
|
|
||||||
it('should be able to get historical block headers', function(done) {
|
|
||||||
|
|
||||||
var filter = { startHash: constants.BITCOIN_GENESIS_HASH.regtest };
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/headers?filter=' + JSON.stringify(filter),
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
|
|
||||||
expect(headers.length).to.equal(utils.opts.blockHeight + 1);
|
|
||||||
var lastBlockHeader = new BlockHeader(blocks[blocks.length - 1]);
|
|
||||||
expect(startingBlockHash).to.equal(lastBlockHeader.hash);
|
|
||||||
done();
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return up to 2000 headers in a single call to getHeaders', function(done) {
|
|
||||||
|
|
||||||
// p2p note: when asking for a series of headers, your peer will always follow up
|
|
||||||
// with an additional inventory message after delivering the initial data.
|
|
||||||
|
|
||||||
// after getHeaders: an inv message for the block matching the latest header you received.
|
|
||||||
// remember: getHeaders message does not respond with an inventory message like getBlocks does,
|
|
||||||
// instead it responds with the header message, but THEN will respond with a single inventory
|
|
||||||
// message representing the block of the last header delievered.
|
|
||||||
|
|
||||||
// For example: if there exists 4 blocks with block hashes a,b,c,d:
|
|
||||||
// getHeaders({ starts: 'a', stop: 0 }) should receive headers for b,c,d and an inv message for block d.
|
|
||||||
var additionalBlockCount = 2000 - 111;
|
|
||||||
headers.length = 0;
|
|
||||||
opts.rpc.generate(additionalBlockCount, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var filter = { startHash: constants.BITCOIN_GENESIS_HASH.regtest };
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/headers?filter=' + JSON.stringify(filter),
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
|
|
||||||
expect(headers.length).to.equal(2000);
|
|
||||||
done();
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return up to 500 blocks in a single call to getBlocks', function(done) {
|
|
||||||
|
|
||||||
// p2p note: when asking for a series of headers, your peer will always follow up
|
|
||||||
// with an additional inventory message after delivering the initial data.
|
|
||||||
|
|
||||||
// after getBlocks: an inv message for the block immediately following the last one you received, if
|
|
||||||
// there more blocks to retrieve. Since there is a 500 block limit in the initial inventory message response,
|
|
||||||
// when receiving 500 blocks, an additional inventory message will tell you what the next block is and that
|
|
||||||
// are more blocks to be retrieved.
|
|
||||||
|
|
||||||
blocks.length = 0;
|
|
||||||
count = 0;
|
|
||||||
var filter = { startHash: constants.BITCOIN_GENESIS_HASH.regtest };
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/blocks?filter=' + JSON.stringify(filter),
|
|
||||||
}, bitcore.httpOpts), function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function() {
|
|
||||||
|
|
||||||
expect(blocks.length).to.equal(501);
|
|
||||||
done();
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
@ -1,168 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var BaseService = require('../lib/service');
|
|
||||||
var inherits = require('util').inherits;
|
|
||||||
var zmq = require('zmq');
|
|
||||||
var index = require('../lib');
|
|
||||||
var log = index.log;
|
|
||||||
|
|
||||||
var TestBusService = function(options) {
|
|
||||||
BaseService.call(this, options);
|
|
||||||
this._cache = { transaction: [], block: [], headers: [] };
|
|
||||||
};
|
|
||||||
|
|
||||||
inherits(TestBusService, BaseService);
|
|
||||||
|
|
||||||
TestBusService.dependencies = ['p2p', 'web', 'block', 'timestamp'];
|
|
||||||
|
|
||||||
TestBusService.prototype.start = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.pubSocket = zmq.socket('pub');
|
|
||||||
|
|
||||||
log.info('zmq bound to port: 38332');
|
|
||||||
|
|
||||||
self.pubSocket.bind('tcp://127.0.0.1:38332');
|
|
||||||
|
|
||||||
self.bus = self.node.openBus({ remoteAddress: 'localhost' });
|
|
||||||
|
|
||||||
self.bus.on('p2p/transaction', function(tx) {
|
|
||||||
self._cache.transaction.push(tx);
|
|
||||||
if (self._ready) {
|
|
||||||
while(self._cache.transaction.length > 0) {
|
|
||||||
var transaction = self._cache.transaction.shift();
|
|
||||||
self.pubSocket.send([ 'transaction', new Buffer(transaction.uncheckedSerialize(), 'hex') ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.node.services.p2p.on('bestHeight', function(height) {
|
|
||||||
self._bestHeight = height;
|
|
||||||
});
|
|
||||||
|
|
||||||
self.bus.on('p2p/block', function(block) {
|
|
||||||
self._cache.block.push(block);
|
|
||||||
if (self._ready) {
|
|
||||||
while(self._cache.block.length > 0) {
|
|
||||||
var blk = self._cache.block.shift();
|
|
||||||
self.pubSocket.send([ 'p2p/block', blk.toBuffer() ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.bus.on('p2p/headers', function(headers) {
|
|
||||||
headers.forEach(function(header) {
|
|
||||||
self._cache.headers.push(header);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self._ready) {
|
|
||||||
while(self._cache.headers.length > 0) {
|
|
||||||
var hdr = self._cache.headers.shift();
|
|
||||||
self.pubSocket.send([ 'headers', hdr.toBuffer() ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.bus.on('block/block', function(block) {
|
|
||||||
self._cache.block.push(block);
|
|
||||||
if (self._ready) {
|
|
||||||
while(self._cache.block.length > 0) {
|
|
||||||
var blk = self._cache.block.shift();
|
|
||||||
self.pubSocket.send([ 'block/block', blk.toBuffer() ]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.bus.subscribe('p2p/transaction');
|
|
||||||
self.bus.subscribe('p2p/block');
|
|
||||||
self.bus.subscribe('p2p/headers');
|
|
||||||
self.bus.subscribe('block/block');
|
|
||||||
|
|
||||||
self.node.on('ready', function() {
|
|
||||||
|
|
||||||
self._ready = true;
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
TestBusService.prototype.setupRoutes = function(app) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
app.get('/p2p/mempool', function(req, res) {
|
|
||||||
self.node.services.p2p.clearInventoryCache();
|
|
||||||
var filter;
|
|
||||||
if (req.query.filter) {
|
|
||||||
filter = JSON.parse(req.query.filter);
|
|
||||||
}
|
|
||||||
self.node.services.p2p.getMempool(filter);
|
|
||||||
res.status(200).end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/p2p/blocks', function(req, res) {
|
|
||||||
self.node.services.p2p.clearInventoryCache();
|
|
||||||
var filter;
|
|
||||||
if (req.query.filter) {
|
|
||||||
filter = JSON.parse(req.query.filter);
|
|
||||||
}
|
|
||||||
self.node.services.p2p.getBlocks(filter);
|
|
||||||
res.status(200).end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/p2p/headers', function(req, res) {
|
|
||||||
var filter;
|
|
||||||
if (req.query.filter) {
|
|
||||||
filter = JSON.parse(req.query.filter);
|
|
||||||
}
|
|
||||||
self.node.services.p2p.getHeaders(filter);
|
|
||||||
res.status(200).end();
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/info', function(req, res) {
|
|
||||||
res.status(200).jsonp({ result: (self._ready && (self._bestHeight >= 0))});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/block/hash/:height', function(req, res) {
|
|
||||||
self.node.services.block.getBlockHash(req.params.height, function(err, hash) {
|
|
||||||
res.status(200).jsonp({ hash: hash, height: parseInt(req.params.height) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/block/height/:hash', function(req, res) {
|
|
||||||
self.node.services.block.getBlockHeight(req.params.hash, function(err, height) {
|
|
||||||
res.status(200).jsonp({ hash: req.params.hash, height: height });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/timestamp/time/:hash', function(req, res) {
|
|
||||||
self.node.services.timestamp.getTimestamp(req.params.hash, function(err, timestamp) {
|
|
||||||
res.status(200).jsonp({ hash: req.params.hash, timestamp: timestamp });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/timestamp/hash/:time', function(req, res) {
|
|
||||||
self.node.services.timestamp.getHash(req.params.time, function(err, hash) {
|
|
||||||
res.status(200).jsonp({ hash: hash, timestamp: parseInt(req.params.time) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/utxo/:address', function(req, res) {
|
|
||||||
self.node.services.utxo.getUtxosForAddress(req.params.address, function(err, utxos) {
|
|
||||||
res.status(200).jsonp({ utxos: utxos });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
TestBusService.prototype.getRoutePrefix = function() {
|
|
||||||
return 'test';
|
|
||||||
};
|
|
||||||
|
|
||||||
TestBusService.prototype.stop = function(callback) {
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = TestBusService;
|
|
||||||
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var BaseService = require('../lib/service');
|
|
||||||
var inherits = require('util').inherits;
|
|
||||||
|
|
||||||
var TestWebService = function(options) {
|
|
||||||
BaseService.call(this, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
inherits(TestWebService, BaseService);
|
|
||||||
|
|
||||||
TestWebService.dependencies = ['web', 'block', 'timestamp'];
|
|
||||||
|
|
||||||
TestWebService.prototype.start = function(callback) {
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
TestWebService.prototype.stop = function(callback) {
|
|
||||||
callback();
|
|
||||||
};
|
|
||||||
|
|
||||||
TestWebService.prototype.setupRoutes = function(app) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
app.get('/block/hash/:height', function(req, res) {
|
|
||||||
self.node.services.block.getBlockHash(req.params.height, function(err, hash) {
|
|
||||||
res.status(200).jsonp({ hash: hash, height: parseInt(req.params.height) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/block/height/:hash', function(req, res) {
|
|
||||||
self.node.services.block.getBlockHeight(req.params.hash, function(err, height) {
|
|
||||||
res.status(200).jsonp({ hash: req.params.hash, height: height });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/timestamp/time/:hash', function(req, res) {
|
|
||||||
self.node.services.timestamp.getTimestamp(req.params.hash, function(err, timestamp) {
|
|
||||||
res.status(200).jsonp({ hash: req.params.hash, timestamp: timestamp });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/timestamp/hash/:time', function(req, res) {
|
|
||||||
self.node.services.timestamp.getHash(req.params.time, function(err, hash) {
|
|
||||||
res.status(200).jsonp({ hash: hash, timestamp: parseInt(req.params.time) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/utxo/:address', function(req, res) {
|
|
||||||
self.node.services.utxo.getUtxosForAddress(req.params.address, function(err, utxos) {
|
|
||||||
res.status(200).jsonp({ utxos: utxos });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/info', function(req, res) {
|
|
||||||
var tip = self.node.services.block.tip;
|
|
||||||
if (tip) {
|
|
||||||
return res.status(200).jsonp({ tip: JSON.stringify(tip) });
|
|
||||||
}
|
|
||||||
return res.status(503).end();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
TestWebService.prototype.getRoutePrefix = function() {
|
|
||||||
return 'test';
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = TestWebService;
|
|
||||||
@ -1,199 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var expect = chai.expect;
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var path = require('path');
|
|
||||||
var utils = require('./utils');
|
|
||||||
|
|
||||||
var debug = true;
|
|
||||||
var bitcoreDataDir = '/tmp/bitcore';
|
|
||||||
var bitcoinDataDir = '/tmp/bitcoin';
|
|
||||||
|
|
||||||
var rpcConfig = {
|
|
||||||
protocol: 'http',
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '58332',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcoin = {
|
|
||||||
args: {
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
listen: 0,
|
|
||||||
regtest: 1,
|
|
||||||
server: 1,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:38332',
|
|
||||||
zmqpubhashblock: 'tcp://127.0.0.1:38332'
|
|
||||||
},
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: [
|
|
||||||
'bitcoind',
|
|
||||||
'web',
|
|
||||||
'db',
|
|
||||||
'timestamp',
|
|
||||||
'block',
|
|
||||||
'test-timestamp'
|
|
||||||
],
|
|
||||||
servicesConfig: {
|
|
||||||
bitcoind: {
|
|
||||||
connect: [
|
|
||||||
{
|
|
||||||
rpcconnect: rpcConfig.host,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
zmqpubrawtx: bitcoin.args.zmqpubrawtx
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'test-timestamp': {
|
|
||||||
requirePath: path.resolve(__dirname + '/test_web.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoin: bitcoin,
|
|
||||||
bitcoinDataDir: bitcoinDataDir,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
rpc: new BitcoinRPC(rpcConfig),
|
|
||||||
blockHeight: 0,
|
|
||||||
initialHeight: 150
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Timestamp Index', function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.timeout(60000);
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
utils.cleanup(self.opts, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
self.opts = Object.assign({}, opts);
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoinReady.bind(utils, self.opts),
|
|
||||||
utils.startBitcoreNode.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoreNode.bind(utils, self.opts),
|
|
||||||
function(next) {
|
|
||||||
|
|
||||||
async.timesLimit(opts.initialHeight, 12, function(n, next) {
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/block/hash/' + n
|
|
||||||
}, bitcore.httpOpts), function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
res = JSON.parse(res);
|
|
||||||
expect(res.height).to.equal(n);
|
|
||||||
expect(res.hash.length).to.equal(64);
|
|
||||||
next(null, res.hash);
|
|
||||||
});
|
|
||||||
}, function(err, hashes) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
self.hashes = hashes;
|
|
||||||
next();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sync block hashes as keys and timestamps as values', function(done) {
|
|
||||||
|
|
||||||
var lastTimestamp = 0;
|
|
||||||
async.mapLimit(self.hashes, 12, function(hash, next) {
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/timestamp/time/' + hash
|
|
||||||
}, bitcore.httpOpts), function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = JSON.parse(res);
|
|
||||||
next(null, res.timestamp);
|
|
||||||
});
|
|
||||||
}, function(err, timestamps) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
timestamps.forEach(function(timestamp) {
|
|
||||||
expect(timestamp).to.be.above(lastTimestamp);
|
|
||||||
lastTimestamp = timestamp;
|
|
||||||
});
|
|
||||||
self.timestamps = timestamps;
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should sync block timestamps as keys and block hashes as values', function(done) {
|
|
||||||
|
|
||||||
async.eachOfLimit(self.timestamps, 12, function(timestamp, index, next) {
|
|
||||||
utils.queryBitcoreNode(Object.assign({
|
|
||||||
path: '/test/timestamp/hash/' + timestamp
|
|
||||||
}, bitcore.httpOpts), function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = JSON.parse(res);
|
|
||||||
expect(res.hash).to.equal(self.hashes[index]);
|
|
||||||
expect(res.timestamp).to.equal(timestamp);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
428
regtest/utils.js
428
regtest/utils.js
@ -1,428 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var _ = require('lodash');
|
|
||||||
var mkdirp = require('mkdirp');
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var fs = require('fs');
|
|
||||||
var async = require('async');
|
|
||||||
var spawn = require('child_process').spawn;
|
|
||||||
var http = require('http');
|
|
||||||
var Unit = bitcore.Unit;
|
|
||||||
var Transaction = bitcore.Transaction;
|
|
||||||
var PrivateKey = bitcore.PrivateKey;
|
|
||||||
var assert = require('assert');
|
|
||||||
|
|
||||||
var Utils = function(opts) {
|
|
||||||
this.opts = opts;
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.writeConfigFile = function(fileStr, obj) {
|
|
||||||
fs.writeFileSync(fileStr, JSON.stringify(obj));
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.toArgs = function(opts) {
|
|
||||||
return Object.keys(opts).map(function(key) {
|
|
||||||
return '-' + key + '=' + opts[key];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.waitForService = function(task, callback) {
|
|
||||||
var retryOpts = { times: 20, interval: 1000 };
|
|
||||||
async.retry(retryOpts, task, callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.queryBitcoreNode = function(httpOpts, callback) {
|
|
||||||
var error;
|
|
||||||
var request = http.request(httpOpts, function(res) {
|
|
||||||
|
|
||||||
if (res.statusCode !== 200 && res.statusCode !== 201) {
|
|
||||||
if (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return callback(res.statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resError;
|
|
||||||
var resData = '';
|
|
||||||
|
|
||||||
res.on('error', function(e) {
|
|
||||||
resError = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('data', function(data) {
|
|
||||||
resData += data;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('end', function() {
|
|
||||||
if (error) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (httpOpts.errorFilter) {
|
|
||||||
return callback(httpOpts.errorFilter(resError, resData));
|
|
||||||
}
|
|
||||||
callback(resError, resData);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', function(e) {
|
|
||||||
error = e;
|
|
||||||
callback(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
request.write(httpOpts.body || '');
|
|
||||||
request.end();
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.waitForBitcoreNode = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var errorFilter = self.opts.errorFilter;
|
|
||||||
if (!errorFilter) {
|
|
||||||
errorFilter = function(err, res) {
|
|
||||||
try {
|
|
||||||
var info = JSON.parse(res);
|
|
||||||
if (info.dbheight === self.opts.blockHeight &&
|
|
||||||
info.dbheight === info.bitcoindheight &&
|
|
||||||
info.bitcoindhash === info.dbhash) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
} catch(e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var httpOpts = self.getHttpOpts({ path: self.opts.path || '/info', errorFilter: errorFilter });
|
|
||||||
|
|
||||||
self.waitForService(self.queryBitcoreNode.bind(self, httpOpts), callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.waitForBitcoinReady = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.waitForService(function(callback) {
|
|
||||||
|
|
||||||
self.opts.rpc.generate(self.opts.initialHeight, function(err, res) {
|
|
||||||
|
|
||||||
if (err || (res && res.error)) {
|
|
||||||
return callback('keep trying');
|
|
||||||
}
|
|
||||||
self.opts.blockHeight += self.opts.initialHeight;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback();
|
|
||||||
|
|
||||||
}, callback);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.initializeAndStartService = function(opts, callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
rimraf(opts.datadir, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirp(opts.datadir, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.configFile) {
|
|
||||||
self.writeConfigFile(opts.configFile.file, opts.configFile.conf);
|
|
||||||
}
|
|
||||||
|
|
||||||
var args = _.isArray(opts.args) ? opts.args : self.toArgs(opts.args);
|
|
||||||
opts.process = spawn(opts.exec, args, opts.opts);
|
|
||||||
callback();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.startBitcoreNode = function(callback) {
|
|
||||||
var self = this;
|
|
||||||
this.initializeAndStartService(self.opts.bitcore, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.opts.bitcore.process.stdout.on('data', function(data) {
|
|
||||||
if (self.opts.debug) {
|
|
||||||
process.stdout.write(data.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.opts.bitcore.process.stderr.on('data', function(data) {
|
|
||||||
process.stdout.write(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
callback();
|
|
||||||
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.startBitcoind = function(callback) {
|
|
||||||
var self = this;
|
|
||||||
self.initializeAndStartService(self.opts.bitcoin, function() {
|
|
||||||
|
|
||||||
// in case you choose to -printtoconsole
|
|
||||||
self.opts.bitcoin.process.stdout.on('data', function(data) {
|
|
||||||
if (self.opts.debug) {
|
|
||||||
process.stdout.write(data.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
self.opts.bitcoin.process.stderr.on('data', function(data) {
|
|
||||||
process.stdout.write(data.toString());
|
|
||||||
});
|
|
||||||
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.unlockWallet = function(callback) {
|
|
||||||
this.opts.rpc.walletPassPhrase(this.opts.walletPassphrase, 3000, function(err) {
|
|
||||||
if(err && err.code !== -15) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.getPrivateKeysWithABalance = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.opts.rpc.listUnspent(function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var utxos = [];
|
|
||||||
for(var i = 0; i < res.result.length; i++) {
|
|
||||||
if (res.result[i].amount > 1) {
|
|
||||||
utxos.push(res.result[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (utxos.length <= 0) {
|
|
||||||
return callback(new Error('no utxos available'));
|
|
||||||
}
|
|
||||||
async.mapLimit(utxos, 8, function(utxo, callback) {
|
|
||||||
|
|
||||||
self.opts.rpc.dumpPrivKey(utxo.address, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
var privKey = res.result;
|
|
||||||
callback(null, { utxo: utxo, privKey: privKey });
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(err, utxos) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, utxos);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.generateSpendingTxs = function(utxos) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
return utxos.map(function(utxo) {
|
|
||||||
|
|
||||||
var toPrivKey = new PrivateKey('testnet'); //external addresses
|
|
||||||
var changePrivKey = new PrivateKey('testnet'); //our wallet keys
|
|
||||||
var utxoSatoshis = Unit.fromBTC(utxo.utxo.amount).satoshis;
|
|
||||||
var satsToPrivKey = Math.round(utxoSatoshis / 2);
|
|
||||||
var tx = new Transaction();
|
|
||||||
|
|
||||||
tx.from(utxo.utxo);
|
|
||||||
tx.to(toPrivKey.toAddress().toString(), satsToPrivKey);
|
|
||||||
tx.fee(self.opts.fee);
|
|
||||||
tx.change(changePrivKey.toAddress().toString());
|
|
||||||
tx.sign(utxo.privKey);
|
|
||||||
|
|
||||||
self.opts.walletPrivKeys.push(changePrivKey);
|
|
||||||
self.opts.satoshisReceived += Unit.fromBTC(utxo.utxo.amount).toSatoshis() - (satsToPrivKey + self.opts.fee);
|
|
||||||
return tx;
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.setupInitialTxs = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.getPrivateKeysWithABalance(function(err, utxos) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
self.opts.initialTxs = self.generateSpendingTxs(utxos);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.sendTxs = function(generateBlockAfterEach, callback) {
|
|
||||||
var self = this;
|
|
||||||
if (typeof generateBlockAfterEach !== 'function') {
|
|
||||||
return async.eachSeries(this.opts.initialTxs, function(tx, next) {
|
|
||||||
self.sendTx(tx, generateBlockAfterEach, next);
|
|
||||||
}, callback);
|
|
||||||
}
|
|
||||||
async.eachOfSeries(this.opts.initialTxs, this.sendTx.bind(this), callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.sendTx = function(tx, index, callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
// sending these too quickly will prevent them from being relayed over the
|
|
||||||
// p2p network
|
|
||||||
self.opts.rpc.sendRawTransaction(tx.serialize(), function(err, res) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
assert(res.result === tx.hash, 'sendTx: provided hash did not match returned hash');
|
|
||||||
var mod = index % 2;
|
|
||||||
setTimeout(function() {
|
|
||||||
if (mod === 1) {
|
|
||||||
self.opts.blockHeight++;
|
|
||||||
self.opts.rpc.generate(1, callback);
|
|
||||||
} else {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.getHttpOpts = function(httpOpts) {
|
|
||||||
return Object.assign({
|
|
||||||
path: httpOpts.path,
|
|
||||||
method: httpOpts.method || 'GET',
|
|
||||||
body: httpOpts.body,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Content-Length': httpOpts.length || 0
|
|
||||||
},
|
|
||||||
errorFilter: httpOpts.errorFilter
|
|
||||||
}, this.opts.bitcore.httpOpts);
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.registerWallet = function(callback) {
|
|
||||||
|
|
||||||
var httpOpts = this.getHttpOpts(this.opts, { path: '/wallet-api/wallets/' + this.opts.walletId, method: 'POST' });
|
|
||||||
this.queryBitcoreNode(httpOpts, callback);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.uploadWallet = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var addresses = JSON.stringify(self.opts.walletPrivKeys.map(function(privKey) {
|
|
||||||
if (privKey.privKey) {
|
|
||||||
return privKey.pubKey.toString();
|
|
||||||
}
|
|
||||||
return privKey.toAddress().toString();
|
|
||||||
}));
|
|
||||||
|
|
||||||
var httpOpts = self.getHttpOpts(self.opts, {
|
|
||||||
path: '/wallet-api/wallets/' + self.opts.walletId + '/addresses',
|
|
||||||
method: 'POST',
|
|
||||||
body: addresses,
|
|
||||||
length: addresses.length
|
|
||||||
});
|
|
||||||
|
|
||||||
async.waterfall([ self.queryBitcoreNode.bind(self, httpOpts) ], function(err, res) {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
var job = JSON.parse(res);
|
|
||||||
|
|
||||||
Object.keys(job).should.deep.equal(['jobId']);
|
|
||||||
|
|
||||||
var httpOpts = self.getHttpOpts(self.opts, { path: '/wallet-api/jobs/' + job.jobId });
|
|
||||||
|
|
||||||
async.retry({ times: 10, interval: 1000 }, function(next) {
|
|
||||||
self.queryBitcoreNode(httpOpts, function(err, res) {
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
var result = JSON.parse(res);
|
|
||||||
if (result.status === 'complete') {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
next(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function(err) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.getListOfTxs = function(callback) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var end = Date.now() + 86400000;
|
|
||||||
var httpOpts = self.getHttpOpts(self.opts, {
|
|
||||||
path: '/wallet-api/wallets/' + self.opts.walletId + '/transactions?start=0&end=' + end });
|
|
||||||
|
|
||||||
self.queryBitcoreNode(httpOpts, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
var results = [];
|
|
||||||
res.split('\n').forEach(function(result) {
|
|
||||||
|
|
||||||
if (result.length > 0) {
|
|
||||||
return results.push(JSON.parse(result));
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
var map = self.opts.initialTxs.map(function(tx) {
|
|
||||||
return tx.serialize();
|
|
||||||
});
|
|
||||||
|
|
||||||
results.forEach(function(result) {
|
|
||||||
var tx = new Transaction(result);
|
|
||||||
map.splice(map.indexOf(tx.uncheckedSerialize()), 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
map.length.should.equal(0);
|
|
||||||
results.length.should.equal(self.opts.initialTxs.length);
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Utils.prototype.cleanup = function(callback) {
|
|
||||||
this.opts.bitcore.process.kill();
|
|
||||||
this.opts.bitcoin.process.kill();
|
|
||||||
setTimeout(callback, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Utils;
|
|
||||||
270
regtest/utxo.js
270
regtest/utxo.js
@ -1,270 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var expect = chai.expect;
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var path = require('path');
|
|
||||||
var Utils = require('./utils');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var PrivateKey = bitcore.PrivateKey;
|
|
||||||
var Transaction = bitcore.Transaction;
|
|
||||||
var Output = bitcore.Transaction.Output;
|
|
||||||
var Script = bitcore.Script;
|
|
||||||
var _ = require('lodash');
|
|
||||||
|
|
||||||
var debug = false;
|
|
||||||
var bitcoreDataDir = '/tmp/bitcore';
|
|
||||||
var bitcoinDataDir = '/tmp/bitcoin';
|
|
||||||
|
|
||||||
var rpcConfig = {
|
|
||||||
protocol: 'http',
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '58332',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcoin = {
|
|
||||||
args: {
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
listen: 0,
|
|
||||||
regtest: 1,
|
|
||||||
server: 1,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:38332',
|
|
||||||
zmqpubrawblock: 'tcp://127.0.0.1:38332'
|
|
||||||
},
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: [
|
|
||||||
'bitcoind',
|
|
||||||
'db',
|
|
||||||
'timestamp',
|
|
||||||
'web',
|
|
||||||
'block',
|
|
||||||
'utxo',
|
|
||||||
'utxo-test'
|
|
||||||
],
|
|
||||||
servicesConfig: {
|
|
||||||
bitcoind: {
|
|
||||||
connect: [
|
|
||||||
{
|
|
||||||
rpcconnect: rpcConfig.host,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
zmqpubrawtx: bitcoin.args.zmqpubrawtx
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
'utxo-test': {
|
|
||||||
requirePath: path.resolve(__dirname + '/test_web.js')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoin: bitcoin,
|
|
||||||
bitcoinDataDir: bitcoinDataDir,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
rpc: new BitcoinRPC(rpcConfig),
|
|
||||||
walletPassphrase: 'test',
|
|
||||||
txCount: 0,
|
|
||||||
blockHeight: 0,
|
|
||||||
walletPrivKeys: [],
|
|
||||||
initialTxs: [],
|
|
||||||
fee: 100000,
|
|
||||||
feesReceived: 0,
|
|
||||||
satoshisSent: 0,
|
|
||||||
walletId: crypto.createHash('sha256').update('test').digest('hex'),
|
|
||||||
satoshisReceived: 0,
|
|
||||||
initialHeight: 150
|
|
||||||
};
|
|
||||||
|
|
||||||
var utils = new Utils(opts);
|
|
||||||
|
|
||||||
describe('Utxo Operations', function() {
|
|
||||||
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
utils.cleanup(done);
|
|
||||||
});
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils),
|
|
||||||
utils.waitForBitcoinReady.bind(utils),
|
|
||||||
utils.unlockWallet.bind(utils),
|
|
||||||
utils.setupInitialTxs.bind(utils),
|
|
||||||
utils.sendTxs.bind(utils),
|
|
||||||
utils.startBitcoreNode.bind(utils),
|
|
||||||
utils.waitForBitcoreNode.bind(utils)
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should index utxos', function(done) {
|
|
||||||
async.mapSeries(opts.walletPrivKeys, function(privKey, next) {
|
|
||||||
|
|
||||||
var address = privKey.toAddress().toString();
|
|
||||||
|
|
||||||
var httpOpts = Object.assign({
|
|
||||||
path: '/test/utxo/' + address
|
|
||||||
}, bitcore.httpOpts);
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(httpOpts, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = JSON.parse(res);
|
|
||||||
expect(res.utxos.length).equal(1);
|
|
||||||
expect(res.utxos[0].address).to.equal(address);
|
|
||||||
expect(Object.keys(res.utxos[0])).to.deep.equal([
|
|
||||||
'address',
|
|
||||||
'txId',
|
|
||||||
'outputIndex',
|
|
||||||
'height',
|
|
||||||
'satoshis',
|
|
||||||
'script' ]);
|
|
||||||
next(null, res.utxos);
|
|
||||||
|
|
||||||
});
|
|
||||||
}, function(err, results) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.utxos = _.flatten(results);
|
|
||||||
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should store p2pk and p2pkh utxos', function(done) {
|
|
||||||
|
|
||||||
var pk1 = new PrivateKey('testnet');
|
|
||||||
var pk2 = new PrivateKey('testnet');
|
|
||||||
|
|
||||||
var satoshis = 100000000;
|
|
||||||
var utxo = self.utxos[0];
|
|
||||||
|
|
||||||
var tx = new Transaction();
|
|
||||||
|
|
||||||
tx.from(utxo);
|
|
||||||
|
|
||||||
tx.addOutput(new Output({
|
|
||||||
satoshis: satoshis,
|
|
||||||
script: Script.buildPublicKeyOut(pk1.publicKey)
|
|
||||||
}));
|
|
||||||
|
|
||||||
tx.change(pk2.toAddress().toString());
|
|
||||||
tx.sign(opts.walletPrivKeys[0]);
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
|
|
||||||
function(next) {
|
|
||||||
utils.sendTx(tx, 1, function(err) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
function(next) {
|
|
||||||
|
|
||||||
utils.waitForBitcoreNode(function(err) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
function(next) {
|
|
||||||
|
|
||||||
var address = pk1.publicKey.toString('hex');
|
|
||||||
var httpOpts = Object.assign({
|
|
||||||
path: '/test/utxo/' + address
|
|
||||||
}, bitcore.httpOpts);
|
|
||||||
|
|
||||||
utils.queryBitcoreNode(httpOpts, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
res = JSON.parse(res);
|
|
||||||
expect(res.utxos.length).to.equal(1);
|
|
||||||
expect(res.utxos[0].address).to.equal(address);
|
|
||||||
expect(res.utxos[0].satoshis).to.equal(satoshis);
|
|
||||||
expect(Object.keys(res.utxos[0])).to.deep.equal([
|
|
||||||
'address',
|
|
||||||
'txId',
|
|
||||||
'outputIndex',
|
|
||||||
'height',
|
|
||||||
'satoshis',
|
|
||||||
'script' ]);
|
|
||||||
|
|
||||||
next();
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
], function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
done();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
@ -1,467 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
// To run the tests: $ mocha -R spec regtest/bitcoind.js
|
|
||||||
|
|
||||||
var path = require('path');;
|
|
||||||
var index = require('../..');
|
|
||||||
var log = index.log;
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
var BN = bitcore.crypto.BN;
|
|
||||||
var async = require('async');
|
|
||||||
var rimraf = require('rimraf');
|
|
||||||
var bitcoind;
|
|
||||||
|
|
||||||
/* jshint unused: false */
|
|
||||||
var should = chai.should();
|
|
||||||
var assert = chai.assert;
|
|
||||||
var sinon = require('sinon');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var transactionData = [];
|
|
||||||
var blockHashes = [];
|
|
||||||
var utxos;
|
|
||||||
var client;
|
|
||||||
var coinbasePrivateKey;
|
|
||||||
var privateKey = bitcore.PrivateKey();
|
|
||||||
var destKey = bitcore.PrivateKey();
|
|
||||||
|
|
||||||
describe('Bitcoind Functionality', function() {
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
// Add the regtest network
|
|
||||||
bitcore.Networks.enableRegtest();
|
|
||||||
var regtestNetwork = bitcore.Networks.get('regtest');
|
|
||||||
var config = { rpcprotocol: 'http', rpcport: 18332, rpcuser: 'bitcoin', rpcpassword: 'local321', zmqpubrawtx: 'tcp://127.0.0.1:38332' };
|
|
||||||
|
|
||||||
bitcoind = require('../..').services.Bitcoin({
|
|
||||||
connect: [ config ],
|
|
||||||
node: {
|
|
||||||
network: regtestNetwork,
|
|
||||||
getNetworkName: function() {
|
|
||||||
return 'regtest';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
bitcoind.on('error', function(err) {
|
|
||||||
log.error('error="%s"', err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
bitcoind.start(function() {
|
|
||||||
log.info('Bitcoind started');
|
|
||||||
|
|
||||||
client = new BitcoinRPC({
|
|
||||||
protocol: config.rpcprotocol || 'http',
|
|
||||||
host: config.rpchost || '127.0.0.1',
|
|
||||||
port: config.rpcport,
|
|
||||||
user: config.rpcuser,
|
|
||||||
pass: config.rpcpassword,
|
|
||||||
rejectUnauthorized: true
|
|
||||||
});
|
|
||||||
|
|
||||||
setImmediate(function() {
|
|
||||||
client.generate(150, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
blockHashes = response.result;
|
|
||||||
|
|
||||||
log.info('Preparing test data...');
|
|
||||||
|
|
||||||
// Get all of the unspent outputs
|
|
||||||
client.listUnspent(0, 150, function(err, response) {
|
|
||||||
utxos = response.result;
|
|
||||||
|
|
||||||
async.mapSeries(utxos, function(utxo, next) {
|
|
||||||
async.series([
|
|
||||||
function(finished) {
|
|
||||||
// Load all of the transactions for later testing
|
|
||||||
client.getTransaction(utxo.txid, function(err, txresponse) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
// add to the list of transactions for testing later
|
|
||||||
transactionData.push(txresponse.result.hex);
|
|
||||||
finished();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(finished) {
|
|
||||||
// Get the private key for each utxo
|
|
||||||
client.dumpPrivKey(utxo.address, function(err, privresponse) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
utxo.privateKeyWIF = privresponse.result;
|
|
||||||
finished();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
], next);
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
this.timeout(60000);
|
|
||||||
bitcoind.node.stopping = true;
|
|
||||||
bitcoind.stop(function(err, result) {
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks by hash', function() {
|
|
||||||
|
|
||||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
bitcoind.getBlock(blockHashes[i], function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
block.hash.should.equal(blockHashes[i]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks as buffers', function() {
|
|
||||||
[0,1,2,3,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
bitcoind.getRawBlock(blockHashes[i], function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
(block instanceof Buffer).should.equal(true);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get errors as error instances', function() {
|
|
||||||
it('will wrap an rpc into a javascript error', function(done) {
|
|
||||||
bitcoind.client.getBlock(1000000000, function(err, response) {
|
|
||||||
var error = bitcoind._wrapRPCError(err);
|
|
||||||
(error instanceof Error).should.equal(true);
|
|
||||||
error.message.should.equal(err.message);
|
|
||||||
error.code.should.equal(err.code);
|
|
||||||
should.exist(error.stack);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get blocks by height', function() {
|
|
||||||
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generated block ' + i, function(done) {
|
|
||||||
// add the genesis block
|
|
||||||
var height = i + 1;
|
|
||||||
bitcoind.getBlock(i + 1, function(err, block) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
should.exist(block);
|
|
||||||
block.hash.should.equal(blockHashes[i]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will get error with number greater than tip', function(done) {
|
|
||||||
bitcoind.getBlock(1000000000, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
err.code.should.equal(-8);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get transactions by hash', function() {
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('for tx ' + i, function(done) {
|
|
||||||
var txhex = transactionData[i];
|
|
||||||
var tx = new bitcore.Transaction();
|
|
||||||
tx.fromString(txhex);
|
|
||||||
bitcoind.getTransaction(tx.hash, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will return error if the transaction does not exist', function(done) {
|
|
||||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
|
||||||
bitcoind.getTransaction(txid, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get transactions as buffers', function() {
|
|
||||||
[0,1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('for tx ' + i, function(done) {
|
|
||||||
var txhex = transactionData[i];
|
|
||||||
var tx = new bitcore.Transaction();
|
|
||||||
tx.fromString(txhex);
|
|
||||||
bitcoind.getRawTransaction(tx.hash, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
response.should.be.instanceOf(Buffer);
|
|
||||||
assert(response.toString('hex') === txhex, 'incorrect tx data result');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will return error if the transaction does not exist', function(done) {
|
|
||||||
var txid = '6226c407d0e9705bdd7158e60983e37d0f5d23529086d6672b07d9238d5aa618';
|
|
||||||
bitcoind.getRawTransaction(txid, function(err, response) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get block header', function() {
|
|
||||||
var expectedWork = new BN(6);
|
|
||||||
[1,2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generate block ' + i, function(done) {
|
|
||||||
bitcoind.getBlockHeader(blockHashes[i], function(err, blockIndex) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(blockIndex);
|
|
||||||
should.exist(blockIndex.chainWork);
|
|
||||||
var work = new BN(blockIndex.chainWork, 'hex');
|
|
||||||
work.toString(16).should.equal(expectedWork.toString(16));
|
|
||||||
expectedWork = expectedWork.add(new BN(2));
|
|
||||||
should.exist(blockIndex.prevHash);
|
|
||||||
blockIndex.hash.should.equal(blockHashes[i]);
|
|
||||||
blockIndex.prevHash.should.equal(blockHashes[i - 1]);
|
|
||||||
blockIndex.height.should.equal(i + 1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get null prevHash for the genesis block', function(done) {
|
|
||||||
bitcoind.getBlockHeader(0, function(err, header) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(header);
|
|
||||||
should.equal(header.prevHash, undefined);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get error for block not found', function(done) {
|
|
||||||
bitcoind.getBlockHeader('notahash', function(err, header) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get block index by height', function() {
|
|
||||||
var expectedWork = new BN(6);
|
|
||||||
[2,3,4,5,6,7,8,9].forEach(function(i) {
|
|
||||||
it('generate block ' + i, function() {
|
|
||||||
bitcoind.getBlockHeader(i, function(err, header) {
|
|
||||||
should.exist(header);
|
|
||||||
should.exist(header.chainWork);
|
|
||||||
var work = new BN(header.chainWork, 'hex');
|
|
||||||
work.toString(16).should.equal(expectedWork.toString(16));
|
|
||||||
expectedWork = expectedWork.add(new BN(2));
|
|
||||||
should.exist(header.prevHash);
|
|
||||||
header.hash.should.equal(blockHashes[i - 1]);
|
|
||||||
header.prevHash.should.equal(blockHashes[i - 2]);
|
|
||||||
header.height.should.equal(i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('will get error with number greater than tip', function(done) {
|
|
||||||
bitcoind.getBlockHeader(100000, function(err, header) {
|
|
||||||
should.exist(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('send transaction functionality', function() {
|
|
||||||
|
|
||||||
it('will not error and return the transaction hash', function(done) {
|
|
||||||
|
|
||||||
// create and sign the transaction
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[0]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[0].amount * 1e8 - 1000);
|
|
||||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[0].privateKeyWIF));
|
|
||||||
|
|
||||||
// test sending the transaction
|
|
||||||
bitcoind.sendTransaction(tx.serialize(), function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
hash.should.equal(tx.hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will throw an error if an unsigned transaction is sent', function(done) {
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[1]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[1].amount * 1e8 - 1000);
|
|
||||||
bitcoind.sendTransaction(tx.uncheckedSerialize(), function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
(err instanceof Error).should.equal(true);
|
|
||||||
should.not.exist(hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will throw an error for unexpected types (tx decode failed)', function(done) {
|
|
||||||
var garbage = new Buffer('abcdef', 'hex');
|
|
||||||
bitcoind.sendTransaction(garbage, function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
should.not.exist(hash);
|
|
||||||
var num = 23;
|
|
||||||
bitcoind.sendTransaction(num, function(err, hash) {
|
|
||||||
should.exist(err);
|
|
||||||
(err instanceof Error).should.equal(true);
|
|
||||||
should.not.exist(hash);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will emit "tx" events', function(done) {
|
|
||||||
var tx = bitcore.Transaction();
|
|
||||||
tx.from(utxos[2]);
|
|
||||||
tx.change(privateKey.toAddress());
|
|
||||||
tx.to(destKey.toAddress(), utxos[2].amount * 1e8 - 1000);
|
|
||||||
tx.sign(bitcore.PrivateKey.fromWIF(utxos[2].privateKeyWIF));
|
|
||||||
|
|
||||||
var serialized = tx.serialize();
|
|
||||||
|
|
||||||
bitcoind.once('tx', function(buffer) {
|
|
||||||
buffer.toString('hex').should.equal(serialized);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
bitcoind.sendTransaction(serialized, function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(hash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fee estimation', function() {
|
|
||||||
it('will estimate fees', function(done) {
|
|
||||||
bitcoind.estimateFee(1, function(err, fees) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
fees.should.equal(-1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('tip updates', function() {
|
|
||||||
it('will get an event when the tip is new', function(done) {
|
|
||||||
this.timeout(4000);
|
|
||||||
bitcoind.on('tip', function(height) {
|
|
||||||
if (height === 151) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
client.generate(1, function(err, response) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get detailed transaction', function() {
|
|
||||||
it('should include details for coinbase tx', function(done) {
|
|
||||||
bitcoind.getDetailedTransaction(utxos[0].txid, function(err, tx) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
should.exist(tx.height);
|
|
||||||
tx.height.should.be.a('number');
|
|
||||||
should.exist(tx.blockTimestamp);
|
|
||||||
should.exist(tx.blockHash);
|
|
||||||
tx.coinbase.should.equal(true);
|
|
||||||
tx.version.should.equal(1);
|
|
||||||
tx.hex.should.be.a('string');
|
|
||||||
tx.locktime.should.equal(0);
|
|
||||||
tx.feeSatoshis.should.equal(0);
|
|
||||||
tx.outputSatoshis.should.equal(50 * 1e8);
|
|
||||||
tx.inputSatoshis.should.equal(0);
|
|
||||||
tx.inputs.length.should.equal(1);
|
|
||||||
tx.outputs.length.should.equal(1);
|
|
||||||
should.equal(tx.inputs[0].prevTxId, null);
|
|
||||||
should.equal(tx.inputs[0].outputIndex, null);
|
|
||||||
tx.inputs[0].script.should.be.a('string');
|
|
||||||
should.equal(tx.inputs[0].scriptAsm, null);
|
|
||||||
should.equal(tx.inputs[0].address, null);
|
|
||||||
should.equal(tx.inputs[0].satoshis, null);
|
|
||||||
tx.outputs[0].satoshis.should.equal(50 * 1e8);
|
|
||||||
tx.outputs[0].script.should.be.a('string');
|
|
||||||
tx.outputs[0].scriptAsm.should.be.a('string');
|
|
||||||
tx.outputs[0].spentTxId.should.be.a('string');
|
|
||||||
tx.outputs[0].spentIndex.should.equal(0);
|
|
||||||
tx.outputs[0].spentHeight.should.be.a('number');
|
|
||||||
tx.outputs[0].address.should.be.a('string');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#getInfo', function() {
|
|
||||||
it('will get information', function(done) {
|
|
||||||
bitcoind.getInfo(function(err, info) {
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
info.network.should.equal('regtest');
|
|
||||||
should.exist(info);
|
|
||||||
should.exist(info.version);
|
|
||||||
should.exist(info.blocks);
|
|
||||||
should.exist(info.timeOffset);
|
|
||||||
should.exist(info.connections);
|
|
||||||
should.exist(info.difficulty);
|
|
||||||
should.exist(info.testnet);
|
|
||||||
should.exist(info.relayFee);
|
|
||||||
should.exist(info.errors);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
@ -1,268 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var chai = require('chai');
|
|
||||||
var should = chai.should();
|
|
||||||
var async = require('async');
|
|
||||||
var BitcoinRPC = require('bitcoind-rpc');
|
|
||||||
var path = require('path');
|
|
||||||
var utils = require('./utils');
|
|
||||||
var crypto = require('crypto');
|
|
||||||
|
|
||||||
var debug = true;
|
|
||||||
var bitcoreDataDir = '/tmp/bitcore';
|
|
||||||
var bitcoinDataDir = '/tmp/bitcoin';
|
|
||||||
|
|
||||||
var rpcConfig = {
|
|
||||||
protocol: 'http',
|
|
||||||
user: 'bitcoin',
|
|
||||||
pass: 'local321',
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '58332',
|
|
||||||
rejectUnauthorized: false
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcoin = {
|
|
||||||
args: {
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
listen: 0,
|
|
||||||
regtest: 1,
|
|
||||||
server: 1,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
zmqpubrawtx: 'tcp://127.0.0.1:38332',
|
|
||||||
zmqpubhashblock: 'tcp://127.0.0.1:38332'
|
|
||||||
},
|
|
||||||
datadir: bitcoinDataDir,
|
|
||||||
exec: 'bitcoind', //if this isn't on your PATH, then provide the absolute path, e.g. /usr/local/bin/bitcoind
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var bitcore = {
|
|
||||||
configFile: {
|
|
||||||
file: bitcoreDataDir + '/bitcore-node.json',
|
|
||||||
conf: {
|
|
||||||
network: 'regtest',
|
|
||||||
port: 53001,
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
services: [
|
|
||||||
'bitcoind',
|
|
||||||
'db',
|
|
||||||
'transaction',
|
|
||||||
'timestamp',
|
|
||||||
'address',
|
|
||||||
'mempool',
|
|
||||||
'wallet-api',
|
|
||||||
'web',
|
|
||||||
'block'
|
|
||||||
],
|
|
||||||
servicesConfig: {
|
|
||||||
bitcoind: {
|
|
||||||
connect: [
|
|
||||||
{
|
|
||||||
rpcconnect: rpcConfig.host,
|
|
||||||
rpcport: rpcConfig.port,
|
|
||||||
rpcuser: rpcConfig.user,
|
|
||||||
rpcpassword: rpcConfig.pass,
|
|
||||||
zmqpubrawtx: bitcoin.args.zmqpubrawtx
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
httpOpts: {
|
|
||||||
protocol: 'http:',
|
|
||||||
hostname: 'localhost',
|
|
||||||
port: 53001,
|
|
||||||
},
|
|
||||||
opts: { cwd: bitcoreDataDir },
|
|
||||||
datadir: bitcoreDataDir,
|
|
||||||
exec: path.resolve(__dirname, '../bin/bitcore-node'),
|
|
||||||
args: ['start'],
|
|
||||||
process: null
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
debug: debug,
|
|
||||||
bitcore: bitcore,
|
|
||||||
bitcoin: bitcoin,
|
|
||||||
bitcoinDataDir: bitcoinDataDir,
|
|
||||||
bitcoreDataDir: bitcoreDataDir,
|
|
||||||
rpc: new BitcoinRPC(rpcConfig),
|
|
||||||
walletPassphrase: 'test',
|
|
||||||
txCount: 0,
|
|
||||||
blockHeight: 0,
|
|
||||||
walletPrivKeys: [],
|
|
||||||
initialTxs: [],
|
|
||||||
fee: 100000,
|
|
||||||
feesReceived: 0,
|
|
||||||
satoshisSent: 0,
|
|
||||||
walletId: crypto.createHash('sha256').update('test').digest('hex'),
|
|
||||||
satoshisReceived: 0,
|
|
||||||
initialHeight: 150
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Wallet Operations', function() {
|
|
||||||
|
|
||||||
this.timeout(60000);
|
|
||||||
|
|
||||||
describe('Register, Upload, GetTransactions', function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
after(function(done) {
|
|
||||||
utils.cleanup(self.opts, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
self.opts = Object.assign({}, opts);
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoinReady.bind(utils, self.opts),
|
|
||||||
utils.unlockWallet.bind(utils, self.opts),
|
|
||||||
utils.setupInitialTxs.bind(utils, self.opts),
|
|
||||||
utils.startBitcoreNode.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoreNode.bind(utils, self.opts)
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should register wallet', function(done) {
|
|
||||||
|
|
||||||
utils.registerWallet.call(utils, self.opts, function(err, res) {
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.should.deep.equal(JSON.stringify({
|
|
||||||
walletId: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'
|
|
||||||
}));
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should upload a wallet', function(done) {
|
|
||||||
|
|
||||||
utils.uploadWallet.call(utils, self.opts, done);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get a list of transactions', function(done) {
|
|
||||||
|
|
||||||
//the wallet should be fully uploaded and indexed by the time this happens
|
|
||||||
utils.sendTxs.call(utils, self.opts, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
utils.waitForBitcoreNode.call(utils, self.opts, function(err) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
utils.getListOfTxs.call(utils, self.opts, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Load addresses after syncing the blockchain', function() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.opts = Object.assign({}, opts);
|
|
||||||
|
|
||||||
after(utils.cleanup.bind(utils, self.opts));
|
|
||||||
|
|
||||||
before(function(done) {
|
|
||||||
async.series([
|
|
||||||
utils.startBitcoind.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoinReady.bind(utils, self.opts),
|
|
||||||
utils.unlockWallet.bind(utils, self.opts),
|
|
||||||
utils.setupInitialTxs.bind(utils, self.opts),
|
|
||||||
utils.sendTxs.bind(utils, self.opts),
|
|
||||||
utils.startBitcoreNode.bind(utils, self.opts),
|
|
||||||
utils.waitForBitcoreNode.bind(utils, self.opts),
|
|
||||||
utils.registerWallet.bind(utils, self.opts),
|
|
||||||
utils.uploadWallet.bind(utils, self.opts)
|
|
||||||
], done);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get list of transactions', function(done) {
|
|
||||||
|
|
||||||
utils.getListOfTxs.call(utils, self.opts, done);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get the balance of a wallet', function(done) {
|
|
||||||
|
|
||||||
var httpOpts = utils.getHttpOpts.call(
|
|
||||||
utils,
|
|
||||||
self.opts,
|
|
||||||
{ path: '/wallet-api/wallets/' + self.opts.walletId + '/balance' });
|
|
||||||
|
|
||||||
utils.queryBitcoreNode.call(utils, httpOpts, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
var results = JSON.parse(res);
|
|
||||||
results.satoshis.should.equal(self.opts.satoshisReceived);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get the set of utxos for the wallet', function(done) {
|
|
||||||
|
|
||||||
var httpOpts = utils.getHttpOpts.call(
|
|
||||||
utils,
|
|
||||||
self.opts,
|
|
||||||
{ path: '/wallet-api/wallets/' + opts.walletId + '/utxos' });
|
|
||||||
|
|
||||||
utils.queryBitcoreNode.call(utils, httpOpts, function(err, res) {
|
|
||||||
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
var results = JSON.parse(res);
|
|
||||||
var balance = 0;
|
|
||||||
|
|
||||||
results.utxos.forEach(function(utxo) {
|
|
||||||
balance += utxo.satoshis;
|
|
||||||
});
|
|
||||||
|
|
||||||
results.height.should.equal(self.opts.blockHeight);
|
|
||||||
balance.should.equal(self.opts.satoshisReceived);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get the list of jobs', function(done) {
|
|
||||||
var httpOpts = utils.getHttpOpts.call(utils, self.opts, { path: '/wallet-api/jobs' });
|
|
||||||
utils.queryBitcoreNode.call(utils, httpOpts, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
var results = JSON.parse(res);
|
|
||||||
results.jobCount.should.equal(1);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove all wallets', function(done) {
|
|
||||||
var httpOpts = utils.getHttpOpts.call(utils, self.opts, { path: '/wallet-api/wallets', method: 'DELETE' });
|
|
||||||
utils.queryBitcoreNode.call(utils, httpOpts, function(err, res) {
|
|
||||||
if(err) {
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
var results = JSON.parse(res);
|
|
||||||
results.numberRemoved.should.equal(152);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,198 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var should = require('chai').should();
|
|
||||||
var bitcore = require('bitcore-lib');
|
|
||||||
|
|
||||||
var Encoding = require('../../../lib/services/wallet-api/encoding');
|
|
||||||
|
|
||||||
describe('Wallet-Api service encoding', function() {
|
|
||||||
|
|
||||||
var servicePrefix = new Buffer('0000', 'hex');
|
|
||||||
var encoding = new Encoding(servicePrefix);
|
|
||||||
var walletId = 'abcdef123456';
|
|
||||||
var txid = '91b58f19b6eecba94ed0f6e463e8e334ec0bcda7880e2985c82a8f32e4d03add';
|
|
||||||
var address = '1EZBqbJSHFKSkVPNKzc5v26HA6nAHiTXq6';
|
|
||||||
var height = 1;
|
|
||||||
var addressSizeBuf = new Buffer(1);
|
|
||||||
addressSizeBuf.writeUInt8(address.length);
|
|
||||||
var outputIndex = 5;
|
|
||||||
var txHex = '0100000001cc3ffe0638792c8b39328bb490caaefe2cf418f2ce0144956e0c22515f29724d010000006a473044022030ce9fa68d1a32abf0cd4adecf90fb998375b64fe887c6987278452b068ae74c022036a7d00d1c8af19e298e04f14294c807ebda51a20389ad751b4ff3c032cf8990012103acfcb348abb526526a9f63214639d79183871311c05b2eebc727adfdd016514fffffffff02f6ae7d04000000001976a9144455183e407ee4d3423858c8a3275918aedcd18e88aca99b9b08010000001976a9140beceae2c29bfde08d2b6d80b33067451c5887be88ac00000000';
|
|
||||||
var tx = new bitcore.Transaction(txHex);
|
|
||||||
var sats = tx.outputs[0].satoshis;
|
|
||||||
var satsBuf = new Buffer(8);
|
|
||||||
satsBuf.writeDoubleBE(sats);
|
|
||||||
|
|
||||||
it('should encode wallet transaction key' , function() {
|
|
||||||
encoding.encodeWalletTransactionKey(walletId, height, txid).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.transaction.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
new Buffer(txid, 'hex')
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet transaction key without a txid (all zeroes) or height (0)' , function() {
|
|
||||||
encoding.encodeWalletTransactionKey(walletId).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.transaction.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
new Buffer('00000000', 'hex'),
|
|
||||||
new Buffer(new Array(65).join('0'), 'hex')
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet transaction key', function() {
|
|
||||||
var walletTransactionKey = encoding.decodeWalletTransactionKey(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.transaction.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
new Buffer('00000001', 'hex')
|
|
||||||
]));
|
|
||||||
walletTransactionKey.walletId.should.equal(walletId);
|
|
||||||
walletTransactionKey.height.should.equal(height);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet utxo key', function() {
|
|
||||||
encoding.encodeWalletUtxoKey(walletId, txid, outputIndex).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.utxo.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
new Buffer(txid, 'hex'),
|
|
||||||
new Buffer('00000005', 'hex')]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet utxo key', function() {
|
|
||||||
var walletUtxoKey = encoding.decodeWalletUtxoKey(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.utxo.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
new Buffer(txid, 'hex'),
|
|
||||||
new Buffer('00000005', 'hex')]));
|
|
||||||
walletUtxoKey.walletId.should.equal(walletId);
|
|
||||||
walletUtxoKey.txid.should.equal(txid);
|
|
||||||
walletUtxoKey.outputIndex.should.equal(outputIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet utxo value', function() {
|
|
||||||
encoding.encodeWalletUtxoValue(height, sats, tx.outputs[0]._scriptBuffer).should.deep.equal(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
satsBuf,
|
|
||||||
tx.outputs[0]._scriptBuffer]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet utxo value', function() {
|
|
||||||
var walletUtxoValue = encoding.decodeWalletUtxoValue(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
satsBuf,
|
|
||||||
tx.outputs[0]._scriptBuffer]));
|
|
||||||
walletUtxoValue.height.should.equal(height);
|
|
||||||
walletUtxoValue.satoshis.should.equal(sats);
|
|
||||||
walletUtxoValue.script.should.deep.equal(tx.outputs[0]._scriptBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet utxo satoshis key', function() {
|
|
||||||
encoding.encodeWalletUtxoSatoshisKey(walletId, sats, txid, outputIndex).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.utxoSat.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
satsBuf,
|
|
||||||
new Buffer(txid, 'hex'),
|
|
||||||
new Buffer('00000005', 'hex')]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet utxo satoshis key', function() {
|
|
||||||
var walletUtxoSatoshisKey = encoding.decodeWalletUtxoSatoshisKey(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.utxoSat.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId),
|
|
||||||
satsBuf,
|
|
||||||
new Buffer(txid, 'hex'),
|
|
||||||
new Buffer('00000005', 'hex')]));
|
|
||||||
walletUtxoSatoshisKey.walletId.should.equal(walletId);
|
|
||||||
walletUtxoSatoshisKey.satoshis.should.equal(sats);
|
|
||||||
walletUtxoSatoshisKey.txid.should.equal(txid);
|
|
||||||
walletUtxoSatoshisKey.outputIndex.should.equal(outputIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet utxo satoshis value', function() {
|
|
||||||
encoding.encodeWalletUtxoSatoshisValue(height, tx.outputs[0]._scriptBuffer).should.deep.equal(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
tx.outputs[0]._scriptBuffer
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet utxo satoshis value', function() {
|
|
||||||
var walletUtxoSatoshisValue = encoding.decodeWalletUtxoSatoshisValue(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
tx.outputs[0]._scriptBuffer
|
|
||||||
]));
|
|
||||||
walletUtxoSatoshisValue.height.should.equal(height);
|
|
||||||
walletUtxoSatoshisValue.script.should.deep.equal(tx.outputs[0]._scriptBuffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet addresses key', function() {
|
|
||||||
encoding.encodeWalletAddressesKey(walletId).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.addresses.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId)
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet addresses key', function() {
|
|
||||||
encoding.decodeWalletAddressesKey(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.addresses.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId)
|
|
||||||
])).should.equal(walletId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet addresses value', function() {
|
|
||||||
encoding.encodeWalletAddressesValue(['a']).should.deep.equal(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
new Buffer('01', 'hex'),
|
|
||||||
new Buffer('a')]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet addresses value', function() {
|
|
||||||
encoding.decodeWalletAddressesValue(Buffer.concat([
|
|
||||||
new Buffer('00000001', 'hex'),
|
|
||||||
new Buffer('01', 'hex'),
|
|
||||||
new Buffer('a')])).should.deep.equal(['a']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet balance key', function() {
|
|
||||||
encoding.encodeWalletBalanceKey(walletId).should.deep.equal(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.balance.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId)
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet balance key', function() {
|
|
||||||
encoding.decodeWalletBalanceKey(Buffer.concat([
|
|
||||||
servicePrefix,
|
|
||||||
encoding.subKeyMap.balance.buffer,
|
|
||||||
new Buffer('0c', 'hex'),
|
|
||||||
new Buffer(walletId)
|
|
||||||
])).should.equal(walletId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should encode wallet balance value', function() {
|
|
||||||
encoding.encodeWalletBalanceValue(sats).should.deep.equal(satsBuf);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should decode wallet balance value', function() {
|
|
||||||
encoding.decodeWalletBalanceValue(satsBuf).should.equal(sats);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
var should = require('chai').should();
|
|
||||||
var utils = require('../../../lib/services/wallet-api/utils');
|
|
||||||
|
|
||||||
describe('Wallet-Api service utils', function() {
|
|
||||||
it('should create jsonl from obj', function() {
|
|
||||||
var obj = {
|
|
||||||
1: 'test',
|
|
||||||
'foo bar': 'test1',
|
|
||||||
array: [1,2,'test']
|
|
||||||
};
|
|
||||||
utils.toJSONL(obj).should.equal('{"1":"test","foo bar":"test1","array":[1,2,"test"]}\n');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user