Merge pull request #244 from matiu/feat/allow-delete-after-locktimeout
Feat/allow delete after locktimeout
This commit is contained in:
commit
053573b44c
|
@ -131,6 +131,18 @@ TxProposal.prototype.getActors = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getApprovers
|
||||||
|
*
|
||||||
|
* @return {String[]} copayerIds that approved the tx proposal (accept)
|
||||||
|
*/
|
||||||
|
TxProposal.prototype.getApprovers = function() {
|
||||||
|
return _.pluck(
|
||||||
|
_.filter(this.actions, {
|
||||||
|
type: 'accept'
|
||||||
|
}), 'copayerId');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getActionBy
|
* getActionBy
|
||||||
*
|
*
|
||||||
|
|
|
@ -32,6 +32,7 @@ var blockchainExplorerOpts;
|
||||||
var messageBroker;
|
var messageBroker;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of the Bitcore Wallet Service.
|
* Creates an instance of the Bitcore Wallet Service.
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -842,6 +843,26 @@ WalletService.prototype.removeWallet = function(opts, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WalletService.prototype.getRemainingDeleteLockTime = function(txp) {
|
||||||
|
var now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
var lockTimeRemaining = txp.createdOn + WalletService.deleteLockTime - now;
|
||||||
|
if (lockTimeRemaining < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// not the creator? need to wait
|
||||||
|
if (txp.creatorId !== this.copayerId)
|
||||||
|
return lockTimeRemaining;
|
||||||
|
|
||||||
|
// has other approvers? need to wait
|
||||||
|
var approvers = txp.getApprovers();
|
||||||
|
if (approvers.length > 1 || (approvers.length == 1 && approvers[0] !== this.copayerId))
|
||||||
|
return lockTimeRemaining;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* removePendingTx
|
* removePendingTx
|
||||||
*
|
*
|
||||||
|
@ -866,16 +887,12 @@ WalletService.prototype.removePendingTx = function(opts, cb) {
|
||||||
return cb(new ClientError('TXNOTPENDING', 'Transaction proposal not pending'));
|
return cb(new ClientError('TXNOTPENDING', 'Transaction proposal not pending'));
|
||||||
|
|
||||||
|
|
||||||
if (txp.creatorId !== self.copayerId)
|
var deleteLockTime = self.getRemainingDeleteLockTime(txp);
|
||||||
return cb(new ClientError('Only creators can remove pending proposals'));
|
if (deleteLockTime > 0) {
|
||||||
|
return cb(new ClientError('TXCANNOTREMOVE', 'Cannot remove this tx proposal during locktime'));
|
||||||
var actors = txp.getActors();
|
}
|
||||||
|
self.storage.removeTx(self.walletId, txp.id, function() {
|
||||||
if (actors.length > 1 || (actors.length == 1 && actors[0] !== self.copayerId))
|
self._notify('TxProposalRemoved', {}, cb);
|
||||||
return cb(new ClientError('TXACTIONED', 'Cannot remove a proposal signed/rejected by other copayers'));
|
|
||||||
|
|
||||||
self._notify('TxProposalRemoved', {}, function() {
|
|
||||||
self.storage.removeTx(self.walletId, txp.id, cb);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1102,6 +1119,10 @@ 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);
|
||||||
|
|
||||||
|
_.each(txps, function(txp) {
|
||||||
|
txp.deleteLockTime = self.getRemainingDeleteLockTime(txp);
|
||||||
|
});
|
||||||
|
|
||||||
return cb(null, txps);
|
return cb(null, txps);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1318,6 +1339,8 @@ WalletService.prototype.getTxHistory = function(opts, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// in seconds
|
||||||
|
WalletService.deleteLockTime = 24 * 3600;
|
||||||
|
|
||||||
WalletService.scanConfig = {
|
WalletService.scanConfig = {
|
||||||
SCAN_WINDOW: 20,
|
SCAN_WINDOW: 20,
|
||||||
|
|
|
@ -1269,6 +1269,8 @@ describe('Wallet service', function() {
|
||||||
server.getPendingTxs({}, function(err, txs) {
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
txs.length.should.equal(1);
|
txs.length.should.equal(1);
|
||||||
|
// creator
|
||||||
|
txs[0].deleteLockTime.should.equal(0);
|
||||||
server.getBalance({}, function(err, balance) {
|
server.getBalance({}, function(err, balance) {
|
||||||
should.not.exist(err);
|
should.not.exist(err);
|
||||||
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
balance.totalAmount.should.equal(helpers.toSatoshi(300));
|
||||||
|
@ -2657,6 +2659,7 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should allow creator to remove an unsigned TX', function(done) {
|
it('should allow creator to remove an unsigned TX', function(done) {
|
||||||
server.removePendingTx({
|
server.removePendingTx({
|
||||||
txProposalId: txp.id
|
txProposalId: txp.id
|
||||||
|
@ -2669,7 +2672,7 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow creator to remove an signed TX by himself', function(done) {
|
it('should allow creator to remove a signed TX by himself', function(done) {
|
||||||
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
|
var signatures = helpers.clientSign(txp, TestData.copayers[0].xPrivKey);
|
||||||
server.signTx({
|
server.signTx({
|
||||||
txProposalId: txp.id,
|
txProposalId: txp.id,
|
||||||
|
@ -2745,6 +2748,7 @@ describe('Wallet service', function() {
|
||||||
server2.removePendingTx({
|
server2.removePendingTx({
|
||||||
txProposalId: txp.id
|
txProposalId: txp.id
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
|
should.exist(err);
|
||||||
err.message.should.contain('creators');
|
err.message.should.contain('creators');
|
||||||
server2.getPendingTxs({}, function(err, txs) {
|
server2.getPendingTxs({}, function(err, txs) {
|
||||||
txs.length.should.equal(1);
|
txs.length.should.equal(1);
|
||||||
|
@ -2754,7 +2758,7 @@ describe('Wallet service', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow creator copayer to remove an TX signed by other copayer', function(done) {
|
it('should not allow creator copayer to remove a TX signed by other copayer, in less than 24hrs', function(done) {
|
||||||
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
|
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
|
||||||
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
|
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
|
||||||
server2.signTx({
|
server2.signTx({
|
||||||
|
@ -2765,13 +2769,83 @@ describe('Wallet service', function() {
|
||||||
server.removePendingTx({
|
server.removePendingTx({
|
||||||
txProposalId: txp.id
|
txProposalId: txp.id
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
err.code.should.equal('TXACTIONED');
|
err.code.should.equal('TXCANNOTREMOVE');
|
||||||
err.message.should.contain('other copayers');
|
err.message.should.contain('Cannot remove');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow creator copayer to remove a TX rejected by other copayer, in less than 24hrs', function(done) {
|
||||||
|
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
|
||||||
|
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
|
||||||
|
server2.rejectTx({
|
||||||
|
txProposalId: txp.id,
|
||||||
|
signatures: signatures,
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
server.removePendingTx({
|
||||||
|
txProposalId: txp.id
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow creator copayer to remove a TX signed by other copayer, after 24hrs', function(done) {
|
||||||
|
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
|
||||||
|
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
|
||||||
|
server2.signTx({
|
||||||
|
txProposalId: txp.id,
|
||||||
|
signatures: signatures,
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
server.getPendingTxs({}, function(err, txs) {
|
||||||
|
should.not.exist(err);
|
||||||
|
txs[0].deleteLockTime.should.be.above(WalletService.deleteLockTime-10);
|
||||||
|
|
||||||
|
var clock = sinon.useFakeTimers(Date.now() + 1 + 24 * 3600 * 1000);
|
||||||
|
server.removePendingTx({
|
||||||
|
txProposalId: txp.id
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clock.restore();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow other copayer to remove a TX signed, after 24hrs', function(done) {
|
||||||
|
helpers.getAuthServer(wallet.copayers[1].id, function(server2) {
|
||||||
|
var signatures = helpers.clientSign(txp, TestData.copayers[1].xPrivKey);
|
||||||
|
server2.signTx({
|
||||||
|
txProposalId: txp.id,
|
||||||
|
signatures: signatures,
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
|
||||||
|
var clock = sinon.useFakeTimers(Date.now() + 1 + 24 * 3600 * 1000);
|
||||||
|
server2.removePendingTx({
|
||||||
|
txProposalId: txp.id
|
||||||
|
}, function(err) {
|
||||||
|
should.not.exist(err);
|
||||||
|
clock.restore();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#getTxHistory', function() {
|
describe('#getTxHistory', function() {
|
||||||
|
|
Loading…
Reference in New Issue