Autofill LastLedgerSequence for multisigned transactions

This commit is contained in:
wltsmrz
2015-09-21 10:21:20 -07:00
parent 60c604fbe6
commit de67570230
4 changed files with 172 additions and 123 deletions

View File

@@ -45,6 +45,7 @@ function Transaction(remote) {
? this.remote.automatic_resubmission
: true;
this._maxFee = remoteExists ? this.remote.max_fee : undefined;
this._lastLedgerOffset = remoteExists ? this.remote.last_ledger_offset : 3;
this.state = 'unsubmitted';
this.finalized = false;
this.previousSigningHash = undefined;
@@ -582,19 +583,30 @@ Transaction.prototype.clientID = function(id) {
return this;
};
/**
* Set LastLedgerSequence as the absolute last ledger sequence the transaction
* is valid for. LastLedgerSequence is set automatically if not set using this
* method
*
* @param {Number} ledger index
*/
Transaction.prototype.setLastLedgerSequenceOffset = function(offset) {
this._lastLedgerOffset = offset;
};
Transaction.prototype.setLastLedgerSequence =
Transaction.prototype.getLastLedgerSequenceOffset = function() {
return this._lastLedgerOffset;
};
Transaction.prototype.lastLedger =
Transaction.prototype.setLastLedger =
Transaction.prototype.lastLedger = function(sequence) {
this._setUInt32('LastLedgerSequence', sequence);
Transaction.prototype.setLastLedgerSequence = function(sequence) {
if (!_.isUndefined(sequence)) {
this._setUInt32('LastLedgerSequence', sequence);
} else {
// Autofill LastLedgerSequence
assert(this.remote, 'Unable to set LastLedgerSequence, missing Remote');
this._setUInt32('LastLedgerSequence',
this.remote.getLedgerSequence() + 1
+ this.getLastLedgerSequenceOffset());
}
this._setLastLedger = true;
return this;
};
@@ -1479,7 +1491,7 @@ Transaction.prototype.summary = function() {
submissionAttempts: this.attempts,
submitIndex: this.submitIndex,
initialSubmitIndex: this.initialSubmitIndex,
lastLedgerSequence: this.lastLedgerSequence,
lastLedgerSequence: this.tx_json.LastLedgerSequence,
state: this.state,
finalized: this.finalized
};
@@ -1616,30 +1628,44 @@ Transaction.prototype.setSigners = function(signers) {
Transaction.prototype.addMultiSigner = function(signer) {
assert(UInt160.is_valid(signer.Account), 'Signer must have a valid Account');
if (_.isUndefined(this.multi_signers)) {
this.multi_signers = [];
if (_.isUndefined(this.tx_json.Signers)) {
this.tx_json.Signers = [];
}
this.multi_signers.push({Signer: signer});
this.tx_json.Signers.push({Signer: signer});
this.multi_signers.sort((a, b) => {
this.tx_json.Signers.sort((a, b) => {
return UInt160.from_json(a.Signer.Account)
.cmp(UInt160.from_json(b.Signer.Account));
});
return this;
};
Transaction.prototype.hasMultiSigners = function() {
return !_.isEmpty(this.multi_signers);
return !_.isEmpty(this.tx_json.Signers);
};
Transaction.prototype.getMultiSigners = function() {
return this.multi_signers;
return this.tx_json.Signers;
};
Transaction.prototype.getMultiSigningJson = function() {
assert(this.tx_json.Sequence, 'Sequence must be set before multi-signing');
assert(this.tx_json.Fee, 'Fee must be set before multi-signing');
const signingTx = Transaction.from_json(this.tx_json);
if (_.isUndefined(this.tx_json.LastLedgerSequence)) {
// Auto-fill LastLedgerSequence
this.setLastLedgerSequence();
}
const cleanedJson = _.omit(this.tx_json, [
'SigningPubKey',
'Signers',
'TxnSignature'
]);
const signingTx = Transaction.from_json(cleanedJson);
signingTx.remote = this.remote;
signingTx.setSigningPubKey('');
signingTx.setCanonicalFlag();

View File

@@ -27,7 +27,6 @@ function TransactionManager(account) {
this._maxFee = this._remote.max_fee;
this._maxAttempts = this._remote.max_attempts;
this._submissionTimeout = this._remote.submission_timeout;
this._lastLedgerOffset = this._remote.last_ledger_offset;
this._pending = new PendingQueue();
this._account.on('transaction-outbound', function(res) {
@@ -521,17 +520,6 @@ TransactionManager.prototype._resubmit = function(ledgers_, pending_) {
TransactionManager.prototype._prepareRequest = function(tx) {
const submitRequest = this._remote.requestSubmit();
if (tx.hasMultiSigners()) {
tx.setSigningPubKey('');
if (this._remote.local_signing) {
tx.setSigners(tx.getMultiSigners());
} else {
submitRequest.message.command = 'submit_multisigned';
submitRequest.message.Signers = tx.getMultiSigners();
}
}
if (this._remote.local_signing) {
tx.sign();
@@ -541,6 +529,10 @@ TransactionManager.prototype._prepareRequest = function(tx) {
const hash = tx.hash(null, null, serialized);
tx.addId(hash);
} else {
if (tx.hasMultiSigners()) {
submitRequest.message.command = 'submit_multisigned';
}
// ND: `build_path` is completely ignored when doing local signing as
// `Paths` is a component of the signed blob, the `tx_blob` is signed,
// sealed and delivered, and the txn unmodified.
@@ -580,6 +572,11 @@ TransactionManager.prototype._request = function(tx) {
return;
}
if (Number(tx.tx_json.Fee) > tx._maxFee) {
tx.emit('error', new RippleError('tejMaxFeeExceeded'));
return;
}
if (remote.trace) {
log.info('submit transaction:', tx.tx_json);
}
@@ -684,24 +681,12 @@ TransactionManager.prototype._request = function(tx) {
}
}
tx.submitIndex = this._remote._ledger_current_index;
tx.submitIndex = this._remote.getLedgerSequence() + 1;
if (tx.attempts === 0) {
tx.initialSubmitIndex = tx.submitIndex;
}
if (!tx._setLastLedger && !tx.hasMultiSigners()) {
// Honor LastLedgerSequence set with tx.lastLedger()
tx.tx_json.LastLedgerSequence = tx.initialSubmitIndex
+ this._lastLedgerOffset;
}
tx.lastLedgerSequence = tx.tx_json.LastLedgerSequence;
if (remote.local_signing) {
tx.sign();
}
const submitRequest = this._prepareRequest(tx);
submitRequest.once('error', submitted);
submitRequest.once('success', submitted);
@@ -743,8 +728,13 @@ TransactionManager.prototype.submit = function(tx) {
tx.setSequence(this._nextSequence++);
}
if (_.isUndefined(tx.tx_json.LastLedgerSequence)) {
tx.setLastLedgerSequence();
}
if (tx.hasMultiSigners()) {
tx.setResubmittable(false);
tx.setSigningPubKey('');
}
tx.once('cleanup', function() {

View File

@@ -518,7 +518,9 @@ describe('TransactionManager', function() {
break;
}
});
/* eslint-disable no-unused-vars */
rippled.once('request_submit', function(m, req) {
/* eslint-enable no-unused-vars */
req.sendJSON(lodash.extend({}, LEDGER, {
ledger_index: transaction.tx_json.LastLedgerSequence + 1
}));
@@ -574,7 +576,9 @@ describe('TransactionManager', function() {
req.sendResponse(SUBMIT_TEF_RESPONSE, {id: m.id});
});
/* eslint-disable no-unused-vars */
rippled.once('request_submit', function(m, req) {
/* eslint-enable no-unused-vars */
transaction.once('resubmitted', function() {
receivedResubmitted = true;
req.sendJSON(lodash.extend({}, LEDGER, {
@@ -634,7 +638,9 @@ describe('TransactionManager', function() {
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
});
/* eslint-disable no-unused-vars */
rippled.once('request_submit', function(m, req) {
/* eslint-enable no-unused-vars */
transaction.once('resubmitted', function() {
receivedResubmitted = true;
req.sendJSON(lodash.extend({}, LEDGER, {
@@ -690,7 +696,7 @@ describe('TransactionManager', function() {
assert.strictEqual(summary.submissionAttempts, 0);
assert.strictEqual(summary.submitIndex, undefined);
assert.strictEqual(summary.initialSubmitIndex, undefined);
assert.strictEqual(summary.lastLedgerSequence, undefined);
assert.strictEqual(summary.lastLedgerSequence, remote.getLedgerSequence() + 1 + Remote.DEFAULTS.last_ledger_offset);
assert.strictEqual(summary.state, 'failed');
assert.strictEqual(summary.finalized, true);
assert.deepEqual(summary.result, {
@@ -799,7 +805,9 @@ describe('TransactionManager', function() {
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
});
/* eslint-disable no-unused-vars */
rippled.once('request_submit', function(m, req) {
/* eslint-enable no-unused-vars */
transaction.once('resubmitted', function() {
receivedResubmitted = true;
});
@@ -857,6 +865,7 @@ describe('TransactionManager', function() {
receivedSubmitted = true;
});
/* eslint-disable no-unused-vars */
rippled.on('request_submit', function(m, req) {
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
transaction.tx_json).to_hex());
@@ -867,7 +876,9 @@ describe('TransactionManager', function() {
req.sendResponse(SUBMIT_TOO_BUSY_ERROR, {id: m.id});
});
/* eslint-disable no-unused-vars */
rippled.once('request_submit', function(m, req) {
/* eslint-enable no-unused-vars */
transaction.once('resubmitted', function() {
receivedResubmitted = true;
req.sendJSON(lodash.extend({}, LEDGER, {

View File

@@ -983,9 +983,6 @@ describe('Transaction', function() {
it('Set LastLedgerSequence', function() {
const transaction = new Transaction();
assert.throws(function() {
transaction.lastLedger('a');
}, /Error: LastLedgerSequence must be a valid UInt32/);
assert.throws(function() {
transaction.setLastLedgerSequence('a');
}, /Error: LastLedgerSequence must be a valid UInt32/);
@@ -1945,6 +1942,8 @@ describe('Transaction', function() {
it('Submit transaction', function(done) {
const remote = new Remote();
remote._ledger_current_index = 1;
const transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
assert.strictEqual(transaction.callback, undefined);
@@ -1989,6 +1988,8 @@ describe('Transaction', function() {
it('Submit transaction - submission error', function(done) {
const remote = new Remote();
remote._ledger_current_index = 1;
const transaction = new Transaction(remote).accountSet('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
const account = remote.addAccount('r36xtKNKR43SeXnGn7kN4r4JdQzcrkqpWe');
@@ -2107,82 +2108,6 @@ describe('Transaction', function() {
});
});
it('Add multisigner', function() {
const transaction = new Transaction();
const s1 = {
Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
TxnSignature: '304402203020865BDC995431325C371E6A3CE89BFC40597D9CFAF77DBB16E9D159824EA402203645A6462A6DCEC7B5D0811882DC54CEA66258A227A2762BE6EFCD9EB62C27BF',
SigningPubKey: '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE'
};
const s2 = {
Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW',
TxnSignature: '30450221009C84E455DC199A7DB4B800D68C92269D60972E8850AFC0D50B1AE6B08BBB02EA02206FA93A560BE96844DF7D96D07F6400EF9534A32FBA352DD10E855DA8923A3AF8',
SigningPubKey: '028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF'
};
transaction.addMultiSigner(s1);
transaction.addMultiSigner(s2);
assert.deepEqual(transaction.getMultiSigners(), [
{Signer: s2}, {Signer: s1}]);
});
it('Get multisign data', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0
});
transaction.setSigningPubKey('');
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const d1 = transaction.multiSigningData(a1);
const tbytes = ripple.SerializedObject.from_json(
lodash.merge(transaction.tx_json, {SigningPubKey: ''})).buffer;
const abytes = ripple.UInt160.from_json(a1).to_bytes();
const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES;
assert.deepEqual(d1.buffer, prefix.concat(tbytes, abytes));
});
it('Multisign', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0
});
const multiSigningJson = transaction.getMultiSigningJson();
const t1 = Transaction.from_json(multiSigningJson);
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const a2 = 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW';
const s1 = t1.multiSign(a1, 'alice');
assert.strictEqual(s1.Account, a1);
assert.strictEqual(s1.SigningPubKey, '0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C052');
assert.strictEqual(s1.TxnSignature, '30440220611256E46B2946152695FFEF34D5C71BB3AE569C3D919A270BFBCA9ADF260D9202202FAE24FC8A575FE3265A6D7CFA596094A7950E0011706431A11C2A9ABEF60B3B');
const s2 = t1.multiSign(a2, 'bob');
assert.strictEqual(s2.Account, a2);
assert.strictEqual(s2.SigningPubKey, '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE');
assert.strictEqual(s2.TxnSignature, '3044022067F769BE0A4CC2B4F26E7B52B366F861FED02DA0F564F98B44009C8181A9655702206D882919139DF8E9D7F2FC1DD54D8B4FEAC40203349AE21519FD388925A4DE83');
transaction.addMultiSigner(s1);
transaction.addMultiSigner(s2);
assert.deepEqual(transaction.getMultiSigners(), [
{Signer: s2},
{Signer: s1}
]);
});
it('Construct SuspendedPaymentCreate transaction', function() {
const transaction = new Transaction().suspendedPaymentCreate({
account: 'rsLEU1TPdCJPPysqhWYw9jD97xtG5WqSJm',
@@ -2294,4 +2219,101 @@ describe('Transaction', function() {
OfferSequence: 1234
});
});
it('Add multisigner', function() {
const transaction = new Transaction();
const s1 = {
Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
TxnSignature: '304402203020865BDC995431325C371E6A3CE89BFC40597D9CFAF77DBB16E9D159824EA402203645A6462A6DCEC7B5D0811882DC54CEA66258A227A2762BE6EFCD9EB62C27BF',
SigningPubKey: '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE'
};
const s2 = {
Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW',
TxnSignature: '30450221009C84E455DC199A7DB4B800D68C92269D60972E8850AFC0D50B1AE6B08BBB02EA02206FA93A560BE96844DF7D96D07F6400EF9534A32FBA352DD10E855DA8923A3AF8',
SigningPubKey: '028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF'
};
transaction.addMultiSigner(s1);
transaction.addMultiSigner(s2);
assert.deepEqual(transaction.getMultiSigners(), [
{Signer: s2}, {Signer: s1}]);
});
it('Get multisign data', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0
});
transaction.setSigningPubKey('');
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const d1 = transaction.multiSigningData(a1);
const tbytes = ripple.SerializedObject.from_json(
lodash.merge(transaction.tx_json, {SigningPubKey: ''})).buffer;
const abytes = ripple.UInt160.from_json(a1).to_bytes();
const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES;
assert.deepEqual(d1.buffer, prefix.concat(tbytes, abytes));
});
it('Multisign', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0,
LastLedgerSequence: 1
});
const multiSigningJson = transaction.getMultiSigningJson();
const t1 = Transaction.from_json(multiSigningJson);
const a1 = 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK';
const a2 = 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW';
const s1 = t1.multiSign(a1, 'alice');
assert.deepEqual(s1, {
Account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
TxnSignature: '3045022100DB13DC794DDFA1E27D099CDBFC7DB5B1EE892AD1725B0CEEE97D8B1C4C2055C7022030B3372C96D08106594B3CF8CDF88E05CC6260C51954F02387289CB69B839D7A',
SigningPubKey: '0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C052'
});
const s2 = t1.multiSign(a2, 'bob');
assert.deepEqual(s2, {
Account: 'rH4KEcG9dEwGwpn6AyoWK9cZPLL4RLSmWW',
TxnSignature: '304402207A22109088069C5ABE3E961C2F85B2B8111C5666C869E8BA3F2A57C2ECEA7FC402205F9D87FB42266CC498FCE9B4904955D0E6D5F44D092596F5DE3E25843F6D10AB',
SigningPubKey: '02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE'
});
transaction.addMultiSigner(s1);
transaction.addMultiSigner(s2);
assert.deepEqual(transaction.getMultiSigners(), [
{Signer: s2},
{Signer: s1}
]);
});
it('Multisign -- missing LastLedgerSequence', function() {
const transaction = Transaction.from_json({
Account: 'rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn',
Sequence: 1,
Fee: '100',
TransactionType: 'AccountSet',
Flags: 0
});
assert.throws(function() {
transaction.getMultiSigningJson();
});
});
});