mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
Add switch to disable transaction resubmission
* Add Transaction.setResumittable(Boolean) * Add Transaction.isResumittable() -> Boolean * Add automatic_resubmission (Boolean) config option to Remote
This commit is contained in:
@@ -135,6 +135,9 @@ function Remote(options = {}) {
|
|||||||
if (typeof this.submission_timeout !== 'number') {
|
if (typeof this.submission_timeout !== 'number') {
|
||||||
throw new TypeError('submission_timeout must be a number');
|
throw new TypeError('submission_timeout must be a number');
|
||||||
}
|
}
|
||||||
|
if (typeof this.automatic_resubmission !== 'boolean') {
|
||||||
|
throw new TypeError('automatic_resubmission must be a boolean');
|
||||||
|
}
|
||||||
if (typeof this.last_ledger_offset !== 'number') {
|
if (typeof this.last_ledger_offset !== 'number') {
|
||||||
throw new TypeError('last_ledger_offset must be a number');
|
throw new TypeError('last_ledger_offset must be a number');
|
||||||
}
|
}
|
||||||
@@ -191,6 +194,7 @@ Remote.DEFAULTS = {
|
|||||||
max_fee: 1000000, // 1 XRP
|
max_fee: 1000000, // 1 XRP
|
||||||
max_attempts: 10,
|
max_attempts: 10,
|
||||||
submission_timeout: 1000 * 20,
|
submission_timeout: 1000 * 20,
|
||||||
|
automatic_resubmission: true,
|
||||||
last_ledger_offset: 3,
|
last_ledger_offset: 3,
|
||||||
servers: [ ],
|
servers: [ ],
|
||||||
max_listeners: 0 // remove Node EventEmitter warnings
|
max_listeners: 0 // remove Node EventEmitter warnings
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ function Transaction(remote) {
|
|||||||
this.tx_json = {Flags: 0};
|
this.tx_json = {Flags: 0};
|
||||||
this._secret = undefined;
|
this._secret = undefined;
|
||||||
this._build_path = false;
|
this._build_path = false;
|
||||||
|
this._should_resubmit = remoteExists
|
||||||
|
? this.remote.automatic_resubmission
|
||||||
|
: true;
|
||||||
this._maxFee = remoteExists ? this.remote.max_fee : undefined;
|
this._maxFee = remoteExists ? this.remote.max_fee : undefined;
|
||||||
this.state = 'unsubmitted';
|
this.state = 'unsubmitted';
|
||||||
this.finalized = false;
|
this.finalized = false;
|
||||||
@@ -191,9 +194,10 @@ Transaction.prototype.isRejected = function(ter) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Transaction.from_json = function(j) {
|
Transaction.from_json = function(j) {
|
||||||
return (new Transaction()).parseJson(j);
|
return (new Transaction()).setJson(j);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.setJson =
|
||||||
Transaction.prototype.parseJson = function(v) {
|
Transaction.prototype.parseJson = function(v) {
|
||||||
this.tx_json = v;
|
this.tx_json = v;
|
||||||
return this;
|
return this;
|
||||||
@@ -212,6 +216,15 @@ Transaction.prototype.setState = function(state) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Transaction.prototype.setResubmittable = function(v) {
|
||||||
|
if (typeof v === 'boolean') {
|
||||||
|
this._should_resubmit = v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Transaction.prototype.isResubmittable = function() {
|
||||||
|
return this._should_resubmit;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finalize transaction. This will prevent future activity
|
* Finalize transaction. This will prevent future activity
|
||||||
*
|
*
|
||||||
@@ -269,13 +282,12 @@ Transaction.prototype.getTransactionType = function() {
|
|||||||
* @return {TransactionManager]
|
* @return {TransactionManager]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Transaction.prototype.getManager = function(account_) {
|
Transaction.prototype.getManager = function(account) {
|
||||||
if (!this.remote) {
|
if (!this.remote) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = account_ || this.tx_json.Account;
|
return this.remote.account(account || this.getAccount())._transactionManager;
|
||||||
return this.remote.account(account)._transactionManager;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,13 +297,12 @@ Transaction.prototype.getManager = function(account_) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
Transaction.prototype.getSecret =
|
Transaction.prototype.getSecret =
|
||||||
Transaction.prototype._accountSecret = function(account_) {
|
Transaction.prototype._accountSecret = function(account) {
|
||||||
if (!this.remote) {
|
if (!this.remote) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const account = account_ || this.tx_json.Account;
|
return this.remote.secrets[account || this.getAccount()];
|
||||||
return this.remote.secrets[account];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ TransactionManager._isTooBusy = function(error) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
TransactionManager.normalizeTransaction = function(tx) {
|
TransactionManager.normalizeTransaction = function(tx) {
|
||||||
let transaction = { };
|
const transaction = { };
|
||||||
const keys = Object.keys(tx);
|
const keys = Object.keys(tx);
|
||||||
|
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
@@ -274,7 +274,7 @@ TransactionManager.prototype._fillSequence = function(tx, callback) {
|
|||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
function submitFill(sequence, fCallback) {
|
function submitFill(sequence, fCallback) {
|
||||||
let fillTransaction = self._remote.createTransaction('AccountSet', {
|
const fillTransaction = self._remote.createTransaction('AccountSet', {
|
||||||
account: self._accountID
|
account: self._accountID
|
||||||
});
|
});
|
||||||
fillTransaction.tx_json.Sequence = sequence;
|
fillTransaction.tx_json.Sequence = sequence;
|
||||||
@@ -476,6 +476,13 @@ TransactionManager.prototype._resubmit = function(ledgers_, pending_) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!transaction.isResubmittable()) {
|
||||||
|
// Rather than resubmit, wait for the transaction to fail due to
|
||||||
|
// LastLedgerSequence's being exceeded. The ultimate error emitted on
|
||||||
|
// transaction is 'tejMaxLedger'; should be definitive
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (self._pending.hasSequence(transaction.tx_json.Sequence)) {
|
while (self._pending.hasSequence(transaction.tx_json.Sequence)) {
|
||||||
// Sequence number has been consumed by another transaction
|
// Sequence number has been consumed by another transaction
|
||||||
transaction.tx_json.Sequence += 1;
|
transaction.tx_json.Sequence += 1;
|
||||||
@@ -591,7 +598,12 @@ TransactionManager.prototype._request = function(tx) {
|
|||||||
// Either a tem-class error or generic server error such as tooBusy. This
|
// Either a tem-class error or generic server error such as tooBusy. This
|
||||||
// should be a definitive failure
|
// should be a definitive failure
|
||||||
if (TransactionManager._isTooBusy(error)) {
|
if (TransactionManager._isTooBusy(error)) {
|
||||||
self._resubmit(1, tx);
|
self._waitLedgers(1, function() {
|
||||||
|
tx.once('submitted', function(m) {
|
||||||
|
tx.emit('resubmitted', m);
|
||||||
|
});
|
||||||
|
self._request(tx);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
self._nextSequence--;
|
self._nextSequence--;
|
||||||
tx.emit('error', error);
|
tx.emit('error', error);
|
||||||
@@ -716,7 +728,8 @@ TransactionManager.prototype.submit = function(tx) {
|
|||||||
|
|
||||||
if (typeof tx.tx_json.Sequence !== 'number') {
|
if (typeof tx.tx_json.Sequence !== 'number') {
|
||||||
// Honor manually-set sequences
|
// Honor manually-set sequences
|
||||||
tx.tx_json.Sequence = this._nextSequence++;
|
this._nextSequence += 1;
|
||||||
|
tx.tx_json.Sequence = this._nextSequence;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.once('cleanup', function() {
|
tx.once('cleanup', function() {
|
||||||
@@ -724,7 +737,7 @@ TransactionManager.prototype.submit = function(tx) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!tx.complete()) {
|
if (!tx.complete()) {
|
||||||
this._nextSequence--;
|
this._nextSequence -= 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
test/fixtures/transactionmanager.json
vendored
12
test/fixtures/transactionmanager.json
vendored
@@ -302,5 +302,17 @@
|
|||||||
},
|
},
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"type": "response"
|
"type": "response"
|
||||||
|
},
|
||||||
|
"SUBMIT_TOO_BUSY_ERROR": {
|
||||||
|
"error": "tooBusy",
|
||||||
|
"error_exception": "Too busy",
|
||||||
|
"id": 1,
|
||||||
|
"request": {
|
||||||
|
"command": "submit",
|
||||||
|
"id": 1,
|
||||||
|
"tx_blob": "12000022800000002400000001201B00CCEAD0614000000000000001684000000000002EE0732102999FB4BC17144F83CDC2F17EA642519FF115EE7B0CC8C78DE9061F1A473F7BAC7447304502210098DC7E9ED1CE860FB6B0904E6E8140D5463D288BA633F36E69A68ACB3D6FCA06022014E76E22F5173B37239F9F56F904839B462F5019169C56A324D3F074FBA39A2A811492DECA2DC92352BE97C1F6347F7E6CCB9A8241C883143108B9AC27BF036EFE5CBE787921F54D622B7A5BF9EA7C0B6D79206D656D6F747970657E0C6D79206D656D6F5F64617461E1F1"
|
||||||
|
},
|
||||||
|
"status": "error",
|
||||||
|
"type": "response"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const SUBMIT_TEL_RESPONSE = require('./fixtures/transactionmanager')
|
|||||||
.SUBMIT_TEL_RESPONSE;
|
.SUBMIT_TEL_RESPONSE;
|
||||||
const SUBMIT_REMOTE_ERROR = require('./fixtures/transactionmanager')
|
const SUBMIT_REMOTE_ERROR = require('./fixtures/transactionmanager')
|
||||||
.SUBMIT_REMOTE_ERROR;
|
.SUBMIT_REMOTE_ERROR;
|
||||||
|
const SUBMIT_TOO_BUSY_ERROR = require('./fixtures/transactionmanager')
|
||||||
|
.SUBMIT_TOO_BUSY_ERROR;
|
||||||
|
|
||||||
describe('TransactionManager', function() {
|
describe('TransactionManager', function() {
|
||||||
let rippled;
|
let rippled;
|
||||||
@@ -59,9 +61,9 @@ describe('TransactionManager', function() {
|
|||||||
c.sendJSON = function(v) {
|
c.sendJSON = function(v) {
|
||||||
try {
|
try {
|
||||||
c.send(JSON.stringify(v));
|
c.send(JSON.stringify(v));
|
||||||
} catch (e) {
|
} catch (e) /* eslint-disable no-empty */{
|
||||||
// empty
|
// empty
|
||||||
}
|
} /* eslint-enable no-empty */
|
||||||
};
|
};
|
||||||
c.sendResponse = function(baseResponse, ext) {
|
c.sendResponse = function(baseResponse, ext) {
|
||||||
assert.strictEqual(typeof baseResponse, 'object');
|
assert.strictEqual(typeof baseResponse, 'object');
|
||||||
@@ -426,7 +428,7 @@ describe('TransactionManager', function() {
|
|||||||
assert.strictEqual(transactionManager.getPending().length(), 1);
|
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||||
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
|
req.sendResponse(SUBMIT_RESPONSE, {id: m.id});
|
||||||
setImmediate(function() {
|
setImmediate(function() {
|
||||||
let txEvent = lodash.extend({}, TX_STREAM_TRANSACTION);
|
const txEvent = lodash.extend({}, TX_STREAM_TRANSACTION);
|
||||||
txEvent.transaction = transaction.tx_json;
|
txEvent.transaction = transaction.tx_json;
|
||||||
txEvent.transaction.hash = transaction.hash();
|
txEvent.transaction.hash = transaction.hash();
|
||||||
rippledConnection.sendJSON(txEvent);
|
rippledConnection.sendJSON(txEvent);
|
||||||
@@ -766,4 +768,130 @@ describe('TransactionManager', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Submit transaction -- disabled resubmission', function(done) {
|
||||||
|
const transaction = remote.createTransaction('AccountSet', {
|
||||||
|
account: ACCOUNT.address
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction.setResubmittable(false);
|
||||||
|
|
||||||
|
let receivedSubmitted = false;
|
||||||
|
let receivedResubmitted = false;
|
||||||
|
transaction.once('proposed', function() {
|
||||||
|
assert(false, 'Should not receive proposed event');
|
||||||
|
});
|
||||||
|
transaction.once('submitted', function(m) {
|
||||||
|
assert.strictEqual(m.engine_result, 'telINSUF_FEE_P');
|
||||||
|
receivedSubmitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
rippled.on('request_submit', function(m, req) {
|
||||||
|
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||||
|
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||||
|
transaction.tx_json).to_hex());
|
||||||
|
req.sendResponse(SUBMIT_TEL_RESPONSE, {id: m.id});
|
||||||
|
});
|
||||||
|
|
||||||
|
rippled.once('request_submit', function(m, req) {
|
||||||
|
transaction.once('resubmitted', function() {
|
||||||
|
receivedResubmitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
req.closeLedger();
|
||||||
|
|
||||||
|
setImmediate(function() {
|
||||||
|
req.sendJSON(lodash.extend({}, LEDGER, {
|
||||||
|
ledger_index: transaction.tx_json.LastLedgerSequence + 1
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction.submit(function(err) {
|
||||||
|
assert(err, 'Transaction submission should not succeed');
|
||||||
|
assert(receivedSubmitted);
|
||||||
|
assert(!receivedResubmitted);
|
||||||
|
assert.strictEqual(err.engine_result, 'tejMaxLedger');
|
||||||
|
assert.strictEqual(transactionManager.getPending().length(), 0);
|
||||||
|
|
||||||
|
const summary = transaction.summary();
|
||||||
|
assert.strictEqual(summary.submissionAttempts, 1);
|
||||||
|
assert.strictEqual(summary.submitIndex, 2);
|
||||||
|
assert.strictEqual(summary.initialSubmitIndex, 2);
|
||||||
|
assert.strictEqual(summary.lastLedgerSequence, 5);
|
||||||
|
assert.strictEqual(summary.state, 'failed');
|
||||||
|
assert.strictEqual(summary.finalized, true);
|
||||||
|
assert.deepEqual(summary.result, {
|
||||||
|
engine_result: SUBMIT_TEL_RESPONSE.result.engine_result,
|
||||||
|
engine_result_message: SUBMIT_TEL_RESPONSE.result.engine_result_message,
|
||||||
|
ledger_hash: undefined,
|
||||||
|
ledger_index: undefined,
|
||||||
|
transaction_hash: SUBMIT_TEL_RESPONSE.result.tx_json.hash
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Submit transaction -- disabled resubmission -- too busy error', function(done) {
|
||||||
|
// Transactions should always be resubmitted in the event of a 'tooBusy'
|
||||||
|
// rippled response, even with transaction resubmission disabled
|
||||||
|
|
||||||
|
const transaction = remote.createTransaction('AccountSet', {
|
||||||
|
account: ACCOUNT.address
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction.setResubmittable(false);
|
||||||
|
|
||||||
|
let receivedSubmitted = false;
|
||||||
|
let receivedResubmitted = false;
|
||||||
|
transaction.once('proposed', function() {
|
||||||
|
assert(false, 'Should not receive proposed event');
|
||||||
|
});
|
||||||
|
transaction.once('submitted', function() {
|
||||||
|
receivedSubmitted = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
rippled.on('request_submit', function(m, req) {
|
||||||
|
assert.strictEqual(transactionManager.getPending().length(), 1);
|
||||||
|
assert.strictEqual(m.tx_blob, SerializedObject.from_json(
|
||||||
|
transaction.tx_json).to_hex());
|
||||||
|
|
||||||
|
req.sendResponse(SUBMIT_TOO_BUSY_ERROR, {id: m.id});
|
||||||
|
});
|
||||||
|
|
||||||
|
rippled.once('request_submit', function(m, req) {
|
||||||
|
transaction.once('resubmitted', function() {
|
||||||
|
receivedResubmitted = true;
|
||||||
|
req.sendJSON(lodash.extend({}, LEDGER, {
|
||||||
|
ledger_index: transaction.tx_json.LastLedgerSequence + 1
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
req.closeLedger();
|
||||||
|
});
|
||||||
|
|
||||||
|
transaction.submit(function(err) {
|
||||||
|
assert(err, 'Transaction submission should not succeed');
|
||||||
|
assert(receivedSubmitted);
|
||||||
|
assert(receivedResubmitted);
|
||||||
|
assert.strictEqual(err.engine_result, 'tejMaxLedger');
|
||||||
|
assert.strictEqual(transactionManager.getPending().length(), 0);
|
||||||
|
|
||||||
|
const summary = transaction.summary();
|
||||||
|
assert.strictEqual(summary.submissionAttempts, 2);
|
||||||
|
assert.strictEqual(summary.submitIndex, 3);
|
||||||
|
assert.strictEqual(summary.initialSubmitIndex, 2);
|
||||||
|
assert.strictEqual(summary.lastLedgerSequence, 5);
|
||||||
|
assert.strictEqual(summary.state, 'failed');
|
||||||
|
assert.strictEqual(summary.finalized, true);
|
||||||
|
assert.deepEqual(summary.result, {
|
||||||
|
engine_result: undefined,
|
||||||
|
engine_result_message: undefined,
|
||||||
|
ledger_hash: undefined,
|
||||||
|
ledger_index: undefined,
|
||||||
|
transaction_hash: undefined
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1008,6 +1008,18 @@ describe('Transaction', function() {
|
|||||||
assert.strictEqual(transaction.tx_json.Fee, '1000');
|
assert.strictEqual(transaction.tx_json.Fee, '1000');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Set resubmittable', function() {
|
||||||
|
const tx = new Transaction();
|
||||||
|
|
||||||
|
assert.strictEqual(tx.isResubmittable(), true);
|
||||||
|
|
||||||
|
tx.setResubmittable(false);
|
||||||
|
assert.strictEqual(tx.isResubmittable(), false);
|
||||||
|
|
||||||
|
tx.setResubmittable(true);
|
||||||
|
assert.strictEqual(tx.isResubmittable(), true);
|
||||||
|
});
|
||||||
|
|
||||||
it('Rewrite transaction path', function() {
|
it('Rewrite transaction path', function() {
|
||||||
const path = [
|
const path = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user