Merge pull request #340 from matiu/feat/rawTx

add transaction raw HEX if tx status is "accepted"
This commit is contained in:
Ivan Socolsky 2015-09-02 13:36:03 -03:00
commit 3f1bae6f25
3 changed files with 228 additions and 169 deletions

View File

@ -260,7 +260,7 @@ TxProposal.prototype.addAction = function(copayerId, type, comment, signatures,
this._updateStatus(); this._updateStatus();
}; };
TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) { TxProposal.prototype._addSignaturesToBitcoreTx = function(tx, signatures, xpub) {
var self = this; var self = this;
if (signatures.length != this.inputs.length) if (signatures.length != this.inputs.length)
@ -280,12 +280,12 @@ TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) {
sigtype: Bitcore.crypto.Signature.SIGHASH_ALL, sigtype: Bitcore.crypto.Signature.SIGHASH_ALL,
publicKey: pub, publicKey: pub,
}; };
t.inputs[i].addSignature(t, s); tx.inputs[i].addSignature(tx, s);
i++; i++;
} catch (e) {}; } catch (e) {};
}); });
if (i != t.inputs.length) if (i != tx.inputs.length)
throw new Error('Wrong signatures'); throw new Error('Wrong signatures');
}; };
@ -293,10 +293,14 @@ TxProposal.prototype._addSignaturesToBitcoreTx = function(t, signatures, xpub) {
TxProposal.prototype.sign = function(copayerId, signatures, xpub) { TxProposal.prototype.sign = function(copayerId, signatures, xpub) {
try { try {
// Tests signatures are OK // Tests signatures are OK
var t = this.getBitcoreTx(); var tx = this.getBitcoreTx();
this._addSignaturesToBitcoreTx(t, signatures, xpub); this._addSignaturesToBitcoreTx(tx, signatures, xpub);
this.addAction(copayerId, 'accept', null, signatures, xpub); this.addAction(copayerId, 'accept', null, signatures, xpub);
if (this.status == 'accepted')
this.raw = tx.uncheckedSerialize();
return true; return true;
} catch (e) { } catch (e) {
return false; return false;

View File

@ -162,6 +162,10 @@ Storage.prototype._completeTxData = function(walletId, txs, cb) {
_.each(tx.actions, function(action) { _.each(tx.actions, function(action) {
action.copayerName = wallet.getCopayer(action.copayerId).name; action.copayerName = wallet.getCopayer(action.copayerId).name;
}); });
if (tx.status=='accepted')
tx.raw = tx.getRawTx();
}); });
return cb(null, txs); return cb(null, txs);
}); });

View File

@ -2773,173 +2773,212 @@ describe('Wallet service', function() {
}); });
describe('#signTx', function() { describe('#signTx', function() {
var server, wallet, txid; describe('1-1', function() {
var server, wallet, txid;
beforeEach(function(done) { beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) { helpers.createAndJoinWallet(1, 1, function(s, w) {
server = s; server = s;
wallet = w; wallet = w;
helpers.stubUtxos(server, wallet, _.range(1, 9), function() { helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 20, null, TestData.copayers[0].privKey_1H_0); var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 20, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) { server.createTx(txOpts, function(err, tx) {
should.not.exist(err); should.not.exist(err);
should.exist(tx); should.exist(tx);
txid = tx.id; txid = tx.id;
done(); done();
});
}); });
}); });
}); });
});
it('should sign a TX with multiple inputs, different paths', function(done) { it('should sign a TX with multiple inputs, different paths, and return raw', function(done) {
server.getPendingTxs({}, function(err, txs) { server.getPendingTxs({}, function(err, txs) {
var tx = txs[0]; var tx = txs[0];
tx.id.should.equal(txid); tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey); should.not.exist(tx.raw);
server.signTx({ server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.not.exist(err);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
var tx = txs[0];
tx.id.should.equal(txid);
var actors = tx.getActors();
actors.length.should.equal(1);
actors[0].should.equal(wallet.copayers[0].id);
tx.getActionBy(wallet.copayers[0].id).type.should.equal('accept');
done();
});
});
});
});
it('should fail to sign with a xpriv from other copayer', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[1].xPrivKey);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.code.should.equal('BAD_SIGNATURES');
done();
});
});
});
it('should fail if one signature is broken', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
signatures[0] = 1;
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.message.should.contain('signatures');
done();
});
});
});
it('should fail on invalid signature', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = ['11', '22', '33', '44', '55'];
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.message.should.contain('Bad signatures');
done();
});
});
});
it('should fail on wrong number of invalid signatures', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = _.take(helpers.clientSign(tx, TestData.copayers[0].xPrivKey), tx.inputs.length - 1);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.message.should.contain('Bad signatures');
done();
});
});
});
it('should fail when signing a TX previously rejected', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
server.rejectTx({
txProposalId: txid, txProposalId: txid,
signatures: signatures,
}, function(err, txp) {
should.not.exist(err);
txp.status.should.equal('accepted');
// The raw Tx should contain the Signatures.
txp.raw.should.contain(signatures[0]);
// Get pending should also contains the raw TX
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
should.not.exist(err);
tx.status.should.equal('accepted');
tx.raw.should.contain(signatures[0]);
done();
});
});
});
});
});
describe('Multisign 2-3', function() {
var server, wallet, txid;
beforeEach(function(done) {
helpers.createAndJoinWallet(2, 3, function(s, w) {
server = s;
wallet = w;
helpers.stubUtxos(server, wallet, _.range(1, 9), function() {
var txOpts = helpers.createSimpleProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 20, null, TestData.copayers[0].privKey_1H_0);
server.createTx(txOpts, function(err, tx) {
should.not.exist(err);
should.exist(tx);
txid = tx.id;
done();
});
});
});
});
it('should sign a TX with multiple inputs, different paths', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err, txp) {
should.not.exist(err);
should.not.exist(tx.raw);
server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
var tx = txs[0];
tx.id.should.equal(txid);
var actors = tx.getActors();
actors.length.should.equal(1);
actors[0].should.equal(wallet.copayers[0].id);
tx.getActionBy(wallet.copayers[0].id).type.should.equal('accept');
done();
});
});
});
});
it('should fail to sign with a xpriv from other copayer', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[1].xPrivKey);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) { }, function(err) {
err.code.should.contain('COPAYER_VOTED'); err.code.should.equal('BAD_SIGNATURES');
done(); done();
}); });
}); });
}); });
});
it('should fail when rejected a previously signed TX', function(done) { it('should fail if one signature is broken', function(done) {
server.getPendingTxs({}, function(err, txs) { server.getPendingTxs({}, function(err, txs) {
var tx = txs[0]; var tx = txs[0];
tx.id.should.equal(txid); tx.id.should.equal(txid);
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
signatures[0] = 1;
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.message.should.contain('signatures');
done();
});
});
});
it('should fail on invalid signature', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = ['11', '22', '33', '44', '55'];
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.message.should.contain('Bad signatures');
done();
});
});
});
it('should fail on wrong number of invalid signatures', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
var signatures = _.take(helpers.clientSign(tx, TestData.copayers[0].xPrivKey), tx.inputs.length - 1);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.message.should.contain('Bad signatures');
done();
});
});
});
it('should fail when signing a TX previously rejected', function(done) {
server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
server.rejectTx({
txProposalId: txid,
}, function(err) {
var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey); var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
server.signTx({ server.signTx({
txProposalId: txid, txProposalId: txid,
signatures: signatures, signatures: signatures,
}, function(err) { }, function(err) {
err.code.should.contain('COPAYER_VOTED'); server.rejectTx({
done(); txProposalId: txid,
}, function(err) {
err.code.should.contain('COPAYER_VOTED');
done();
});
}); });
}); });
}); });
});
it('should fail to sign a non-pending TX', function(done) { it('should fail when rejected a previously signed TX', function(done) {
async.waterfall([ server.getPendingTxs({}, function(err, txs) {
var tx = txs[0];
tx.id.should.equal(txid);
function(next) {
server.rejectTx({ server.rejectTx({
txProposalId: txid, txProposalId: txid,
reason: 'some reason',
}, function(err) { }, function(err) {
should.not.exist(err); var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey);
next(); server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
err.code.should.contain('COPAYER_VOTED');
done();
});
}); });
}, });
function(next) { });
helpers.getAuthServer(wallet.copayers[1].id, function(server) {
it('should fail to sign a non-pending TX', function(done) {
async.waterfall([
function(next) {
server.rejectTx({ server.rejectTx({
txProposalId: txid, txProposalId: txid,
reason: 'some reason', reason: 'some reason',
@ -2947,34 +2986,45 @@ describe('Wallet service', function() {
should.not.exist(err); should.not.exist(err);
next(); next();
}); });
}); },
}, function(next) {
function(next) { helpers.getAuthServer(wallet.copayers[1].id, function(server) {
server.getPendingTxs({}, function(err, txs) { server.rejectTx({
should.not.exist(err);
txs.should.be.empty;
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[2].id, function(server) {
server.getTx({
txProposalId: txid
}, function(err, tx) {
should.not.exist(err);
var signatures = helpers.clientSign(tx, TestData.copayers[2].xPrivKey);
server.signTx({
txProposalId: txid, txProposalId: txid,
signatures: signatures, reason: 'some reason',
}, function(err) { }, function(err) {
should.exist(err); should.not.exist(err);
err.code.should.equal('TX_NOT_PENDING'); next();
done();
}); });
}); });
}); },
}, function(next) {
]); server.getPendingTxs({}, function(err, txs) {
should.not.exist(err);
txs.should.be.empty;
next();
});
},
function(next) {
helpers.getAuthServer(wallet.copayers[2].id, function(server) {
server.getTx({
txProposalId: txid
}, function(err, tx) {
should.not.exist(err);
var signatures = helpers.clientSign(tx, TestData.copayers[2].xPrivKey);
server.signTx({
txProposalId: txid,
signatures: signatures,
}, function(err) {
should.exist(err);
err.code.should.equal('TX_NOT_PENDING');
done();
});
});
});
},
]);
});
}); });
}); });
@ -3017,6 +3067,7 @@ describe('Wallet service', function() {
txProposalId: txpid txProposalId: txpid
}, function(err, txp) { }, function(err, txp) {
should.not.exist(err); should.not.exist(err);
should.not.exist(txp.raw);
txp.txid.should.equal('999'); txp.txid.should.equal('999');
txp.isBroadcasted().should.be.true; txp.isBroadcasted().should.be.true;
txp.broadcastedOn.should.equal(1234); txp.broadcastedOn.should.equal(1234);