diff --git a/HISTORY.md b/HISTORY.md index 460d9324..dff6c591 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,27 @@ # ripple-lib Release History +## 1.0.0 (UNRELEASED) + +### Breaking Changes + ++ During transaction preparation, there is now a maximum fee. Also, when a transaction is signed, its fee is checked and an error is thrown if the fee exceeds the maximum. The default `maxFeeXRP` is `'2'` (2 XRP). Override this value in the RippleAPI constructor. ++ Attempting to prepare a transaction with an exact `fee` higher than `maxFeeXRP` causes a `ValidationError` to be thrown. ++ Attempting to sign a transaction with a fee higher than `maxFeeXRP` causes a `ValidationError` to be thrown. ++ The value returned by `getFee()` is capped at `maxFeeXRP`. + +### Other Changes + ++ In Transaction Instructions, the `maxFee` parameter is deprecated. Use the `maxFeeXRP` parameter in the RippleAPI constructor. + +#### Overview of fee limits + +Most users of ripple-lib do not need to make any code changes to accommodate the new soft limit on fees. The limit is designed to protect against the most severe cases where an unintentionally high fee may be used. + ++ When having ripple-lib provide the fee with a `prepare*` method, a maximum fee of `maxFeeXRP` (default 2 XRP) applies. You can prepare more economical transactions by setting a lower `maxFeeXRP`, or support high-priority transactions by setting a higher `maxFeeXRP` in the RippleAPI constructor. ++ When using `sign` with a Fee higher than `maxFeeXRP`, a `ValidationError` is thrown. + +If you have any questions or concerns, please open an issue on GitHub. + ## 1.0.0-beta.1 (2018-05-24) ### Breaking Changes diff --git a/docs/index.md b/docs/index.md index 6bf38710..2b19c67f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -152,6 +152,7 @@ authorization | string | *Optional* Username and password for HTTP basic authent certificate | string | *Optional* A string containing the certificate key of the client in PEM format. (Can be an array of certificates). feeCushion | number | *Optional* Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`. key | string | *Optional* A string containing the private key of the client in PEM format. (Can be an array of keys). +maxFeeXRP | string | *Optional* Maximum fee to use with transactions, in XRP. Must be a string-encoded number. Defaults to `'2'`. passphrase | string | *Optional* The passphrase for the private key of the client. proxy | uri string | *Optional* URI for HTTP/HTTPS proxy to use to connect to the rippled server. proxyAuthorization | string | *Optional* Username and password for HTTP basic authentication to the proxy in the format **username:password**. @@ -316,7 +317,7 @@ Transaction instructions indicate how to execute a transaction, complementary wi Name | Type | Description ---- | ---- | ----------- fee | [value](#value) | *Optional* An exact fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information. -maxFee | [value](#value) | *Optional* The maximum fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information. +maxFee | [value](#value) | *Optional* Deprecated: Use `maxFeeXRP` in the RippleAPI constructor instead. The maximum fee to pay for this transaction. If this exceeds `maxFeeXRP`, `maxFeeXRP` will be used instead. See [Transaction Fees](#transaction-fees) for more information. maxLedgerVersion | integer,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version. maxLedgerVersion | string,null | *Optional* The highest ledger version that the transaction can be included in. If this option and `maxLedgerVersionOffset` are both omitted, the `maxLedgerVersion` option will default to 3 greater than the current validated ledger version (equivalent to `maxLedgerVersionOffset=3`). Use `null` to not set a maximum ledger version. maxLedgerVersionOffset | integer | *Optional* Offset from current validated ledger version to highest ledger version that the transaction can be included in. @@ -1076,7 +1077,9 @@ Returns the estimated transaction fee for the rippled server the RippleAPI insta ### Parameters -This method has no parameters. +Name | Type | Description +---- | ---- | ----------- +cushion | number | *Optional* The fee is the product of the base fee, the `load_factor`, and this cushion. Default is provided by the `RippleAPI` constructor's `feeCushion`. ### Return Value diff --git a/docs/src/getFee.md.ejs b/docs/src/getFee.md.ejs index b12f6cd0..7e40289b 100644 --- a/docs/src/getFee.md.ejs +++ b/docs/src/getFee.md.ejs @@ -6,7 +6,7 @@ Returns the estimated transaction fee for the rippled server the RippleAPI insta ### Parameters -This method has no parameters. +<%- renderSchema('input/get-fee.json') %> ### Return Value diff --git a/src/api.ts b/src/api.ts index 9b0b2e29..2cc95404 100644 --- a/src/api.ts +++ b/src/api.ts @@ -64,6 +64,7 @@ import {clamp} from './ledger/utils' export type APIOptions = { server?: string, feeCushion?: number, + maxFeeXRP?: string, trace?: boolean, proxy?: string, timeout?: number @@ -88,6 +89,7 @@ function getCollectKeyFromCommand(command: string): string|undefined { class RippleAPI extends EventEmitter { _feeCushion: number + _maxFeeXRP: string // New in > 0.21.0 // non-validated ledger versions are allowed, and passed to rippled as-is. @@ -105,6 +107,7 @@ class RippleAPI extends EventEmitter { super() validate.apiOptions(options) this._feeCushion = options.feeCushion || 1.2 + this._maxFeeXRP = options.maxFeeXRP || '2' const serverURL = options.server if (serverURL !== undefined) { this.connection = new Connection(serverURL, options) diff --git a/src/common/schemas/input/api-options.json b/src/common/schemas/input/api-options.json index 9272dd46..1cc118b8 100644 --- a/src/common/schemas/input/api-options.json +++ b/src/common/schemas/input/api-options.json @@ -12,6 +12,10 @@ "minimum": 1, "description": "Factor to multiply estimated fee by to provide a cushion in case the required fee rises during submission of a transaction. Defaults to `1.2`." }, + "maxFeeXRP": { + "type": "string", + "description": "Maximum fee to use with transactions, in XRP. Must be a string-encoded number. Defaults to `'2'`." + }, "server": { "type": "string", "description": "URI for rippled websocket port to connect to. Must start with `wss://` or `ws://`.", diff --git a/src/common/schemas/input/get-fee.json b/src/common/schemas/input/get-fee.json new file mode 100644 index 00000000..207537d9 --- /dev/null +++ b/src/common/schemas/input/get-fee.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "getFeeParameters", + "description": "Parameters for getFee", + "type": "object", + "properties": { + "cushion": { + "type": "number", + "description": "The fee is the product of the base fee, the `load_factor`, and this cushion. Default is provided by the `RippleAPI` constructor's `feeCushion`." + } + }, + "additionalProperties": false +} diff --git a/src/common/schemas/objects/instructions.json b/src/common/schemas/objects/instructions.json index 04c98955..fe1c5eb6 100644 --- a/src/common/schemas/objects/instructions.json +++ b/src/common/schemas/objects/instructions.json @@ -14,7 +14,7 @@ "$ref": "value" }, "maxFee": { - "description": "The maximum fee to pay for the transaction. See [Transaction Fees](#transaction-fees) for more information.", + "description": "Deprecated: Use `maxFeeXRP` in the RippleAPI constructor instead. The maximum fee to pay for this transaction. If this exceeds `maxFeeXRP`, `maxFeeXRP` will be used instead. See [Transaction Fees](#transaction-fees) for more information.", "$ref": "value" }, "maxLedgerVersion": { diff --git a/src/common/serverinfo.ts b/src/common/serverinfo.ts index b45e38b1..5c8b8de0 100644 --- a/src/common/serverinfo.ts +++ b/src/common/serverinfo.ts @@ -2,7 +2,6 @@ import * as _ from 'lodash' import {convertKeysFromSnakeCaseToCamelCase} from './utils' import BigNumber from 'bignumber.js' import {RippleAPI} from '../index' -import {ServerInfoResponse} from './types/commands' export type GetServerInfoResponse = { buildVersion: string, @@ -40,14 +39,6 @@ function renameKeys(object, mapping) { }) } -function computeFeeFromServerInfo( - cushion: number, serverInfo: ServerInfoResponse -): string { - return (new BigNumber(serverInfo.info.validated_ledger.base_fee_xrp)). - times(serverInfo.info.load_factor). - times(cushion).toString() -} - function getServerInfo(this: RippleAPI): Promise { return this.request('server_info').then(response => { const info = convertKeysFromSnakeCaseToCamelCase(response.info) @@ -70,15 +61,23 @@ function getServerInfo(this: RippleAPI): Promise { }) } -async function getFee(this: RippleAPI, cushion?: number): Promise { +async function getFee( + this: RippleAPI, + cushion?: number +): Promise { if (cushion === undefined) { cushion = this._feeCushion } if (cushion === undefined) { cushion = 1.2 } - const response = await this.request('server_info') - return computeFeeFromServerInfo(cushion, response) + + const serverInfo = (await this.request('server_info')).info + const baseFeeXrp = new BigNumber(serverInfo.validated_ledger.base_fee_xrp) + const fee = baseFeeXrp.times(serverInfo.load_factor).times(cushion) + + // Cap fee to `this._maxFeeXRP` + return BigNumber.min(fee, this._maxFeeXRP).toString(10) } export { diff --git a/src/transaction/sign.ts b/src/transaction/sign.ts index 27974962..6c71a42a 100644 --- a/src/transaction/sign.ts +++ b/src/transaction/sign.ts @@ -3,6 +3,9 @@ import keypairs = require('ripple-keypairs') import binary = require('ripple-binary-codec') import {computeBinaryTransactionHash} from 'ripple-hashes' import {SignOptions, KeyPair} from './types' +import {BigNumber} from 'bignumber.js' +import {xrpToDrops} from '../common' +import {RippleAPI} from '../api' const validate = utils.common.validate function computeSignature(tx: Object, privateKey: string, signAs?: string) { @@ -13,6 +16,7 @@ function computeSignature(tx: Object, privateKey: string, signAs?: string) { } function signWithKeypair( + api: RippleAPI, txJSON: string, keypair: KeyPair, options: SignOptions = { @@ -28,6 +32,15 @@ function signWithKeypair( ) } + const fee = new BigNumber(tx.Fee) + const maxFeeDrops = xrpToDrops(api._maxFeeXRP) + if (fee.greaterThan(maxFeeDrops)) { + throw new utils.common.errors.ValidationError( + `"Fee" should not exceed "${maxFeeDrops}". ` + + 'To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor.' + ) + } + tx.SigningPubKey = options.signAs ? '' : keypair.publicKey if (options.signAs) { @@ -49,6 +62,7 @@ function signWithKeypair( } function sign( + this: RippleAPI, txJSON: string, secret?: any, options?: SignOptions, @@ -58,9 +72,18 @@ function sign( // we can't validate that the secret matches the account because // the secret could correspond to the regular key validate.sign({txJSON, secret}) - return signWithKeypair(txJSON, keypairs.deriveKeypair(secret), options) + return signWithKeypair( + this, + txJSON, + keypairs.deriveKeypair(secret), + options + ) } else { - return signWithKeypair(txJSON, keypair ? keypair : secret, options) + return signWithKeypair( + this, + txJSON, + keypair ? keypair : secret, + options) } } diff --git a/src/transaction/types.ts b/src/transaction/types.ts index 8afebd25..1724578e 100644 --- a/src/transaction/types.ts +++ b/src/transaction/types.ts @@ -12,6 +12,7 @@ import {ApiMemo} from './utils' export type Instructions = { sequence?: number, fee?: string, + // @deprecated maxFee?: string, maxLedgerVersion?: number, maxLedgerVersionOffset?: number, diff --git a/src/transaction/utils.ts b/src/transaction/utils.ts index 57a447dd..b7ad1b81 100644 --- a/src/transaction/utils.ts +++ b/src/transaction/utils.ts @@ -4,6 +4,7 @@ import {Memo} from '../common/types/objects' const txFlags = common.txFlags import {Instructions, Prepare} from './types' import {RippleAPI} from '../api' +import {ValidationError} from '../common/errors' export type ApiMemo = { MemoData?: string, @@ -63,6 +64,13 @@ function prepareTransaction(txJSON: any, api: RippleAPI, const multiplier = instructions.signersCount === undefined ? 1 : instructions.signersCount + 1 if (instructions.fee !== undefined) { + const fee = new BigNumber(instructions.fee) + if (fee.greaterThan(api._maxFeeXRP)) { + const errorMessage = `Fee of ${fee.toString(10)} XRP exceeds ` + + `max of ${api._maxFeeXRP} XRP. To use this fee, increase ` + + '`maxFeeXRP` in the RippleAPI constructor.' + throw new ValidationError(errorMessage) + } txJSON.Fee = scaleValue(common.xrpToDrops(instructions.fee), multiplier) return Promise.resolve(txJSON) } @@ -75,13 +83,12 @@ function prepareTransaction(txJSON: any, api: RippleAPI, (cushion * feeRef * (32 + Math.floor( new Buffer(txJSON.Fulfillment, 'hex').length / 16))) const feeDrops = common.xrpToDrops(fee) - if (instructions.maxFee !== undefined) { - const maxFeeDrops = common.xrpToDrops(instructions.maxFee) - const normalFee = scaleValue(feeDrops, multiplier, extraFee) - txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString() - } else { - txJSON.Fee = scaleValue(feeDrops, multiplier, extraFee) - } + const maxFeeXRP = instructions.maxFee ? + BigNumber.min(api._maxFeeXRP, instructions.maxFee) : api._maxFeeXRP + const maxFeeDrops = common.xrpToDrops(maxFeeXRP) + const normalFee = scaleValue(feeDrops, multiplier, extraFee) + txJSON.Fee = BigNumber.min(normalFee, maxFeeDrops).toString(10) + return txJSON }) }) diff --git a/test/api-test.js b/test/api-test.js index 28342b83..cb6765b8 100644 --- a/test/api-test.js +++ b/test/api-test.js @@ -392,6 +392,37 @@ describe('RippleAPI', function () { instructions).then(_.partial(checkResult, responses.preparePayment.minAmount, 'prepare')); }); + + it('preparePayment - throws when fee exceeds 2 XRP', function () { + const localInstructions = _.defaults({ + fee: '2.1' + }, instructions); + + assert.throws(() => { + this.api.preparePayment( + address, requests.preparePayment.normal, localInstructions) + }, /Fee of 2\.1 XRP exceeds max of 2 XRP\. To use this fee, increase `maxFeeXRP` in the RippleAPI constructor\./) + }); + + it('preparePayment - allows fee exceeding 2 XRP when maxFeeXRP is higher', function () { + this.api._maxFeeXRP = '2.2' + const localInstructions = _.defaults({ + fee: '2.1' + }, instructions); + + const expectedResponse = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2100000\",\"Sequence\":23}", + "instructions": { + "fee": "2.1", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + return this.api.preparePayment( + address, requests.preparePayment.normal, localInstructions).then( + _.partial(checkResult, expectedResponse, 'prepare')); + }); }); it('prepareOrder - buy order', function () { @@ -823,6 +854,62 @@ describe('RippleAPI', function () { assert.deepEqual(signature, responses.sign.signAs); }); + it('sign - throws when Fee exceeds maxFeeXRP (in drops)', function () { + const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'; + const request = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}", + "instructions": { + "fee": "2.01", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + assert.throws(() => { + this.api.sign(request.txJSON, secret) + }, /Fee" should not exceed "2000000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./) + }); + + it('sign - throws when Fee exceeds maxFeeXRP (in drops) - custom maxFeeXRP', function () { + this.api._maxFeeXRP = '1.9' + const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'; + const request = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}", + "instructions": { + "fee": "2.01", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + assert.throws(() => { + this.api.sign(request.txJSON, secret) + }, /Fee" should not exceed "1900000"\. To use a higher fee, set `maxFeeXRP` in the RippleAPI constructor\./) + }); + + it('sign - permits fee exceeding 2000000 drops when maxFeeXRP is higher than 2 XRP', function () { + this.api._maxFeeXRP = '2.1' + const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'; + const request = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"AccountSet\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Domain\":\"726970706C652E636F6D\",\"LastLedgerSequence\":8820051,\"Fee\":\"2010000\",\"Sequence\":23,\"SigningPubKey\":\"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8\"}", + "instructions": { + "fee": "2.01", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + const result = this.api.sign(request.txJSON, secret) + + const expectedResponse = { + signedTransaction: "12000322800000002400000017201B008695536840000000001EAB90732102F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8744630440220384FBB48EEE7B0E58BD89294A609F9407C51FBE8FA08A4B305B22E9A7489D66602200152315EFE752DA381E74493419871550D206AC6503841DA5F8C30E35D9E3892770A726970706C652E636F6D81145E7B112523F68D2F5E879DB4EAC51C6698A69304", + id: "A1586D6AF7B0821E7075E12A0132D9EB50BC1874A0749441201497F7561795FB" + } + + assert.deepEqual(result, expectedResponse) + schemaValidator.schemaValidate('sign', result) + }); + it('submit', function () { return this.api.submit(responses.sign.normal.signedTransaction).then( _.partial(checkResult, responses.submit, 'submit')); @@ -1663,6 +1750,76 @@ describe('RippleAPI', function () { }); }); + it('getFee - high load_factor', function () { + this.api.connection._send(JSON.stringify({ + command: 'config', + data: { highLoadFactor: true } + })); + + return this.api.getFee().then(fee => { + assert.strictEqual(fee, '2'); + }); + }); + + it('fee - default maxFee of 2 XRP', function () { + this.api._feeCushion = 1000000; + + const expectedResponse = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"2000000\",\"Sequence\":23}", + "instructions": { + "fee": "2", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + return this.api.preparePayment( + address, requests.preparePayment.normal, instructions).then( + _.partial(checkResult, expectedResponse, 'prepare')); + }); + + it('fee - capped to maxFeeXRP when maxFee exceeds maxFeeXRP', function () { + this.api._feeCushion = 1000000 + this.api._maxFeeXRP = '3' + const localInstructions = _.defaults({ + maxFee: '4' + }, instructions); + + const expectedResponse = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"3000000\",\"Sequence\":23}", + "instructions": { + "fee": "3", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + return this.api.preparePayment( + address, requests.preparePayment.normal, localInstructions).then( + _.partial(checkResult, expectedResponse, 'prepare')); + }); + + it('fee - capped to maxFee', function () { + this.api._feeCushion = 1000000 + this.api._maxFeeXRP = '5' + const localInstructions = _.defaults({ + maxFee: '4' + }, instructions); + + const expectedResponse = { + "txJSON": "{\"Flags\":2147483648,\"TransactionType\":\"Payment\",\"Account\":\"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59\",\"Destination\":\"rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo\",\"Amount\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"SendMax\":{\"value\":\"0.01\",\"currency\":\"USD\",\"issuer\":\"rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM\"},\"LastLedgerSequence\":8820051,\"Fee\":\"4000000\",\"Sequence\":23}", + "instructions": { + "fee": "4", + "sequence": 23, + "maxLedgerVersion": 8820051 + } + } + + return this.api.preparePayment( + address, requests.preparePayment.normal, localInstructions).then( + _.partial(checkResult, expectedResponse, 'prepare')); + }); + it('disconnect & isConnected', function () { assert.strictEqual(this.api.isConnected(), true); return this.api.disconnect().then(() => { diff --git a/test/mock-rippled.js b/test/mock-rippled.js index 39bdbb3c..0680eb3e 100644 --- a/test/mock-rippled.js +++ b/test/mock-rippled.js @@ -152,7 +152,39 @@ module.exports = function createMockRippled(port) { mock.on('request_server_info', function (request, conn) { assert.strictEqual(request.command, 'server_info'); - if (conn.config.returnErrorOnServerInfo) { + if (conn.config.highLoadFactor) { + const response = { + "id": 0, + "status": "success", + "type": "response", + "result": { + "info": { + "build_version": "0.24.0-rc1", + "complete_ledgers": "32570-6595042", + "hostid": "ARTS", + "io_latency_ms": 1, + "last_close": { + "converge_time_s": 2.007, + "proposers": 4 + }, + "load_factor": 4294967296, + "peers": 53, + "pubkey_node": "n94wWvFUmaKGYrKUGgpv1DyYgDeXRGdACkNQaSe7zJiy5Znio7UC", + "server_state": "full", + "validated_ledger": { + "age": 5, + "base_fee_xrp": 0.00001, + "hash": "4482DEE5362332F54A4036ED57EE1767C9F33CF7CE5A6670355C16CECE381D46", + "reserve_base_xrp": 20, + "reserve_inc_xrp": 5, + "seq": 6595042 + }, + "validation_quorum": 3 + } + } + } + conn.send(createResponse(request, response)); + } else if (conn.config.returnErrorOnServerInfo) { conn.send(createResponse(request, fixtures.server_info.error)); } else if (conn.config.disconnectOnServerInfo) { conn.close();