Browse Source

Merge pull request #250 from isocolsky/explicit_fees

Support for smaller fees
activeAddress
Matias Alejo Garcia 10 years ago
parent
commit
75d46181af
  1. 2
      lib/model/txproposal.js
  2. 10
      lib/server.js
  3. 24
      package.json
  4. 49
      test/integration/server.js

2
lib/model/txproposal.js

@ -37,6 +37,7 @@ TxProposal.create = function(opts) {
x.outputOrder = _.shuffle(_.range(2)); x.outputOrder = _.shuffle(_.range(2));
x.fee = null; x.fee = null;
x.network = Bitcore.Address(x.toAddress).toObject().network; x.network = Bitcore.Address(x.toAddress).toObject().network;
x.feePerKb = opts.feePerKb;
return x; return x;
}; };
@ -68,6 +69,7 @@ TxProposal.fromObj = function(obj) {
x.outputOrder = obj.outputOrder; x.outputOrder = obj.outputOrder;
x.fee = obj.fee; x.fee = obj.fee;
x.network = obj.network; x.network = obj.network;
x.feePerKb = obj.feePerKb;
return x; return x;
}; };

10
lib/server.js

@ -731,6 +731,7 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
return cb(); return cb();
} }
} catch (ex) { } catch (ex) {
log.error('Error building Bitcore transaction', ex);
return cb(ex); return cb(ex);
} }
} }
@ -785,7 +786,8 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) {
* @param {number} opts.amount - Amount to transfer in satoshi. * @param {number} opts.amount - Amount to transfer in satoshi.
* @param {string} opts.message - A message to attach to this transaction. * @param {string} opts.message - A message to attach to this transaction.
* @param {string} opts.proposalSignature - S(toAddress|amount|message|payProUrl). Used by other copayers to verify the proposal. * @param {string} opts.proposalSignature - S(toAddress|amount|message|payProUrl). Used by other copayers to verify the proposal.
* @param {string} opts.payProUrl - Options: Paypro URL for peers to verify TX * @param {string} opts.feePerKb - Optional: Use an alternative fee per KB for this TX
* @param {string} opts.payProUrl - Optional: Paypro URL for peers to verify TX
* @returns {TxProposal} Transaction proposal. * @returns {TxProposal} Transaction proposal.
*/ */
WalletService.prototype.createTx = function(opts, cb) { WalletService.prototype.createTx = function(opts, cb) {
@ -794,6 +796,10 @@ WalletService.prototype.createTx = function(opts, cb) {
if (!Utils.checkRequired(opts, ['toAddress', 'amount', 'proposalSignature'])) if (!Utils.checkRequired(opts, ['toAddress', 'amount', 'proposalSignature']))
return cb(new ClientError('Required argument missing')); return cb(new ClientError('Required argument missing'));
var feePerKb = opts.feePerKb || 10000;
if (feePerKb < WalletUtils.MIN_FEE_PER_KB || feePerKb > WalletUtils.MAX_FEE_PER_KB)
return cb(new ClientError('Invalid fee per KB value'));
self._runLocked(cb, function(cb) { self._runLocked(cb, function(cb) {
self.getWallet({}, function(err, wallet) { self.getWallet({}, function(err, wallet) {
if (err) return cb(err); if (err) return cb(err);
@ -825,7 +831,6 @@ WalletService.prototype.createTx = function(opts, cb) {
if (opts.amount < Bitcore.Transaction.DUST_AMOUNT) if (opts.amount < Bitcore.Transaction.DUST_AMOUNT)
return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold')); return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold'));
var changeAddress = wallet.createAddress(true); var changeAddress = wallet.createAddress(true);
var txp = Model.TxProposal.create({ var txp = Model.TxProposal.create({
@ -835,6 +840,7 @@ WalletService.prototype.createTx = function(opts, cb) {
amount: opts.amount, amount: opts.amount,
message: opts.message, message: opts.message,
proposalSignature: opts.proposalSignature, proposalSignature: opts.proposalSignature,
feePerKb: feePerKb,
payProUrl: opts.payProUrl, payProUrl: opts.payProUrl,
changeAddress: changeAddress, changeAddress: changeAddress,
requiredSignatures: wallet.m, requiredSignatures: wallet.m,

24
package.json

@ -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.33", "version": "0.0.34",
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
"copay", "copay",
@ -19,8 +19,9 @@
}, },
"dependencies": { "dependencies": {
"async": "^0.9.0", "async": "^0.9.0",
"bitcore": "^0.11.6", "bitcore": "git://github.com/bitpay/bitcore.git#a4ac3f50d300b3f89fad02f9e38fc536ac90abdc",
"bitcore-wallet-utils": "0.0.13", "bitcore-explorers": "^0.10.3",
"bitcore-wallet-utils": "0.0.15",
"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",
@ -61,14 +62,11 @@
"test": "./node_modules/.bin/mocha", "test": "./node_modules/.bin/mocha",
"coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" "coveralls": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
}, },
"contributors": [ "contributors": [{
{ "name": "Ivan Socolsky",
"name": "Ivan Socolsky", "email": "ivan@bitpay.com"
"email": "ivan@bitpay.com" }, {
}, "name": "Matias Alejo Garcia",
{ "email": "ematiu@gmail.com"
"name": "Matias Alejo Garcia", }]
"email": "ematiu@gmail.com"
}
]
} }

49
test/integration/server.js

@ -184,13 +184,15 @@ helpers.stubAddressActivity = function(activeAddresses) {
helpers.clientSign = WalletUtils.signTxp; helpers.clientSign = WalletUtils.signTxp;
helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { helpers.createProposalOpts = function(toAddress, amount, message, signingKey, feePerKb) {
var opts = { var opts = {
toAddress: toAddress, toAddress: toAddress,
amount: helpers.toSatoshi(amount), amount: helpers.toSatoshi(amount),
message: message, message: message,
proposalSignature: null, proposalSignature: null,
}; };
if (feePerKb) opts.feePerKb = feePerKb;
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
try { try {
opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); opts.proposalSignature = WalletUtils.signMessage(hash, signingKey);
@ -1322,7 +1324,6 @@ describe('Wallet service', function() {
}); });
}); });
it('should fail to create tx with invalid proposal signature', function(done) { it('should fail to create tx with invalid proposal signature', function(done) {
helpers.stubUtxos(server, wallet, [100, 200], function() { helpers.stubUtxos(server, wallet, [100, 200], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, 'dummy'); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 80, null, 'dummy');
@ -1409,8 +1410,8 @@ describe('Wallet service', function() {
}); });
it('should fail to create tx when insufficient funds for fee', function(done) { it('should fail to create tx when insufficient funds for fee', function(done) {
helpers.stubUtxos(server, wallet, [100], function() { helpers.stubUtxos(server, wallet, 0.048222, function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 100, null, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.048200, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.exist(err); should.exist(err);
err.code.should.equal('INSUFFICIENTFUNDS'); err.code.should.equal('INSUFFICIENTFUNDS');
@ -1420,6 +1421,42 @@ describe('Wallet service', function() {
}); });
}); });
it('should scale fees according to tx size', function(done) {
helpers.stubUtxos(server, wallet, [1, 1, 1, 1], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 3.5, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
tx.getBitcoreTx()._estimateSize().should.be.within(1001, 1999);
tx.fee.should.equal(20000);
done();
});
});
});
it('should be possible to use a smaller fee', function(done) {
helpers.stubUtxos(server, wallet, 1, function() {
var txOpts = helpers.createProposalOpts('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.createProposalOpts('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);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
// Sign it to make sure Bitcore doesn't complain about the fees
server.signTx({
txProposalId: tx.id,
signatures: signatures,
}, function(err) {
should.not.exist(err);
done();
});
});
});
});
});
it('should fail to create tx for dust amount', function(done) { it('should fail to create tx for dust amount', function(done) {
helpers.stubUtxos(server, wallet, [1], function() { helpers.stubUtxos(server, wallet, [1], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0);
@ -1434,7 +1471,7 @@ describe('Wallet service', function() {
it('should fail to create tx that would return change for dust amount', function(done) { it('should fail to create tx that would return change for dust amount', function(done) {
helpers.stubUtxos(server, wallet, [1], function() { helpers.stubUtxos(server, wallet, [1], function() {
var fee = Bitcore.Transaction.FEE_PER_KB / 1e8; var fee = 10000 / 1e8;
var change = 0.00000001; var change = 0.00000001;
var amount = 1 - fee - change; var amount = 1 - fee - change;
@ -2446,7 +2483,7 @@ describe('Wallet service', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
helpers.stubUtxos(server, wallet, helpers.toSatoshi(_.range(4)), function() { helpers.stubUtxos(server, wallet, _.range(4), function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.01, null, TestData.copayers[0].privKey_1H_0);
async.eachSeries(_.range(3), function(i, next) { async.eachSeries(_.range(3), function(i, next) {
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {

Loading…
Cancel
Save