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.fee = null;
x.network = Bitcore.Address(x.toAddress).toObject().network;
x.feePerKb = opts.feePerKb;
return x;
};
@ -68,6 +69,7 @@ TxProposal.fromObj = function(obj) {
x.outputOrder = obj.outputOrder;
x.fee = obj.fee;
x.network = obj.network;
x.feePerKb = obj.feePerKb;
return x;
};

10
lib/server.js

@ -731,6 +731,7 @@ WalletService.prototype._selectTxInputs = function(txp, cb) {
return cb();
}
} catch (ex) {
log.error('Error building Bitcore transaction', ex);
return cb(ex);
}
}
@ -785,7 +786,8 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) {
* @param {number} opts.amount - Amount to transfer in satoshi.
* @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.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.
*/
WalletService.prototype.createTx = function(opts, cb) {
@ -794,6 +796,10 @@ WalletService.prototype.createTx = function(opts, cb) {
if (!Utils.checkRequired(opts, ['toAddress', 'amount', 'proposalSignature']))
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.getWallet({}, function(err, wallet) {
if (err) return cb(err);
@ -825,7 +831,6 @@ WalletService.prototype.createTx = function(opts, cb) {
if (opts.amount < Bitcore.Transaction.DUST_AMOUNT)
return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold'));
var changeAddress = wallet.createAddress(true);
var txp = Model.TxProposal.create({
@ -835,6 +840,7 @@ WalletService.prototype.createTx = function(opts, cb) {
amount: opts.amount,
message: opts.message,
proposalSignature: opts.proposalSignature,
feePerKb: feePerKb,
payProUrl: opts.payProUrl,
changeAddress: changeAddress,
requiredSignatures: wallet.m,

24
package.json

@ -2,7 +2,7 @@
"name": "bitcore-wallet-service",
"description": "A service for Mutisig HD Bitcoin Wallets",
"author": "BitPay Inc",
"version": "0.0.33",
"version": "0.0.34",
"keywords": [
"bitcoin",
"copay",
@ -19,8 +19,9 @@
},
"dependencies": {
"async": "^0.9.0",
"bitcore": "^0.11.6",
"bitcore-wallet-utils": "0.0.13",
"bitcore": "git://github.com/bitpay/bitcore.git#a4ac3f50d300b3f89fad02f9e38fc536ac90abdc",
"bitcore-explorers": "^0.10.3",
"bitcore-wallet-utils": "0.0.15",
"body-parser": "^1.11.0",
"coveralls": "^2.11.2",
"email-validator": "^1.0.1",
@ -61,14 +62,11 @@
"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"
},
"contributors": [
{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.com"
},
{
"name": "Matias Alejo Garcia",
"email": "ematiu@gmail.com"
}
]
"contributors": [{
"name": "Ivan Socolsky",
"email": "ivan@bitpay.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.createProposalOpts = function(toAddress, amount, message, signingKey) {
helpers.createProposalOpts = function(toAddress, amount, message, signingKey, feePerKb) {
var opts = {
toAddress: toAddress,
amount: helpers.toSatoshi(amount),
message: message,
proposalSignature: null,
};
if (feePerKb) opts.feePerKb = feePerKb;
var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message);
try {
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) {
helpers.stubUtxos(server, wallet, [100, 200], function() {
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) {
helpers.stubUtxos(server, wallet, [100], function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 100, null, TestData.copayers[0].privKey_1H_0);
helpers.stubUtxos(server, wallet, 0.048222, function() {
var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.048200, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.exist(err);
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) {
helpers.stubUtxos(server, wallet, [1], function() {
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) {
helpers.stubUtxos(server, wallet, [1], function() {
var fee = Bitcore.Transaction.FEE_PER_KB / 1e8;
var fee = 10000 / 1e8;
var change = 0.00000001;
var amount = 1 - fee - change;
@ -2446,7 +2483,7 @@ describe('Wallet service', function() {
helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s;
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);
async.eachSeries(_.range(3), function(i, next) {
server.createTx(txOpts, function(err, tx) {

Loading…
Cancel
Save