flocore-node/regtest/utils.js
Chris Kleeschulte 09ff858e81 wip
2017-06-05 08:17:24 -04:00

429 lines
10 KiB
JavaScript

'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;