Merge pull request #306 from isocolsky/improve_fees
Support legacy clients
This commit is contained in:
commit
362d1a42bb
|
@ -9,7 +9,7 @@ var Address = Bitcore.Address;
|
||||||
var TxProposalAction = require('./txproposalaction');
|
var TxProposalAction = require('./txproposalaction');
|
||||||
|
|
||||||
function TxProposal() {
|
function TxProposal() {
|
||||||
this.version = '1.0.1';
|
this.version = '2.0.0';
|
||||||
};
|
};
|
||||||
|
|
||||||
TxProposal.Types = {
|
TxProposal.Types = {
|
||||||
|
|
|
@ -865,6 +865,13 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
var inputs = sortUtxos(utxos);
|
var inputs = sortUtxos(utxos);
|
||||||
|
|
||||||
var bitcoreTx, bitcoreError;
|
var bitcoreTx, bitcoreError;
|
||||||
|
var serializationOpts = {
|
||||||
|
disableIsFullySigned: true,
|
||||||
|
};
|
||||||
|
if (!_.startsWith(txp.version, '1.')) {
|
||||||
|
serializationOpts.disableSmallFees = true;
|
||||||
|
serializationOpts.disableLargeFees = true;
|
||||||
|
}
|
||||||
|
|
||||||
while (i < inputs.length) {
|
while (i < inputs.length) {
|
||||||
selected.push(inputs[i]);
|
selected.push(inputs[i]);
|
||||||
|
@ -876,11 +883,7 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
|
||||||
txp.setInputs(selected);
|
txp.setInputs(selected);
|
||||||
txp.estimateFee();
|
txp.estimateFee();
|
||||||
bitcoreTx = txp.getBitcoreTx();
|
bitcoreTx = txp.getBitcoreTx();
|
||||||
bitcoreError = bitcoreTx.getSerializationError({
|
bitcoreError = bitcoreTx.getSerializationError(serializationOpts);
|
||||||
disableIsFullySigned: true,
|
|
||||||
disableSmallFees: true,
|
|
||||||
disableLargeFees: true,
|
|
||||||
});
|
|
||||||
if (!bitcoreError) {
|
if (!bitcoreError) {
|
||||||
txp.fee = bitcoreTx.getFee();
|
txp.fee = bitcoreTx.getFee();
|
||||||
return cb();
|
return cb();
|
||||||
|
@ -1052,6 +1055,10 @@ WalletService.prototype.createTx = function(opts, cb) {
|
||||||
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
|
excludeUnconfirmedUtxos: !!opts.excludeUnconfirmedUtxos,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!self.clientVersion || _.startsWith(self.clientVersion, 'bwc-0.0.')) {
|
||||||
|
txp.version = '1.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
self._selectTxInputs(txp, function(err) {
|
self._selectTxInputs(txp, function(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
@ -1384,6 +1391,14 @@ WalletService.prototype.getPendingTxs = function(opts, cb) {
|
||||||
self.storage.fetchPendingTxs(self.walletId, function(err, txps) {
|
self.storage.fetchPendingTxs(self.walletId, function(err, txps) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
|
if (_.startsWith(self.clientVersion, 'bwc-0.0.')) {
|
||||||
|
var allLegacy = _.all(txps, function(txp) {
|
||||||
|
return _.startsWith(txp.version, '1.');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!allLegacy) return cb(new Error('Some spend proposals were created using a newer version. Please upgrade your client app.'))
|
||||||
|
}
|
||||||
|
|
||||||
_.each(txps, function(txp) {
|
_.each(txps, function(txp) {
|
||||||
txp.deleteLockTime = self.getRemainingDeleteLockTime(txp);
|
txp.deleteLockTime = self.getRemainingDeleteLockTime(txp);
|
||||||
});
|
});
|
||||||
|
@ -1534,7 +1549,12 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
||||||
amount = 0;
|
amount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function outputMap(o) { return { amount: o.amount, address: o.address } };
|
function outputMap(o) {
|
||||||
|
return {
|
||||||
|
amount: o.amount,
|
||||||
|
address: o.address
|
||||||
|
}
|
||||||
|
};
|
||||||
var newTx = {
|
var newTx = {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
action: action,
|
action: action,
|
||||||
|
@ -1542,7 +1562,9 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
||||||
fees: tx.fees,
|
fees: tx.fees,
|
||||||
time: tx.time,
|
time: tx.time,
|
||||||
addressTo: addressTo,
|
addressTo: addressTo,
|
||||||
outputs: _.map(_.filter(outputs, { isChange: false }), outputMap),
|
outputs: _.map(_.filter(outputs, {
|
||||||
|
isChange: false
|
||||||
|
}), outputMap),
|
||||||
confirmations: tx.confirmations,
|
confirmations: tx.confirmations,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1556,11 +1578,13 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
||||||
return _.pick(action, ['createdOn', 'type', 'copayerId', 'copayerName', 'comment']);
|
return _.pick(action, ['createdOn', 'type', 'copayerId', 'copayerName', 'comment']);
|
||||||
});
|
});
|
||||||
_.each(newTx.outputs, function(output) {
|
_.each(newTx.outputs, function(output) {
|
||||||
var query = { toAddress: output.address, amount: output.amount };
|
var query = {
|
||||||
var txpOut = _.find(proposal.outputs, query);
|
toAddress: output.address,
|
||||||
output.message = txpOut ? txpOut.message : null;
|
amount: output.amount
|
||||||
}
|
};
|
||||||
);
|
var txpOut = _.find(proposal.outputs, query);
|
||||||
|
output.message = txpOut ? txpOut.message : null;
|
||||||
|
});
|
||||||
// newTx.sentTs = proposal.sentTs;
|
// newTx.sentTs = proposal.sentTs;
|
||||||
// newTx.merchant = proposal.merchant;
|
// newTx.merchant = proposal.merchant;
|
||||||
//newTx.paymentAckMemo = proposal.paymentAckMemo;
|
//newTx.paymentAckMemo = proposal.paymentAckMemo;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "bitcore-wallet-service",
|
"name": "bitcore-wallet-service",
|
||||||
"description": "A service for Mutisig HD Bitcoin Wallets",
|
"description": "A service for Mutisig HD Bitcoin Wallets",
|
||||||
"author": "BitPay Inc",
|
"author": "BitPay Inc",
|
||||||
"version": "0.0.47",
|
"version": "0.1.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bitcoin",
|
"bitcoin",
|
||||||
"copay",
|
"copay",
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^0.9.0",
|
"async": "^0.9.0",
|
||||||
"bitcore": "^0.12.9",
|
"bitcore": "^0.12.9",
|
||||||
"bitcore-wallet-utils": "^0.0.23",
|
"bitcore-wallet-utils": "^0.1.0",
|
||||||
"body-parser": "^1.11.0",
|
"body-parser": "^1.11.0",
|
||||||
"coveralls": "^2.11.2",
|
"coveralls": "^2.11.2",
|
||||||
"email-validator": "^1.0.1",
|
"email-validator": "^1.0.1",
|
||||||
|
|
|
@ -35,6 +35,7 @@ helpers.getAuthServer = function(copayerId, cb) {
|
||||||
copayerId: copayerId,
|
copayerId: copayerId,
|
||||||
message: 'dummy',
|
message: 'dummy',
|
||||||
signature: 'dummy',
|
signature: 'dummy',
|
||||||
|
clientVersion: 'bwc-0.1.0',
|
||||||
}, function(err, server) {
|
}, function(err, server) {
|
||||||
verifyStub.restore();
|
verifyStub.restore();
|
||||||
if (err || !server) throw new Error('Could not login as copayerId ' + copayerId);
|
if (err || !server) throw new Error('Could not login as copayerId ' + copayerId);
|
||||||
|
@ -4249,4 +4250,144 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Legacy', function() {
|
||||||
|
describe('Fees', function() {
|
||||||
|
var server, wallet;
|
||||||
|
beforeEach(function(done) {
|
||||||
|
helpers.createAndJoinWallet(2, 3, function(s, w) {
|
||||||
|
server = s;
|
||||||
|
wallet = w;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a tx from legacy (bwc-0.0.*) client', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, [100, 200], function() {
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
|
|
||||||
|
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
||||||
|
verifyStub.returns(true);
|
||||||
|
WalletService.getInstanceWithAuth({
|
||||||
|
copayerId: wallet.copayers[0].id,
|
||||||
|
message: 'dummy',
|
||||||
|
signature: 'dummy',
|
||||||
|
clientVersion: 'bwc-0.0.40',
|
||||||
|
}, function(err, server) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(server);
|
||||||
|
verifyStub.restore();
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
tx.amount.should.equal(helpers.toSatoshi(80));
|
||||||
|
tx.fee.should.equal(WalletUtils.DEFAULT_FEE_PER_KB);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should return error when fetching new txps from legacy (bwc-0.0.*) client', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, [100, 200], function() {
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
|
||||||
|
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
||||||
|
verifyStub.returns(true);
|
||||||
|
WalletService.getInstanceWithAuth({
|
||||||
|
copayerId: wallet.copayers[0].id,
|
||||||
|
message: 'dummy',
|
||||||
|
signature: 'dummy',
|
||||||
|
clientVersion: 'bwc-0.0.40',
|
||||||
|
}, function(err, server) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(server);
|
||||||
|
verifyStub.restore();
|
||||||
|
server.getPendingTxs({}, function(err, txps) {
|
||||||
|
should.exist(err);
|
||||||
|
should.not.exist(txps);
|
||||||
|
err.toString().should.contain('created by a newer version');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should create a tx from legacy (bwc-0.0.*) client and sign it from newer client', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, [100, 200], function() {
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, 'some message', TestData.copayers[0].privKey_1H_0);
|
||||||
|
|
||||||
|
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
||||||
|
verifyStub.returns(true);
|
||||||
|
WalletService.getInstanceWithAuth({
|
||||||
|
copayerId: wallet.copayers[0].id,
|
||||||
|
message: 'dummy',
|
||||||
|
signature: 'dummy',
|
||||||
|
clientVersion: 'bwc-0.0.40',
|
||||||
|
}, function(err, server) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(server);
|
||||||
|
verifyStub.restore();
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(tx);
|
||||||
|
tx.amount.should.equal(helpers.toSatoshi(80));
|
||||||
|
tx.fee.should.equal(WalletUtils.DEFAULT_FEE_PER_KB);
|
||||||
|
helpers.getAuthServer(wallet.copayers[0].id, function(server) {
|
||||||
|
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
|
||||||
|
server.signTx({
|
||||||
|
txProposalId: tx.id,
|
||||||
|
signatures: signatures,
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should fail with insufficient fee when invoked from legacy (bwc-0.0.*) client', function(done) {
|
||||||
|
helpers.stubUtxos(server, wallet, 1, function() {
|
||||||
|
var verifyStub = sinon.stub(WalletService.prototype, '_verifySignature');
|
||||||
|
verifyStub.returns(true);
|
||||||
|
WalletService.getInstanceWithAuth({
|
||||||
|
copayerId: wallet.copayers[0].id,
|
||||||
|
message: 'dummy',
|
||||||
|
signature: 'dummy',
|
||||||
|
clientVersion: 'bwc-0.0.40',
|
||||||
|
}, function(err, server) {
|
||||||
|
should.not.exist(err);
|
||||||
|
should.exist(server);
|
||||||
|
verifyStub.restore();
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0);
|
||||||
|
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.exist(err);
|
||||||
|
err.code.should.equal('INSUFFICIENTFUNDS');
|
||||||
|
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0, 5000);
|
||||||
|
server.createTx(txOpts, function(err, tx) {
|
||||||
|
should.not.exist(err);
|
||||||
|
tx.fee.should.equal(5000);
|
||||||
|
|
||||||
|
// Sign it to make sure Bitcore doesn't complain about the fees
|
||||||
|
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
|
||||||
|
server.signTx({
|
||||||
|
txProposalId: tx.id,
|
||||||
|
signatures: signatures,
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -153,18 +153,8 @@ var aTxpOpts = function(type) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var aTXP = function(type) {
|
var aTXP = function(type) {
|
||||||
var version;
|
|
||||||
switch (type) {
|
|
||||||
case TxProposal.Types.MULTIPLEOUTPUTS:
|
|
||||||
version = '1.0.1';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
version = '1.0.0';
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var txp = {
|
var txp = {
|
||||||
"version": version,
|
"version": '2.0.0',
|
||||||
"type": type,
|
"type": type,
|
||||||
"createdOn": 1423146231,
|
"createdOn": 1423146231,
|
||||||
"id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e",
|
"id": "75c34f49-1ed6-255f-e9fd-0c71ae75ed1e",
|
||||||
|
|
Loading…
Reference in New Issue