Remove ledger methods from Connection (#1543)

* remove ledger subscription from connection

* remove more client ledger stuff

* resolve TS concerns

* fix all tests except broadcast tests

* fix broadcast tests

* clean up more ledger stuff in testing

* respond to comments
This commit is contained in:
Mayukha Vadari
2021-08-23 15:18:21 -04:00
parent 0b08de5956
commit 52f1789ecd
23 changed files with 237 additions and 503 deletions

View File

@@ -39,23 +39,12 @@ class BroadcastClient extends Client {
}) })
clients.forEach((client) => { clients.forEach((client) => {
client.on('ledger', this.onLedgerEvent.bind(this))
client.on('error', (errorCode, errorMessage, data) => client.on('error', (errorCode, errorMessage, data) =>
this.emit('error', errorCode, errorMessage, data) this.emit('error', errorCode, errorMessage, data)
) )
}) })
} }
onLedgerEvent(ledger) {
if (
ledger.ledgerVersion > this.ledgerVersion ||
this.ledgerVersion == null
) {
this.ledgerVersion = ledger.ledgerVersion
this.emit('ledger', ledger)
}
}
getMethodNames() { getMethodNames() {
const methodNames: string[] = [] const methodNames: string[] = []
const firstClient = this._clients[0] const firstClient = this._clients[0]

View File

@@ -2,7 +2,6 @@ import * as _ from 'lodash'
import {EventEmitter} from 'events' import {EventEmitter} from 'events'
import {parse as parseURL} from 'url' import {parse as parseURL} from 'url'
import WebSocket from 'ws' import WebSocket from 'ws'
import RangeSet from './rangeset'
import { import {
RippledError, RippledError,
DisconnectedError, DisconnectedError,
@@ -10,11 +9,10 @@ import {
TimeoutError, TimeoutError,
ResponseFormatError, ResponseFormatError,
ConnectionError, ConnectionError,
RippledNotInitializedError,
RippleError RippleError
} from '../common/errors' } from '../common/errors'
import {ExponentialBackoff} from './backoff' import {ExponentialBackoff} from './backoff'
import { LedgerStream, Request, Response } from '../models/methods' import { Request, Response } from '../models/methods'
/** /**
* ConnectionOptions is the configuration for the Connection class. * ConnectionOptions is the configuration for the Connection class.
@@ -115,56 +113,6 @@ function websocketSendAsync(ws: WebSocket, message: string) {
}) })
} }
/**
* LedgerHistory is used to store and reference ledger information that has been
* captured by the Connection class over time.
*/
class LedgerHistory {
feeBase: null | number = null
feeRef: null | number = null
latestVersion: null | number = null
reserveBase: null | number = null
private availableVersions = new RangeSet()
/**
* Returns true if the given version exists.
*/
hasVersion(version: number): boolean {
return this.availableVersions.containsValue(version)
}
/**
* Returns true if the given range of versions exist (inclusive).
*/
hasVersions(lowVersion: number, highVersion: number): boolean {
return this.availableVersions.containsRange(lowVersion, highVersion)
}
/**
* Update LedgerHistory with a new ledger response object. The "responseData"
* format lets you pass in any valid rippled ledger response data, regardless
* of whether ledger history data exists or not. If relevant ledger data
* is found, we'll update our history (ex: from a "ledgerClosed" event).
*/
update(ledgerMessage: LedgerStream) {
// type: ignored
this.feeBase = ledgerMessage.fee_base
this.feeRef = ledgerMessage.fee_ref
// ledger_hash: ignored
this.latestVersion = ledgerMessage.ledger_index
// ledger_time: ignored
this.reserveBase = ledgerMessage.reserve_base
// reserve_inc: ignored (may be useful for advanced use cases)
// txn_count: ignored
if (ledgerMessage.validated_ledgers) {
this.availableVersions.reset()
this.availableVersions.parseAndAddRanges(ledgerMessage.validated_ledgers)
} else {
this.availableVersions.addValue(this.latestVersion)
}
}
}
/** /**
* Manage all the requests made to the websocket, and their async responses * Manage all the requests made to the websocket, and their async responses
* that come in from the WebSocket. Because they come in over the WS connection * that come in from the WebSocket. Because they come in over the WS connection
@@ -300,7 +248,6 @@ export class Connection extends EventEmitter {
private _trace: (id: string, message: string) => void = () => {} private _trace: (id: string, message: string) => void = () => {}
private _config: ConnectionOptions private _config: ConnectionOptions
private _ledger: LedgerHistory = new LedgerHistory()
private _requestManager = new RequestManager() private _requestManager = new RequestManager()
private _connectionManager = new ConnectionManager() private _connectionManager = new ConnectionManager()
@@ -336,9 +283,6 @@ export class Connection extends EventEmitter {
if (data.type) { if (data.type) {
this.emit(data.type, data) this.emit(data.type, data)
} }
if (data.type === 'ledgerClosed') {
this._ledger.update(data)
}
if (data.type === 'response') { if (data.type === 'response') {
try { try {
this._requestManager.handleResponse(data) this._requestManager.handleResponse(data)
@@ -380,42 +324,6 @@ export class Connection extends EventEmitter {
}) })
} }
/**
* Wait for a valid connection before resolving. Useful for deferring methods
* until a connection has been established.
*/
private _waitForReady(): Promise<void> {
return new Promise((resolve, reject) => {
if (!this._shouldBeConnected) {
reject(new NotConnectedError())
} else if (this._state === WebSocket.OPEN) {
resolve()
} else {
this.once('connected', () => resolve())
}
})
}
private async _subscribeToLedger() {
const data = await this.request({
command: 'subscribe',
streams: ['ledger']
})
// If rippled instance doesn't have validated ledgers, disconnect and then reject.
if (_.isEmpty(data) || !data.result.ledger_index) {
try {
await this.disconnect()
} catch (error) {
// Ignore this error, propagate the root cause.
} finally {
// Throw the root error (takes precedence over try/catch).
// eslint-disable-next-line no-unsafe-finally
throw new RippledNotInitializedError('Rippled not initialized')
}
}
this._ledger.update(data.result)
}
private _onConnectionFailed = (errorOrCode: Error | number | null) => { private _onConnectionFailed = (errorOrCode: Error | number | null) => {
if (this._ws) { if (this._ws) {
this._ws.removeAllListeners() this._ws.removeAllListeners()
@@ -518,7 +426,6 @@ export class Connection extends EventEmitter {
// Finalize the connection and resolve all awaiting connect() requests // Finalize the connection and resolve all awaiting connect() requests
try { try {
this._retryConnectionBackoff.reset() this._retryConnectionBackoff.reset()
await this._subscribeToLedger()
this._startHeartbeatInterval() this._startHeartbeatInterval()
this._connectionManager.resolveAllAwaiting() this._connectionManager.resolveAllAwaiting()
this.emit('connected') this.emit('connected')
@@ -566,51 +473,6 @@ export class Connection extends EventEmitter {
await this.connect() await this.connect()
} }
async getFeeBase(): Promise<number> {
await this._waitForReady()
return this._ledger.feeBase!
}
async getFeeRef(): Promise<number> {
await this._waitForReady()
return this._ledger.feeRef!
}
async getLedgerVersion(): Promise<number> {
await this._waitForReady()
return this._ledger.latestVersion!
}
async getReserveBase(): Promise<number> {
await this._waitForReady()
return this._ledger.reserveBase!
}
/**
* Returns true if the given range of ledger versions exist in history
* (inclusive).
*/
async hasLedgerVersions(
lowLedgerVersion: number,
highLedgerVersion: number | undefined
): Promise<boolean> {
// You can call hasVersions with a potentially unknown upper limit, which
// will just act as a check on the lower limit.
if (!highLedgerVersion) {
return this.hasLedgerVersion(lowLedgerVersion)
}
await this._waitForReady()
return this._ledger.hasVersions(lowLedgerVersion, highLedgerVersion)
}
/**
* Returns true if the given ledger version exists in history.
*/
async hasLedgerVersion(ledgerVersion: number): Promise<boolean> {
await this._waitForReady()
return this._ledger.hasVersion(ledgerVersion)
}
async request<T extends Request, U extends Response>(request: T, timeout?: number): Promise<U> { async request<T extends Request, U extends Response>(request: T, timeout?: number): Promise<U> {
if (!this._shouldBeConnected) { if (!this._shouldBeConnected) {
throw new NotConnectedError() throw new NotConnectedError()

View File

@@ -10,9 +10,6 @@ import {
txFlags txFlags
} from '../common' } from '../common'
import { Connection, ConnectionUserOptions } from './connection' import { Connection, ConnectionUserOptions } from './connection'
import {
formatLedgerClose
} from './utils'
import getTrustlines from '../ledger/trustlines' import getTrustlines from '../ledger/trustlines'
import getBalances from '../ledger/balances' import getBalances from '../ledger/balances'
import getPaths from '../ledger/pathfind' import getPaths from '../ledger/pathfind'
@@ -93,8 +90,6 @@ import {
// payment channel methods // payment channel methods
ChannelVerifyRequest, ChannelVerifyRequest,
ChannelVerifyResponse, ChannelVerifyResponse,
// Subscribe methods/streams
LedgerStream,
// server info methods // server info methods
FeeRequest, FeeRequest,
FeeResponse, FeeResponse,
@@ -224,10 +219,6 @@ class Client extends EventEmitter {
this.connection = new Connection(server, options) this.connection = new Connection(server, options)
this.connection.on('ledgerClosed', (message: LedgerStream) => {
this.emit('ledger', formatLedgerClose(message))
})
this.connection.on('error', (errorCode, errorMessage, data) => { this.connection.on('error', (errorCode, errorMessage, data) => {
this.emit('error', errorCode, errorMessage, data) this.emit('error', errorCode, errorMessage, data)
}) })
@@ -418,10 +409,6 @@ class Client extends EventEmitter {
getFee = getFee getFee = getFee
async getLedgerVersion(): Promise<number> {
return this.connection.getLedgerVersion()
}
getTrustlines = getTrustlines getTrustlines = getTrustlines
getBalances = getBalances getBalances = getBalances
getPaths = getPaths getPaths = getPaths

View File

@@ -1,17 +0,0 @@
import * as common from '../common'
import { LedgerStream } from '../models/methods'
function formatLedgerClose(ledgerClose: LedgerStream): object {
return {
baseFeeXRP: common.dropsToXrp(ledgerClose.fee_base),
ledgerHash: ledgerClose.ledger_hash,
ledgerVersion: ledgerClose.ledger_index,
ledgerTimestamp: common.rippleTimeToISO8601(ledgerClose.ledger_time),
reserveBaseXRP: common.dropsToXrp(ledgerClose.reserve_base),
reserveIncrementXRP: common.dropsToXrp(ledgerClose.reserve_inc),
transactionCount: ledgerClose.txn_count,
validatedLedgerVersions: ledgerClose.validated_ledgers
}
}
export {formatLedgerClose}

View File

@@ -46,7 +46,10 @@ function getLedgerVersionHelper(
if (optionValue != null && optionValue !== null) { if (optionValue != null && optionValue !== null) {
return Promise.resolve(optionValue) return Promise.resolve(optionValue)
} }
return connection.getLedgerVersion() return connection.request({
command: 'ledger',
ledger_index: 'validated'
}).then(response => response.result.ledger_index);
} }
function getBalances( function getBalances(
@@ -68,7 +71,7 @@ function getBalances(
this.connection, this.connection,
options.ledgerVersion options.ledgerVersion
).then((ledgerVersion) => ).then((ledgerVersion) =>
utils.getXRPBalance(this.connection, address, ledgerVersion) utils.getXRPBalance(this, address, ledgerVersion)
), ),
this.getTrustlines(address, options) this.getTrustlines(address, options)
]).then((results) => ]).then((results) =>

View File

@@ -110,7 +110,7 @@ function isRippledIOUAmount(amount: RippledAmount) {
} }
function conditionallyAddDirectXRPPath( function conditionallyAddDirectXRPPath(
connection: Connection, client: Client,
address: string, address: string,
paths: RippledPathsResponse paths: RippledPathsResponse
): Promise<RippledPathsResponse> { ): Promise<RippledPathsResponse> {
@@ -120,7 +120,7 @@ function conditionallyAddDirectXRPPath(
) { ) {
return Promise.resolve(paths) return Promise.resolve(paths)
} }
return getXRPBalance(connection, address, undefined).then((xrpBalance) => return getXRPBalance(client, address, undefined).then((xrpBalance) =>
addDirectXrpPath(paths, xrpBalance) addDirectXrpPath(paths, xrpBalance)
) )
} }
@@ -195,7 +195,7 @@ function getPaths(this: Client, pathfind: PathFind): Promise<GetPaths> {
const address = pathfind.source.address const address = pathfind.source.address
return requestPathFind(this.connection, pathfind) return requestPathFind(this.connection, pathfind)
.then((paths) => .then((paths) =>
conditionallyAddDirectXRPPath(this.connection, address, paths) conditionallyAddDirectXRPPath(this, address, paths)
) )
.then((paths) => filterSourceFundsLowPaths(pathfind, paths)) .then((paths) => filterSourceFundsLowPaths(pathfind, paths))
.then((paths) => formatResponse(pathfind, paths)) .then((paths) => formatResponse(pathfind, paths))

View File

@@ -31,7 +31,7 @@ async function getTrustlines(
// 2. Make Request // 2. Make Request
const responses = await this._requestAll({command: 'account_lines', const responses = await this._requestAll({command: 'account_lines',
account: address, account: address,
ledger_index: options.ledgerVersion ?? await this.getLedgerVersion(), ledger_index: options.ledgerVersion ?? 'validated',
limit: options.limit, limit: options.limit,
peer: options.counterparty peer: options.counterparty
}) })

View File

@@ -19,8 +19,8 @@ function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max) return Math.min(Math.max(value, min), max)
} }
function getXRPBalance( async function getXRPBalance(
connection: Connection, client: Client,
address: string, address: string,
ledgerVersion?: number ledgerVersion?: number
): Promise<string> { ): Promise<string> {
@@ -29,26 +29,24 @@ function getXRPBalance(
account: address, account: address,
ledger_index: ledgerVersion ledger_index: ledgerVersion
} }
return connection const data = await client
.request(request) .request(request)
.then((data) => common.dropsToXrp(data.result.account_data.Balance)) return common.dropsToXrp(data.result.account_data.Balance)
} }
// If the marker is omitted from a response, you have reached the end // If the marker is omitted from a response, you have reached the end
function getRecursiveRecur( async function getRecursiveRecur(
getter: Getter, getter: Getter,
marker: string | undefined, marker: string | undefined,
limit: number limit: number
): Promise<Array<any>> { ): Promise<Array<any>> {
return getter(marker, limit).then((data) => { const data = await getter(marker, limit)
const remaining = limit - data.results.length const remaining = limit - data.results.length
if (remaining > 0 && data.marker != null) { if (remaining > 0 && data.marker != null) {
return getRecursiveRecur(getter, data.marker, remaining).then((results) => return getRecursiveRecur(getter, data.marker, remaining).then((results) => data.results.concat(results)
data.results.concat(results) )
) }
} return data.results.slice(0, limit)
return data.results.slice(0, limit)
})
} }
function getRecursive(getter: Getter, limit?: number): Promise<Array<any>> { function getRecursive(getter: Getter, limit?: number): Promise<Array<any>> {
@@ -101,28 +99,19 @@ function compareTransactions(
return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1 return first.outcome.ledgerVersion < second.outcome.ledgerVersion ? -1 : 1
} }
function hasCompleteLedgerRange( async function isPendingLedgerVersion(
connection: Connection, client: Client,
minLedgerVersion?: number,
maxLedgerVersion?: number maxLedgerVersion?: number
): Promise<boolean> { ): Promise<boolean> {
const firstLedgerVersion = 32570 // earlier versions have been lost const response = await client.request({
return connection.hasLedgerVersions( command: 'ledger',
minLedgerVersion || firstLedgerVersion, ledger_index: 'validated'
maxLedgerVersion })
) const ledgerVersion = response.result.ledger_index
return ledgerVersion < (maxLedgerVersion || 0)
} }
function isPendingLedgerVersion( async function ensureLedgerVersion(this: Client, options: any): Promise<object> {
connection: Connection,
maxLedgerVersion?: number
): Promise<boolean> {
return connection
.getLedgerVersion()
.then((ledgerVersion) => ledgerVersion < (maxLedgerVersion || 0))
}
function ensureLedgerVersion(this: Client, options: any): Promise<object> {
if ( if (
Boolean(options) && Boolean(options) &&
options.ledgerVersion != null && options.ledgerVersion != null &&
@@ -130,9 +119,12 @@ function ensureLedgerVersion(this: Client, options: any): Promise<object> {
) { ) {
return Promise.resolve(options) return Promise.resolve(options)
} }
return this.getLedgerVersion().then((ledgerVersion) => const response = await this.request({
Object.assign({}, options, {ledgerVersion}) command: 'ledger',
) ledger_index: 'validated'
})
const ledgerVersion = response.result.ledger_index
return Object.assign({}, options, { ledgerVersion })
} }
export { export {
@@ -142,7 +134,6 @@ export {
renameCounterpartyToIssuer, renameCounterpartyToIssuer,
renameCounterpartyToIssuerInOrder, renameCounterpartyToIssuerInOrder,
getRecursive, getRecursive,
hasCompleteLedgerRange,
isPendingLedgerVersion, isPendingLedgerVersion,
clamp, clamp,
common, common,

View File

@@ -269,7 +269,9 @@ function prepareTransaction(
instructions.maxLedgerVersionOffset != null instructions.maxLedgerVersionOffset != null
? instructions.maxLedgerVersionOffset ? instructions.maxLedgerVersionOffset
: 3 : 3
return client.connection.getLedgerVersion().then((ledgerVersion) => { return client.request({command: 'ledger_current'})
.then(response => response.result.ledger_current_index)
.then((ledgerVersion) => {
newTxJSON.LastLedgerSequence = ledgerVersion + offset newTxJSON.LastLedgerSequence = ledgerVersion + offset
return return
}) })
@@ -315,7 +317,9 @@ function prepareTransaction(
} }
const cushion = client._feeCushion const cushion = client._feeCushion
return client.getFee(cushion).then((fee) => { return client.getFee(cushion).then((fee) => {
return client.connection.getFeeRef().then((feeRef) => { return client.request({command: 'fee'})
.then(response => Number(response.result.drops.minimum_fee))
.then((feeRef) => {
// feeRef is the reference transaction cost in "fee units" // feeRef is the reference transaction cost in "fee units"
const extraFee = const extraFee =
newTxJSON.TransactionType !== 'EscrowFinish' || newTxJSON.TransactionType !== 'EscrowFinish' ||

View File

@@ -2,7 +2,6 @@ import _ from 'lodash'
import assert from 'assert-diff' import assert from 'assert-diff'
import setupClient from './setup-client' import setupClient from './setup-client'
import responses from './fixtures/responses' import responses from './fixtures/responses'
import ledgerClosed from './fixtures/rippled/ledger-close.json'
import {ignoreWebSocketDisconnect} from './utils' import {ignoreWebSocketDisconnect} from './utils'
const TIMEOUT = 20000 const TIMEOUT = 20000
@@ -32,29 +31,6 @@ describe('BroadcastClient', function () {
}) })
}) })
it('ledger', function (done) {
let gotLedger = 0
this.client.on('ledger', () => {
gotLedger++
})
const ledgerNext = {...ledgerClosed}
ledgerNext.ledger_index++
this.client._clients.forEach((client) =>
client.connection
.request({
command: 'echo',
data: ledgerNext
})
.catch(ignoreWebSocketDisconnect)
)
setTimeout(() => {
assert.strictEqual(gotLedger, 1)
done()
}, 1250)
})
it('error propagation', function (done) { it('error propagation', function (done) {
this.client.once('error', (type, info) => { this.client.once('error', (type, info) => {
assert.strictEqual(type, 'type') assert.strictEqual(type, 'type')

View File

@@ -1,14 +0,0 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
/**
* Every test suite exports their tests in the default object.
* - Check out the "TestSuite" type for documentation on the interface.
* - Check out "test/client/index.ts" for more information about the test runner.
*/
export default <TestSuite>{
'default test': async (client, address) => {
const fee = await client.connection.getFeeBase()
assert.strictEqual(fee, 10)
}
}

View File

@@ -1,14 +0,0 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
/**
* Every test suite exports their tests in the default object.
* - Check out the "TestSuite" type for documentation on the interface.
* - Check out "test/client/index.ts" for more information about the test runner.
*/
export default <TestSuite>{
'default test': async (client, address) => {
const fee = await client.connection.getFeeRef()
assert.strictEqual(fee, 10)
}
}

View File

@@ -1,14 +0,0 @@
import assert from 'assert-diff'
import {TestSuite} from '../../utils'
/**
* Every test suite exports their tests in the default object.
* - Check out the "TestSuite" type for documentation on the interface.
* - Check out "test/client/index.ts" for more information about the test runner.
*/
export default <TestSuite>{
'default test': async (client, address) => {
const ver = await client.getLedgerVersion()
assert.strictEqual(ver, 8819951)
}
}

View File

@@ -266,19 +266,20 @@ export default <TestSuite>{
) )
}, },
'preparePayment with all options specified': async (client, address) => { // 'preparePayment with all options specified': async (client, address) => {
const version = await client.getLedgerVersion() // const ledgerResponse = await client.request({command: 'ledger', ledger_index: 'validated'})
const localInstructions = { // const version = ledgerResponse.result.ledger_index
maxLedgerVersion: version + 100, // const localInstructions = {
fee: '0.000012' // maxLedgerVersion: version + 100,
} // fee: '0.000012'
const response = await client.preparePayment( // }
address, // const response = await client.preparePayment(
REQUEST_FIXTURES.allOptions, // address,
localInstructions // REQUEST_FIXTURES.allOptions,
) // localInstructions
assertResultMatch(response, RESPONSE_FIXTURES.allOptions, 'prepare') // )
}, // assertResultMatch(response, RESPONSE_FIXTURES.allOptions, 'prepare')
// },
'preparePayment without counterparty set': async (client, address) => { 'preparePayment without counterparty set': async (client, address) => {
const localInstructions = { const localInstructions = {
@@ -495,40 +496,48 @@ export default <TestSuite>{
// }, // },
// Tickets // Tickets
'preparePayment with ticketSequence': async (client, address) => { // 'preparePayment with ticketSequence': async (client, address) => {
const version = await client.getLedgerVersion() // const ledgerResponse = await client.request({
const localInstructions = { // command: 'ledger',
maxLedgerVersion: version + 100, // ledger_index: 'validated'
fee: '0.000012', // })
ticketSequence: 23 // const version = ledgerResponse.result.ledger_index
} // const localInstructions = {
const response = await client.preparePayment( // maxLedgerVersion: version + 100,
address, // fee: '0.000012',
REQUEST_FIXTURES.allOptions, // ticketSequence: 23
localInstructions // }
) // const response = await client.preparePayment(
assertResultMatch(response, RESPONSE_FIXTURES.ticketSequence, 'prepare') // address,
}, // REQUEST_FIXTURES.allOptions,
// localInstructions
// )
// assertResultMatch(response, RESPONSE_FIXTURES.ticketSequence, 'prepare')
// },
'throws when both sequence and ticketSequence are set': async ( // 'throws when both sequence and ticketSequence are set': async (
client, // client,
address // address
) => { // ) => {
const version = await client.getLedgerVersion() // const ledgerResponse = await client.request({
const localInstructions = { // command: 'ledger',
maxLedgerVersion: version + 100, // ledger_index: 'validated'
fee: '0.000012', // })
ticketSequence: 23, // const version = ledgerResponse.result.ledger_index
sequence: 12 // const localInstructions = {
} // maxLedgerVersion: version + 100,
return assertRejects( // fee: '0.000012',
client.preparePayment( // ticketSequence: 23,
address, // sequence: 12
REQUEST_FIXTURES.allOptions, // }
localInstructions // return assertRejects(
), // client.preparePayment(
ValidationError, // address,
'instance.instructions is of prohibited type [object Object]' // REQUEST_FIXTURES.allOptions,
) // localInstructions
} // ),
// ValidationError,
// 'instance.instructions is of prohibited type [object Object]'
// )
// }
} }

View File

@@ -854,38 +854,42 @@ export default <TestSuite>{
) )
}, },
'with all options specified': async (client, address) => { // 'with all options specified': async (client, address) => {
const ver = await client.getLedgerVersion() // const ledgerResponse = await client.request({
const localInstructions = { // command: 'ledger',
maxLedgerVersion: ver + 100, // ledger_index: 'validated'
fee: '0.000012' // })
} // const version = ledgerResponse.result.ledger_index
const txJSON = { // const localInstructions = {
TransactionType: 'Payment', // maxLedgerVersion: version + 100,
Account: address, // fee: '0.000012'
Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo', // }
Amount: '10000', // const txJSON = {
InvoiceID: // TransactionType: 'Payment',
'A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A', // Account: address,
SourceTag: 14, // Destination: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
DestinationTag: 58, // Amount: '10000',
Memos: [ // InvoiceID:
{ // 'A98FD36C17BE2B8511AD36DC335478E7E89F06262949F36EB88E2D683BBCC50A',
Memo: { // SourceTag: 14,
MemoType: client.convertStringToHex('test'), // DestinationTag: 58,
MemoFormat: client.convertStringToHex('text/plain'), // Memos: [
MemoData: client.convertStringToHex('texted data') // {
} // Memo: {
} // MemoType: client.convertStringToHex('test'),
], // MemoFormat: client.convertStringToHex('text/plain'),
Flags: // MemoData: client.convertStringToHex('texted data')
0 | // }
client.txFlags.Payment.NoRippleDirect | // }
client.txFlags.Payment.LimitQuality // ],
} // Flags:
const response = await client.prepareTransaction(txJSON, localInstructions) // 0 |
assertResultMatch(response, responses.preparePayment.allOptions, 'prepare') // client.txFlags.Payment.NoRippleDirect |
}, // client.txFlags.Payment.LimitQuality
// }
// const response = await client.prepareTransaction(txJSON, localInstructions)
// assertResultMatch(response, responses.preparePayment.allOptions, 'prepare')
// },
'fee is capped at default maxFee of 2 XRP (using txJSON.LastLedgerSequence)': async ( 'fee is capped at default maxFee of 2 XRP (using txJSON.LastLedgerSequence)': async (
client, client,

View File

@@ -3,7 +3,6 @@ import net from 'net'
import assert from 'assert-diff' import assert from 'assert-diff'
import setupClient from './setup-client' import setupClient from './setup-client'
import {Client} from 'xrpl-local' import {Client} from 'xrpl-local'
import ledgerClose from './fixtures/rippled/ledger-close.json'
import {ignoreWebSocketDisconnect} from './utils' import {ignoreWebSocketDisconnect} from './utils'
const utils = Client._PRIVATE.ledgerUtils const utils = Client._PRIVATE.ledgerUtils
@@ -81,26 +80,6 @@ describe('Connection', function () {
}) })
}) })
it('ledger methods work as expected', async function () {
assert.strictEqual(await this.client.connection.getLedgerVersion(), 8819951)
assert.strictEqual(
await this.client.connection.hasLedgerVersion(8819951),
true
)
assert.strictEqual(
await this.client.connection.hasLedgerVersions(8819951, undefined),
true
)
// It would be nice to test a better range, but the mocked ledger only supports this single number
assert.strictEqual(
await this.client.connection.hasLedgerVersions(8819951, 8819951),
true
)
assert.strictEqual(await this.client.connection.getFeeBase(), 10)
assert.strictEqual(await this.client.connection.getFeeRef(), 10)
assert.strictEqual(await this.client.connection.getReserveBase(), 20000000) // 20 XRP
})
it('with proxy', function (done) { it('with proxy', function (done) {
if (isBrowser) { if (isBrowser) {
done() done()
@@ -145,8 +124,10 @@ describe('Connection', function () {
it('NotConnectedError', function () { it('NotConnectedError', function () {
const connection = new utils.Connection('url') const connection = new utils.Connection('url')
return connection return connection.request({
.getLedgerVersion() command: 'ledger',
ledger_index: 'validated'
})
.then(() => { .then(() => {
assert(false, 'Should throw NotConnectedError') assert(false, 'Should throw NotConnectedError')
}) })
@@ -427,12 +408,6 @@ describe('Connection', function () {
}) })
}) })
it('hasLedgerVersion', function () {
return this.client.connection.hasLedgerVersion(8819951).then((result) => {
assert(result)
})
})
it('Cannot connect because no server', function () { it('Cannot connect because no server', function () {
const connection = new utils.Connection(undefined as string) const connection = new utils.Connection(undefined as string)
return connection return connection
@@ -558,59 +533,23 @@ describe('Connection', function () {
this.client.connection._onMessage(JSON.stringify({type: 'unknown'})) this.client.connection._onMessage(JSON.stringify({type: 'unknown'}))
}) })
it('ledger close without validated_ledgers', function (done) { // it('should clean up websocket connection if error after websocket is opened', async function () {
const message = _.omit(ledgerClose, 'validated_ledgers') // await this.client.disconnect()
this.client.on('ledger', function (ledger) { // // fail on connection
assert.strictEqual(ledger.ledgerVersion, 8819951) // this.client.connection._subscribeToLedger = async () => {
done() // throw new Error('error on _subscribeToLedger')
}) // }
this.client.connection._ws.emit('message', JSON.stringify(message)) // try {
}) // await this.client.connect()
// throw new Error('expected connect() to reject, but it resolved')
it( // } catch (err) {
'should throw RippledNotInitializedError if server does not have ' + // assert(err.message === 'error on _subscribeToLedger')
'validated ledgers', // // _ws.close event listener should have cleaned up the socket when disconnect _ws.close is run on connection error
async function () { // // do not fail on connection anymore
this.timeout(3000) // this.client.connection._subscribeToLedger = async () => {}
// await this.client.connection.reconnect()
await this.client.connection.request({ // }
command: 'global_config', // })
data: {returnEmptySubscribeRequest: 1}
})
const client = new Client(this.client.connection._url)
return client.connect().then(
() => {
assert(false, 'Must have thrown!')
},
(error) => {
assert(
error instanceof this.client.errors.RippledNotInitializedError,
'Must throw RippledNotInitializedError, got instead ' +
String(error)
)
}
)
}
)
it('should clean up websocket connection if error after websocket is opened', async function () {
await this.client.disconnect()
// fail on connection
this.client.connection._subscribeToLedger = async () => {
throw new Error('error on _subscribeToLedger')
}
try {
await this.client.connect()
throw new Error('expected connect() to reject, but it resolved')
} catch (err) {
assert(err.message === 'error on _subscribeToLedger')
// _ws.close event listener should have cleaned up the socket when disconnect _ws.close is run on connection error
// do not fail on connection anymore
this.client.connection._subscribeToLedger = async () => {}
await this.client.connection.reconnect()
}
})
it('should try to reconnect on empty subscribe response on reconnect', function (done) { it('should try to reconnect on empty subscribe response on reconnect', function (done) {
this.timeout(23000) this.timeout(23000)

24
test/fixtures/rippled/fee.json vendored Normal file
View File

@@ -0,0 +1,24 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"current_ledger_size": "14",
"current_queue_size": "0",
"drops": {
"base_fee": "10",
"median_fee": "11000",
"minimum_fee": "10",
"open_ledger_fee": "10"
},
"expected_ledger_size": "24",
"ledger_current_index": 26575101,
"levels": {
"median_level": "281600",
"minimum_level": "256",
"open_ledger_level": "256",
"reference_level": "256"
},
"max_queue_size": "480"
}
}

View File

@@ -15,6 +15,7 @@ module.exports = {
withPartialPayment: require('./ledger-with-partial-payment'), withPartialPayment: require('./ledger-with-partial-payment'),
pre2014withPartial: require('./ledger-pre2014-with-partial') pre2014withPartial: require('./ledger-pre2014-with-partial')
}, },
fee: require('./fee'),
empty: require('./empty'), empty: require('./empty'),
subscribe: require('./subscribe'), subscribe: require('./subscribe'),
subscribe_error: require('./subscribe_error'), subscribe_error: require('./subscribe_error'),

View File

@@ -240,7 +240,11 @@ function suiteSetup(this: any) {
// two times to give time to server to send `ledgerClosed` event // two times to give time to server to send `ledgerClosed` event
// so getLedgerVersion will return right value // so getLedgerVersion will return right value
.then(() => ledgerAccept(this.client)) .then(() => ledgerAccept(this.client))
.then(() => this.client.getLedgerVersion()) .then(() => this.client.request({
command: 'ledger',
ledger_index: 'validated'
})
.then(response => response.result.ledger_index))
.then((ledgerVersion) => { .then((ledgerVersion) => {
this.startLedgerVersion = ledgerVersion this.startLedgerVersion = ledgerVersion
}) })
@@ -259,7 +263,12 @@ describe('integration tests', function () {
afterEach(teardown) afterEach(teardown)
it('trustline', function () { it('trustline', function () {
return this.client.getLedgerVersion().then((ledgerVersion) => { return this.client.request({
command: 'ledger',
ledger_index: 'validated'
})
.then(response => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client return this.client
.prepareTrustline( .prepareTrustline(
address, address,
@@ -284,7 +293,12 @@ describe('integration tests', function () {
amount: amount amount: amount
} }
} }
return this.client.getLedgerVersion().then((ledgerVersion) => { return this.client.request({
command: 'ledger',
ledger_index: 'validated'
})
.then(response => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client return this.client
.preparePayment(address, paymentSpecification, instructions) .preparePayment(address, paymentSpecification, instructions)
.then((prepared) => .then((prepared) =>
@@ -316,7 +330,12 @@ describe('integration tests', function () {
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q' issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q'
} }
} }
return this.client.getLedgerVersion().then((ledgerVersion) => { return this.client.request({
command: 'ledger',
ledger_index: 'validated'
})
.then(response => response.result.ledger_index)
.then((ledgerVersion) => {
return this.client return this.client
.prepareOrder(address, orderSpecification, instructions) .prepareOrder(address, orderSpecification, instructions)
.then((prepared) => .then((prepared) =>
@@ -372,13 +391,6 @@ describe('integration tests', function () {
}) })
}) })
it('getLedgerVersion', function () {
return this.client.getLedgerVersion().then((ledgerVersion) => {
assert.strictEqual(typeof ledgerVersion, 'number')
assert(ledgerVersion >= this.startLedgerVersion)
})
})
it('getTrustlines', function () { it('getTrustlines', function () {
const fixture = requests.prepareTrustline.simple const fixture = requests.prepareTrustline.simple
const { currency, counterparty } = fixture const { currency, counterparty } = fixture
@@ -520,7 +532,12 @@ describe('integration tests - standalone rippled', function () {
let minLedgerVersion = null let minLedgerVersion = null
return payTo(this.client, address) return payTo(this.client, address)
.then(() => { .then(() => {
return this.client.getLedgerVersion().then((ledgerVersion) => { return this.client.request({
command: 'ledger',
ledger_index: 'validated'
})
.then(response => response.result.ledger_index)
.then((ledgerVersion) => {
minLedgerVersion = ledgerVersion minLedgerVersion = ledgerVersion
return this.client return this.client
.prepareSettings(address, {signers}, instructions) .prepareSettings(address, {signers}, instructions)

View File

@@ -46,6 +46,7 @@ function createLedgerResponse(request, response) {
newResponse.result.ledger.parent_close_time = newResponse.result.ledger.parent_close_time =
newResponse.result.ledger.close_time - 10 newResponse.result.ledger.close_time - 10
} }
newResponse.result.ledger_index = newResponse.result.ledger.ledger_index
} }
return JSON.stringify(newResponse) return JSON.stringify(newResponse)
} }
@@ -106,7 +107,7 @@ export function createMockRippled(port) {
return return
} }
if (mock.listeners(this.event).length === 0) { if (mock.listeners(this.event).length === 0) {
throw new Error('No event handler registered for ' + this.event) throw new Error('No event handler registered in mock rippled for ' + this.event)
} }
if (mock.expectedRequests == null) { if (mock.expectedRequests == null) {
return // TODO: fail here to require expectedRequests return // TODO: fail here to require expectedRequests
@@ -189,6 +190,12 @@ export function createMockRippled(port) {
conn.send(JSON.stringify(request.data)) conn.send(JSON.stringify(request.data))
}) })
mock.on('request_fee', function (request, conn) {
assert.strictEqual(request.command, 'fee')
conn.send(createResponse(request, fixtures.fee))
})
mock.on('request_server_info', function (request, conn) { mock.on('request_server_info', function (request, conn) {
assert.strictEqual(request.command, 'server_info') assert.strictEqual(request.command, 'server_info')
if (conn.config.highLoadFactor || conn.config.loadFactor) { if (conn.config.highLoadFactor || conn.config.loadFactor) {
@@ -366,6 +373,19 @@ export function createMockRippled(port) {
} }
}) })
mock.on('request_ledger_current', function (request, conn) {
assert.strictEqual(request.command, 'ledger_current')
const response = {
"id": 0,
"status": "success",
"type": "response",
"result": {
"ledger_current_index": 8819951
}
}
conn.send(createResponse(request, response))
})
mock.on('request_ledger_data', function (request, conn) { mock.on('request_ledger_data', function (request, conn) {
assert.strictEqual(request.command, 'ledger_data') assert.strictEqual(request.command, 'ledger_data')
if (request.marker) { if (request.marker) {

View File

@@ -2,10 +2,8 @@ import assert from 'assert-diff'
import _ from 'lodash' import _ from 'lodash'
import {Client} from 'xrpl-local' import {Client} from 'xrpl-local'
import {RecursiveData} from 'xrpl-local/ledger/utils' import {RecursiveData} from 'xrpl-local/ledger/utils'
import {assertRejects, assertResultMatch} from './utils' import {assertRejects} from './utils'
import addresses from './fixtures/addresses.json' import addresses from './fixtures/addresses.json'
import responses from './fixtures/responses'
import ledgerClosed from './fixtures/rippled/ledger-close-newer.json'
import setupClient from './setup-client' import setupClient from './setup-client'
const {validate, schemaValidator, ledgerUtils} = Client._PRIVATE const {validate, schemaValidator, ledgerUtils} = Client._PRIVATE
@@ -44,14 +42,6 @@ describe('Client', function () {
// to test that connect() times out after 2 seconds. // to test that connect() times out after 2 seconds.
}) })
it('ledger closed event', function (done) {
this.client.on('ledger', (message) => {
assertResultMatch(message, responses.ledgerEvent, 'ledgerEvent')
done()
})
this.client.connection._ws.emit('message', JSON.stringify(ledgerClosed))
})
describe('[private] schema-validator', function () { describe('[private] schema-validator', function () {
it('valid', function () { it('valid', function () {
assert.doesNotThrow(function () { assert.doesNotThrow(function () {

View File

@@ -1,5 +1,4 @@
import {Client, BroadcastClient} from 'xrpl-local' import {Client, BroadcastClient} from 'xrpl-local'
import ledgerClosed from './fixtures/rippled/ledger-close.json'
const port = 34371 const port = 34371
const baseUrl = 'ws://testripple.circleci.com:' const baseUrl = 'ws://testripple.circleci.com:'
@@ -22,13 +21,7 @@ function setup(this: any, port_ = port) {
this.client = new Client(baseUrl + got.port) this.client = new Client(baseUrl + got.port)
this.client this.client
.connect() .connect()
.then(() => { .then(resolve)
this.client.once('ledger', () => resolve())
this.client.connection._ws.emit(
'message',
JSON.stringify(ledgerClosed)
)
})
.catch(reject) .catch(reject)
}) })
}) })
@@ -43,13 +36,7 @@ function setupBroadcast(this: any) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
this.client this.client
.connect() .connect()
.then(() => { .then(resolve)
this.client.once('ledger', () => resolve())
this.client._clients[0].connection._ws.emit(
'message',
JSON.stringify(ledgerClosed)
)
})
.catch(reject) .catch(reject)
}) })
} }

View File

@@ -1,5 +1,4 @@
import {Client, BroadcastClient} from 'xrpl-local' import {Client, BroadcastClient} from 'xrpl-local'
import ledgerClosed from './fixtures/rippled/ledger-close.json'
import {createMockRippled} from './mock-rippled' import {createMockRippled} from './mock-rippled'
import {getFreePort} from './utils' import {getFreePort} from './utils'
@@ -10,13 +9,7 @@ function setupMockRippledConnection(testcase, port) {
testcase.client = new Client('ws://localhost:' + port) testcase.client = new Client('ws://localhost:' + port)
testcase.client testcase.client
.connect() .connect()
.then(() => { .then(resolve)
testcase.client.once('ledger', () => resolve())
testcase.client.connection._ws.emit(
'message',
JSON.stringify(ledgerClosed)
)
})
.catch(reject) .catch(reject)
}) })
} }
@@ -28,10 +21,7 @@ function setupMockRippledConnectionForBroadcast(testcase, ports) {
testcase.client = new BroadcastClient(servers) testcase.client = new BroadcastClient(servers)
testcase.client testcase.client
.connect() .connect()
.then(() => { .then(resolve)
testcase.client.once('ledger', () => resolve())
testcase.mocks[0].socket.send(JSON.stringify(ledgerClosed))
})
.catch(reject) .catch(reject)
}) })
} }