Compare commits

...

41 Commits

Author SHA1 Message Date
pdp2121
f3f5b3a844 Merge branch 'main' into network-id 2023-06-14 14:41:54 -04:00
Phu Pham
17fcddf7bb Merge branch 'network-id' of https://github.com/Transia-RnD/xrpl.js into network-id 2023-06-14 14:32:04 -04:00
Phu Pham
5f5065480a emit connected after server_info 2023-06-14 14:31:35 -04:00
pdp2121
fd550a5bd2 Merge branch 'main' into network-id 2023-06-12 18:10:42 -04:00
Phu Pham
1264d45844 Merge branch 'network-id' of https://github.com/Transia-RnD/xrpl.js into network-id 2023-06-12 18:09:30 -04:00
Phu Pham
8c3ef3c2bd small logic fix for version 2023-06-12 18:09:27 -04:00
pdp2121
2e3bc5d9bb Merge branch 'main' into network-id 2023-06-12 14:29:19 -04:00
Phu Pham
acf76f3c24 Merge branch 'network-id' of https://github.com/Transia-RnD/xrpl.js into network-id 2023-06-12 14:22:11 -04:00
Phu Pham
2fa361cda6 docs fixes 2023-06-12 14:17:03 -04:00
Phu Pham
3a865eba5f add try catch for server_info req 2023-06-12 11:25:45 -04:00
pdp2121
d7d6ae873d Update packages/xrpl/src/sugar/autofill.ts
Co-authored-by: Mayukha Vadari <mvadari@gmail.com>
2023-06-07 14:22:50 -04:00
Phu Pham
a4a6306ade add base tx test 2023-06-06 12:26:22 -04:00
Phu Pham
a940b7c408 fix test mock setup 2023-06-06 12:12:41 -04:00
pdp2121
e85d077d24 Merge branch 'main' into network-id 2023-06-05 14:01:04 -04:00
Phu Pham
6e4531b3c3 fix lint 2023-06-05 13:19:41 -04:00
Phu Pham
7d54c1e059 fix mock test 2023-06-05 13:16:27 -04:00
Phu Pham
faea1abafb use isEarlierRippledVersion as helper for autofill 2023-06-05 11:33:55 -04:00
Phu Pham
cabe79d4c0 add TODO to remove buildVersion in the future 2023-06-05 11:28:18 -04:00
Phu Pham
1d9f9ae7c5 make a txNeedsNetworkID helper function 2023-06-01 17:45:09 -04:00
pdp2121
56cd415a12 Merge branch 'main' into network-id 2023-06-01 17:21:42 -04:00
pdp2121
f6d26bf0b7 Update packages/xrpl/src/sugar/autofill.ts
Co-authored-by: Jackson Mills <aim4math@gmail.com>
2023-06-01 17:20:34 -04:00
Phu Pham
7f078b6a3b add more unit tests 2023-05-30 17:40:34 -04:00
Phu Pham
261669b346 fix integration test 2023-05-30 16:34:41 -04:00
Phu Pham
258f9a391a fix autofill logic 2023-05-29 12:16:23 -04:00
Phu Pham
ce0e6e103a fix mock rippled test 2023-05-26 17:07:37 -04:00
Phu Pham
e656de772a add hooks testnet check for autofill 2023-05-26 16:53:06 -04:00
Phu Pham
6c7d2538e9 get networkid on connection 2023-05-26 14:32:52 -04:00
Denis Angell
bf63fd0173 add autofill tests 2023-05-19 00:24:49 +00:00
Denis Angell
aed48e77bc Merge branch 'main' into network-id 2023-03-27 19:32:02 +00:00
Denis Angell
b26a46b020 fixup!
remove autoset network id

remove my mistake

fixup!
2023-02-24 16:50:08 -05:00
Denis Angell
cbd4f9f350 remove set network func 2023-02-24 16:28:13 -05:00
Denis Angell
37c03460af update getNetworkID 2023-02-24 16:23:02 -05:00
Denis Angell
55fe97d53d update autofill 2023-02-24 16:22:42 -05:00
Denis Angell
07ff7630b1 network id option 3
fixup

add network id to server info fixture

fixup!

Revert "update tests and fixtures"

This reverts commit a5deee1274.
2023-02-23 15:00:10 -05:00
Denis Angell
a5deee1274 update tests and fixtures 2023-02-22 17:47:42 -05:00
Denis Angell
294c1cb083 add network id to autofill 2023-02-22 17:47:42 -05:00
Denis Angell
69c705874f update base tx 2023-02-22 17:47:42 -05:00
Denis Angell
e36912c60a update server info 2023-02-22 17:47:42 -05:00
Denis Angell
3d06185867 Merge branch 'main' into network-id 2023-02-22 17:44:21 -05:00
Denis Angell
b241779f10 add network id to base transaction 2023-02-16 23:01:23 -05:00
Denis Angell
c809bd87e4 update definitions 2023-02-16 23:01:08 -05:00
12 changed files with 334 additions and 6 deletions

View File

@@ -321,6 +321,16 @@
"type": "UInt16"
}
],
[
"NetworkID",
{
"nth": 1,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "UInt32"
}
],
[
"Flags",
{
@@ -2176,6 +2186,9 @@
"telCAN_NOT_QUEUE_BLOCKED": -389,
"telCAN_NOT_QUEUE_FEE": -388,
"telCAN_NOT_QUEUE_FULL": -387,
"telWRONG_NETWORK": -386,
"telREQUIRES_NETWORK_ID": -385,
"telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384,
"temMALFORMED": -299,
"temBAD_AMOUNT": -298,

View File

@@ -205,6 +205,18 @@ class Client extends EventEmitter {
*/
public readonly maxFeeXRP: string
/**
* Network ID of the server this client is connected to
*
*/
public networkID: number | undefined
/**
* Rippled Version used by the server this client is connected to
*
*/
public buildVersion: string | undefined
/**
* Creates a new Client with a websocket connection to a rippled server.
*
@@ -230,8 +242,8 @@ class Client extends EventEmitter {
this.emit('error', errorCode, errorMessage, data)
})
this.connection.on('connected', () => {
this.emit('connected')
this.connection.on('reconnect', () => {
this.connection.on('connected', () => this.emit('connected'))
})
this.connection.on('disconnected', (code: number) => {
@@ -568,6 +580,22 @@ class Client extends EventEmitter {
return results
}
/**
* Get networkID and buildVersion from server_info
*/
public async getServerInfo(): Promise<void> {
try {
const response = await this.request({
command: 'server_info',
})
this.networkID = response.result.info.network_id ?? undefined
this.buildVersion = response.result.info.build_version
} catch (error) {
// eslint-disable-next-line no-console -- Print the error to console but allows client to be connected.
console.error(error)
}
}
/**
* Tells the Client instance to connect to its rippled server.
*
@@ -588,7 +616,10 @@ class Client extends EventEmitter {
* @category Network
*/
public async connect(): Promise<void> {
return this.connection.connect()
return this.connection.connect().then(async () => {
await this.getServerInfo()
this.emit('connected')
})
}
/**

View File

@@ -136,6 +136,10 @@ export interface ServerInfoResponse extends BaseResponse {
* overall network's load factor.
*/
load_factor?: number
/**
* The network id of the server.
*/
network_id?: number
/**
* Current multiplier to the transaction cost based on
* load to this server.

View File

@@ -157,6 +157,10 @@ export interface BaseTransaction {
* account it says it is from.
*/
TxnSignature?: string
/**
* The network id of the transaction.
*/
NetworkID?: number
}
/**
@@ -250,6 +254,9 @@ export function validateBaseTransaction(common: Record<string, unknown>): void {
) {
throw new ValidationError('BaseTransaction: invalid TxnSignature')
}
if (common.NetworkID !== undefined && typeof common.NetworkID !== 'number') {
throw new ValidationError('BaseTransaction: invalid NetworkID')
}
}
/**

View File

@@ -12,6 +12,13 @@ import getFeeXrp from './getFeeXrp'
// Expire unconfirmed transactions after 20 ledger versions, approximately 1 minute, by default
const LEDGER_OFFSET = 20
// Sidechains are expected to have network IDs above this.
// Networks with ID above this restricted number are expected specify an accurate NetworkID field
// in every transaction to that chain to prevent replay attacks.
// Mainnet and testnet are exceptions. More context: https://github.com/XRPLF/rippled/pull/4370
const RESTRICTED_NETWORKS = 1024
const REQUIRED_NETWORKID_VERSION = '1.11.0'
const HOOKS_TESTNET_ID = 21338
interface ClassicAccountAndTag {
classicAccount: string
tag: number | false | undefined
@@ -70,8 +77,10 @@ async function autofill<T extends Transaction>(
setValidAddresses(tx)
setTransactionFlagsToNumber(tx)
const promises: Array<Promise<void>> = []
if (tx.NetworkID == null) {
tx.NetworkID = txNeedsNetworkID(this) ? this.networkID : undefined
}
if (tx.Sequence == null) {
promises.push(setNextValidSequenceNumber(this, tx))
}
@@ -88,6 +97,101 @@ async function autofill<T extends Transaction>(
return Promise.all(promises).then(() => tx)
}
/**
* Determines whether the source rippled version is not later than the target rippled version.
* Example usage: isNotLaterRippledVersion('1.10.0', '1.11.0') returns true.
* isNotLaterRippledVersion('1.10.0', '1.10.0-b1') returns false.
*
* @param source -- The source rippled version.
* @param target -- The target rippled version.
* @returns True if source is earlier than target, false otherwise.
*/
// eslint-disable-next-line max-lines-per-function, max-statements -- Disable for this helper functions.
function isNotLaterRippledVersion(source: string, target: string): boolean {
if (source === target) {
return true
}
const sourceDecomp = source.split('.')
const targetDecomp = target.split('.')
const sourceMajor = parseInt(sourceDecomp[0], 10)
const sourceMinor = parseInt(sourceDecomp[1], 10)
const targetMajor = parseInt(targetDecomp[0], 10)
const targetMinor = parseInt(targetDecomp[1], 10)
// Compare major version
if (sourceMajor !== targetMajor) {
return sourceMajor < targetMajor
}
// Compare minor version
if (sourceMinor !== targetMinor) {
return sourceMinor < targetMinor
}
const sourcePatch = sourceDecomp[2].split('-')
const targetPatch = targetDecomp[2].split('-')
const sourcePatchVersion = parseInt(sourcePatch[0], 10)
const targetPatchVersion = parseInt(targetPatch[0], 10)
// Compare patch version
if (sourcePatchVersion !== targetPatchVersion) {
return sourcePatchVersion < targetPatchVersion
}
// Compare release version
if (sourcePatch.length !== targetPatch.length) {
return sourcePatch.length > targetPatch.length
}
if (sourcePatch.length === 2) {
// Compare different release types
if (!sourcePatch[1][0].startsWith(targetPatch[1][0])) {
return sourcePatch[1] < targetPatch[1]
}
// Compare beta version
if (sourcePatch[1].startsWith('b')) {
return (
parseInt(sourcePatch[1].slice(1), 10) <
parseInt(targetPatch[1].slice(1), 10)
)
}
// Compare rc version
return (
parseInt(sourcePatch[1].slice(2), 10) <
parseInt(targetPatch[1].slice(2), 10)
)
}
return false
}
/**
* Determine if the transaction required a networkID to be valid.
* Transaction needs networkID if later than restricted ID and either the network is hooks testnet
* or build version is >= 1.11.0
*
* @param client -- The connected client.
* @returns True if required networkID, false otherwise.
*/
function txNeedsNetworkID(client: Client): boolean {
if (
client.networkID !== undefined &&
client.networkID > RESTRICTED_NETWORKS
) {
// TODO: remove the buildVersion logic when 1.11.0 is out and widely used.
// Issue: https://github.com/XRPLF/xrpl.js/issues/2339
if (
(client.buildVersion &&
isNotLaterRippledVersion(
REQUIRED_NETWORKID_VERSION,
client.buildVersion,
)) ||
client.networkID === HOOKS_TESTNET_ID
) {
return true
}
}
return false
}
function setValidAddresses(tx: Transaction): void {
validateAccountAddress(tx, 'Account', 'SourceTag')
// eslint-disable-next-line @typescript-eslint/dot-notation -- Destination can exist on Transaction

View File

@@ -15,13 +15,32 @@ import {
} from '../setupClient'
import { assertRejects } from '../testUtils'
const NetworkID = 1025
const Fee = '10'
const Sequence = 1432
const LastLedgerSequence = 2908734
const HOOKS_TESTNET_ID = 21338
describe('client.autofill', function () {
let testContext: XrplTestContext
async function setupMockRippledVersionAndID(
buildVersion: string,
networkID: number,
): Promise<void> {
await testContext.client.disconnect()
rippled.server_info.withNetworkId.result.info.build_version = buildVersion
rippled.server_info.withNetworkId.result.info.network_id = networkID
testContext.client.connection.on('connected', () => {
testContext.mockRippled?.addResponse(
'server_info',
rippled.server_info.withNetworkId,
)
})
await testContext.client.connect()
}
beforeEach(async () => {
testContext = await setupClient()
})
@@ -32,17 +51,116 @@ describe('client.autofill', function () {
TransactionType: 'DepositPreauth',
Account: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
Authorize: 'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
NetworkID,
Fee,
Sequence,
LastLedgerSequence,
}
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, NetworkID)
assert.strictEqual(txResult.Fee, Fee)
assert.strictEqual(txResult.Sequence, Sequence)
assert.strictEqual(txResult.LastLedgerSequence, LastLedgerSequence)
})
it('ignores network ID if missing', async function () {
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, undefined)
})
// NetworkID is required in transaction for network > 1024 and from version 1.11.0 or later.
// More context: https://github.com/XRPLF/rippled/pull/4370
it('overrides network ID if > 1024 and version is later than 1.11.0', async function () {
await setupMockRippledVersionAndID('1.11.1', 1025)
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, 1025)
})
// NetworkID is only required in transaction for version 1.11.0 or later.
// More context: https://github.com/XRPLF/rippled/pull/4370
it('ignores network ID if > 1024 but version is earlier than 1.11.0', async function () {
await setupMockRippledVersionAndID('1.10.0', 1025)
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, undefined)
})
// NetworkID <= 1024 does not require a newtorkID in transaction.
// More context: https://github.com/XRPLF/rippled/pull/4370
it('ignores network ID if <= 1024', async function () {
await setupMockRippledVersionAndID('1.11.1', 1023)
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, undefined)
})
// Hooks Testnet requires networkID in transaction regardless of version.
// More context: https://github.com/XRPLF/rippled/pull/4370
it('overrides network ID for hooks testnet', async function () {
await setupMockRippledVersionAndID('1.10.1', HOOKS_TESTNET_ID)
const tx: Payment = {
TransactionType: 'Payment',
Account: 'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
Amount: '1234',
Destination: 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
Fee,
Sequence,
LastLedgerSequence,
}
testContext.mockRippled!.addResponse('ledger', rippled.ledger.normal)
const txResult = await testContext.client.autofill(tx)
assert.strictEqual(txResult.NetworkID, HOOKS_TESTNET_ID)
})
it('converts Account & Destination X-address to their classic address', async function () {
const tx: Payment = {
TransactionType: 'Payment',

View File

@@ -10,7 +10,7 @@ import type {
import { destroyServer, getFreePort } from './testUtils'
function createResponse(
export function createResponse(
request: { id: number | string },
response: Record<string, unknown>,
): string {

View File

@@ -12,6 +12,7 @@ import iouPartialPayment from './partialPaymentIOU.json'
import xrpPartialPayment from './partialPaymentXRP.json'
import normalServerInfo from './serverInfo.json'
import highLoadFactor from './serverInfoHighLoadFactor.json'
import withNetworkIDServerInfo from './serverInfoNetworkID.json'
import consensusStream from './streams/consensusPhase.json'
import ledgerStream from './streams/ledger.json'
import manifestStream from './streams/manifest.json'
@@ -84,6 +85,7 @@ const ledger_data = {
const server_info = {
normal: normalServerInfo,
highLoadFactor,
withNetworkId: withNetworkIDServerInfo,
}
const tx = {

View File

@@ -0,0 +1,31 @@
{
"id": 0,
"status": "success",
"type": "response",
"result": {
"info": {
"build_version": "1.11.0-rc2",
"complete_ledgers": "37621036-38327626",
"hostid": "JANE",
"io_latency_ms": 1,
"last_close": {
"converge_time_s": 2,
"proposers": 6
},
"load_factor": 1,
"network_id": 1,
"peers": 113,
"pubkey_node": "n9L6MAkAvZKakewLSJPkCKLxuSQ9jrYXJBd2L4fouhpXauyFh6ZM",
"server_state": "full",
"validated_ledger": {
"age": 0,
"base_fee_xrp": 0.00001,
"hash": "A219F66BB8C9992E80A3C93A5EA408CD54B8F47F2AC1246C271C495F833752BA",
"reserve_base_xrp": 10,
"reserve_inc_xrp": 2,
"seq": 38327626
},
"validation_quorum": 5
}
}
}

View File

@@ -22,7 +22,7 @@ describe('mock rippled tests', function () {
}
await assertRejects(
testContext.client.request({ command: 'server_info' }),
testContext.client.request({ command: 'account_info' }),
RippledError,
)
})

View File

@@ -232,4 +232,17 @@ describe('BaseTransaction', function () {
'BaseTransaction: invalid Memos',
)
})
it(`Handles invalid NetworkID`, function () {
const invalidNetworkID = {
Account: 'r97KeayHuEsDwyU1yPBVtMLLoQr79QcRFe',
TransactionType: 'Payment',
NetworkID: '1024',
}
assert.throws(
() => validateBaseTransaction(invalidNetworkID),
ValidationError,
'BaseTransaction: invalid NetworkID',
)
})
})

View File

@@ -5,6 +5,7 @@ import BroadcastClient from '../src/client/BroadcastClient'
import createMockRippled, {
type MockedWebSocketServer,
} from './createMockRippled'
import rippled from './fixtures/rippled'
import { destroyServer, getFreePort } from './testUtils'
export interface XrplTestContext {
@@ -29,6 +30,10 @@ async function setupMockRippledConnection(
context.client.on('error', () => {
// We must have an error listener attached for reconnect errors
})
context.mockRippled?.addResponse(
'server_info',
rippled.server_info.withNetworkId,
)
return context.client.connect().then(() => context)
}