diff --git a/src/containers/Transactions/test/SimpleTab.test.tsx b/src/containers/Transactions/test/SimpleTab.test.tsx index 652ded5b7..bce4d5502 100644 --- a/src/containers/Transactions/test/SimpleTab.test.tsx +++ b/src/containers/Transactions/test/SimpleTab.test.tsx @@ -9,23 +9,36 @@ import { SimpleTab } from '../SimpleTab' import summarize from '../../../rippled/lib/txSummary' import i18n from '../../../i18n/testConfig' import { expectSimpleRowText } from '../../shared/components/Transaction/test' +import SocketContext from '../../shared/SocketContext' +import MockWsClient from '../../test/mockWsClient' import { queryClient } from '../../shared/QueryClient' describe('SimpleTab container', () => { + let client const createWrapper = (tx, width = 1200) => mount( - + + + , ) + beforeEach(() => { + client = new MockWsClient() + }) + + afterEach(() => { + client.close() + }) + it('renders EnableAmendment without crashing', () => { const wrapper = createWrapper(EnableAmendment) wrapper.unmount() diff --git a/src/containers/shared/amendmentUtils.ts b/src/containers/shared/amendmentUtils.ts index 96e189e33..b9e21af55 100644 --- a/src/containers/shared/amendmentUtils.ts +++ b/src/containers/shared/amendmentUtils.ts @@ -1,14 +1,9 @@ -import { sha512 } from '@xrplf/isomorphic/sha512' -import { hexToBytes, bytesToHex, stringToHex } from '@xrplf/isomorphic/utils' import axios from 'axios' import { localizeDate } from './utils' -const cachedAmendmentIDs: any = {} let cachedRippledVersions: any = {} -const ACTIVE_AMENDMENT_REGEX = /^\s*REGISTER_F[A-Z]+\s*\((\S+),\s*.*$/ -const RETIRED_AMENDMENT_REGEX = /^ .*retireFeature\("(\S+)"\)[,;].*$/ const TIME_ZONE = 'UTC' const DATE_OPTIONS = { hour: 'numeric', @@ -31,42 +26,6 @@ export function getExpectedDate(date: string, language: string) { ) } -async function fetchAmendmentNames() { - const response = await axios.get( - 'https://raw.githubusercontent.com/XRPLF/rippled/develop/src/libxrpl/protocol/Feature.cpp', - ) - const text = response.data - - const amendmentNames: string[] = [] - text.split('\n').forEach((line: string) => { - const name = line.match(ACTIVE_AMENDMENT_REGEX) - if (name) { - amendmentNames.push(name[1]) - } else { - const name2 = line.match(RETIRED_AMENDMENT_REGEX) - name2 && amendmentNames.push(name2[1]) - } - }) - return amendmentNames -} - -function sha512Half(bytes: Uint8Array) { - return bytesToHex(sha512(bytes)).slice(0, 64) -} - -export async function nameOfAmendmentID(id: string) { - if (cachedAmendmentIDs[id]) { - return cachedAmendmentIDs[id] - } - // The Amendment ID is the hash of the Amendment name - const amendmentNames = await fetchAmendmentNames() - amendmentNames.forEach((name) => { - cachedAmendmentIDs[sha512Half(hexToBytes(stringToHex(name)))] = name - }) - - return cachedAmendmentIDs[id] -} - async function fetchMinRippledVersions() { const response = await axios.get( 'https://raw.githubusercontent.com/XRPLF/xrpl-dev-portal/master/resources/known-amendments.md', diff --git a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx index 184faee53..35f885c53 100644 --- a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx +++ b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx @@ -1,15 +1,13 @@ -import { useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { useLanguage } from '../../../hooks' import { SimpleRow } from '../SimpleRow' import { TransactionSimpleProps } from '../types' import { EnableAmendment } from './types' -import { - getExpectedDate, - getRippledVersion, - nameOfAmendmentID, -} from '../../../amendmentUtils' +import { getExpectedDate, getRippledVersion } from '../../../amendmentUtils' import { AMENDMENT_ROUTE } from '../../../../App/routes' import { RouteLink } from '../../../routing' +import SocketContext from '../../../SocketContext' +import { getFeature } from '../../../../../rippled/lib/rippled' const states = { loading: 'Loading', @@ -22,31 +20,21 @@ export const Simple = ({ data }: TransactionSimpleProps) => { name: states.loading, minRippledVersion: states.loading, }) + const rippledSocket = useContext(SocketContext) useEffect(() => { - nameOfAmendmentID(data.instructions.Amendment).then((name: string) => { - if (name) { - getRippledVersion(name).then((rippledVersion) => { - if (rippledVersion) { - setAmendmentDetails({ - name, - minRippledVersion: rippledVersion, - }) - } else { - setAmendmentDetails({ - name, - minRippledVersion: states.unknown, - }) - } - }) - } else { + const amendmentId = data.instructions.Amendment + getFeature(rippledSocket, amendmentId).then((feature) => { + const name = + feature && feature[amendmentId] ? feature[amendmentId].name : '' + getRippledVersion(name).then((rippledVersion) => { setAmendmentDetails({ - name: states.unknown, - minRippledVersion: states.unknown, + name: name || states.unknown, + minRippledVersion: rippledVersion || states.unknown, }) - } + }) }) - }, [data.instructions.Amendment]) + }, [data.instructions.Amendment, rippledSocket]) let amendmentStatus = states.unknown let expectedDate: string | null = states.unknown diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx index d04002da3..476931b4d 100644 --- a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx +++ b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx @@ -6,11 +6,11 @@ import { Simple } from '../Simple' import mockEnableAmendmentWithEnabled from './mock_data/EnableAmendmentWithEnabled.json' import mockEnableAmendmentWithMinority from './mock_data/EnableAmendmentWithMinority.json' import mockEnableAmendmentWithMajority from './mock_data/EnableAmendmentWithMajority.json' -import { - getRippledVersion, - nameOfAmendmentID, -} from '../../../../amendmentUtils' +import mockFeatureExpandedSignerList from './mock_data/FeatureExpandedSignerList.json' +import mockFeatureNegativeUNL from './mock_data/FeatureNegativeUNL.json' +import { getRippledVersion } from '../../../../amendmentUtils' import { flushPromises } from '../../../../../test/utils' +import { getFeature } from '../../../../../../rippled/lib/rippled' const createWrapper = createSimpleWrapperFactory(Simple, i18n) @@ -21,22 +21,35 @@ jest.mock('../../../../amendmentUtils', () => { __esModule: true, ...originalModule, getRippledVersion: jest.fn(), - nameOfAmendmentID: jest.fn(), + } +}) + +jest.mock('../../../../../../rippled/lib/rippled', () => { + const originalModule = jest.requireActual( + '../../../../../../rippled/lib/rippled', + ) + + return { + __esModule: true, + ...originalModule, + getFeature: jest.fn(), } }) const mockedGetRippledVersion = getRippledVersion as jest.MockedFunction< typeof getRippledVersion > -const mockedNameOfAmendmentID = nameOfAmendmentID as jest.MockedFunction< - typeof nameOfAmendmentID -> + +const mockedGetFeature = getFeature as jest.MockedFunction describe('EnableAmendment: Simple', () => { + afterEach(() => { + mockedGetFeature.mockReset() + }) it('renders tx that causes an amendment to loose majority', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1')) - mockedNameOfAmendmentID.mockImplementation(() => - Promise.resolve('ExpandedSignerList'), + mockedGetFeature.mockImplementation(() => + Promise.resolve(mockFeatureExpandedSignerList), ) const wrapper = createWrapper(mockEnableAmendmentWithMinority) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') @@ -58,8 +71,8 @@ describe('EnableAmendment: Simple', () => { it('renders tx that causes an amendment to gain majority', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1')) - mockedNameOfAmendmentID.mockImplementation(() => - Promise.resolve('ExpandedSignerList'), + mockedGetFeature.mockImplementation(() => + Promise.resolve(mockFeatureExpandedSignerList), ) const wrapper = createWrapper(mockEnableAmendmentWithMajority) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') @@ -86,8 +99,8 @@ describe('EnableAmendment: Simple', () => { it('renders tx that enables an amendment', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3')) - mockedNameOfAmendmentID.mockImplementation(() => - Promise.resolve('NegativeUNL'), + mockedGetFeature.mockImplementation(() => + Promise.resolve(mockFeatureNegativeUNL), ) const wrapper = createWrapper(mockEnableAmendmentWithEnabled) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') @@ -108,7 +121,7 @@ describe('EnableAmendment: Simple', () => { it('renders tx that cannot determine version or name', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('')) - mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve('')) + mockedGetFeature.mockImplementation(() => Promise.resolve(null)) const wrapper = createWrapper(mockEnableAmendmentWithEnabled) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') expectSimpleRowText(wrapper, 'name', 'Loading') @@ -124,8 +137,8 @@ describe('EnableAmendment: Simple', () => { it('renders tx that cannot determine version', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('')) - mockedNameOfAmendmentID.mockImplementation(() => - Promise.resolve('NegativeUNL'), + mockedGetFeature.mockImplementation(() => + Promise.resolve(mockFeatureNegativeUNL), ) const wrapper = createWrapper(mockEnableAmendmentWithEnabled) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') @@ -142,7 +155,7 @@ describe('EnableAmendment: Simple', () => { it('renders tx that cannot determine name', async () => { mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3')) - mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve('')) + mockedGetFeature.mockImplementation(() => Promise.resolve(null)) const wrapper = createWrapper(mockEnableAmendmentWithEnabled) expectSimpleRowLabel(wrapper, 'name', 'Amendment Name') expectSimpleRowText(wrapper, 'name', 'Loading') @@ -153,6 +166,6 @@ describe('EnableAmendment: Simple', () => { wrapper.update() expectSimpleRowText(wrapper, 'name', 'Unknown') - expectSimpleRowText(wrapper, 'version', 'Unknown') + expectSimpleRowText(wrapper, 'version', 'v1.7.3') }) }) diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json new file mode 100644 index 000000000..09635f407 --- /dev/null +++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json @@ -0,0 +1,7 @@ +{ + "B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856": { + "enabled": false, + "name": "ExpandedSignerList", + "supported": true + } +} diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json new file mode 100644 index 000000000..56341a552 --- /dev/null +++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json @@ -0,0 +1,7 @@ +{ + "B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076": { + "enabled": false, + "name": "NegativeUNL", + "supported": true + } +} diff --git a/src/containers/shared/test/amendmentUtils.test.ts b/src/containers/shared/test/amendmentUtils.test.ts index f7f21e645..e4ef9d4e6 100644 --- a/src/containers/shared/test/amendmentUtils.test.ts +++ b/src/containers/shared/test/amendmentUtils.test.ts @@ -1,170 +1,4 @@ -import { getRippledVersion, nameOfAmendmentID } from '../amendmentUtils' - -const nameTable = [ - [ - '32A122F1352A4C7B3A6D790362CC34749C5E57FCE896377BFDC6CCD14F6CD627', - 'NonFungibleTokensV1_1', - ], - [ - '4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373', - 'MultiSign', - ], - [ - '6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC', - 'TrustSetAuth', - ], - [ - '42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE', - 'FeeEscalation', - ], - [ - '08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647', - 'PayChan', - ], - ['740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11', 'Flow'], - [ - '1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146', - 'CryptoConditions', - ], - [ - '532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164', - 'TickSize', - ], - [ - 'E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA', - 'fix1368', - ], - [ - '07D43DCE529B15A10827E5E04943B496762F9A88E3268269D69C44BE49E21104', - 'Escrow', - ], - [ - '86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90', - 'CryptoConditionsSuite', - ], - [ - '42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC', - 'fix1373', - ], - [ - 'DC9CA96AEA1DCF83E527D1AFC916EFAF5D27388ECA4060A88817C1238CAEE0BF', - 'EnforceInvariants', - ], - [ - '3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC', - 'FlowCross', - ], - [ - 'CC5ABAE4F3EC92E94A59B1908C2BE82D2228B6485C00AFF8F22DF930D89C194E', - 'SortedDirectories', - ], - [ - 'B4D44CC3111ADD964E846FC57760C8B50FFCD5A82C86A72756F6B058DDDF96AD', - 'fix1201', - ], - [ - '6C92211186613F9647A89DFFBAB8F94C99D4C7E956D495270789128569177DA1', - 'fix1512', - ], - [ - '67A34F2CF55BFC0F93AACD5B281413176FEE195269FA6D95219A2DF738671172', - 'fix1513', - ], - [ - 'B9E739B8296B4A1BB29BE990B17D66E21B62A300A909F25AC55C22D6C72E1F9D', - 'fix1523', - ], - [ - '1D3463A5891F9E589C5AE839FFAC4A917CE96197098A1EF22304E1BC5B98A454', - 'fix1528', - ], - [ - 'F64E1EABBE79D55B3BB82020516CEC2C582A98A6BFE20FBE9BB6A0D233418064', - 'DepositAuth', - ], - [ - '157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1', - 'Checks', - ], - [ - '7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C', - 'fix1571', - ], - [ - 'CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2', - 'fix1543', - ], - [ - '58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F', - 'fix1623', - ], - [ - '3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194', - 'DepositPreauth', - ], - [ - '5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE', - 'fix1515', - ], - [ - 'FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288', - 'fix1578', - ], - [ - '586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D', - 'MultiSignReserve', - ], - [ - '2CD5286D8D687E98B41102BDD797198E81EA41DF7BD104E6561FEB104EFF2561', - 'fixTakerDryOfferRemoval', - ], - [ - 'C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD37', - 'fixMasterKeyAsRegularKey', - ], - [ - '8F81B066ED20DAECA20DF57187767685EEF3980B228E0667A650BAF24426D3B4', - 'fixCheckThreading', - ], - [ - '621A0B264970359869E3C0363A899909AAB7A887C8B73519E4ECF952D33258A8', - 'fixPayChanRecipientOwnerDir', - ], - [ - '30CD365592B8EE40489BA01AE2F7555CAC9C983145871DC82A42A31CF5BAE7D9', - 'DeletableAccounts', - ], - [ - '89308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D5232402AE82A6A7220D953', - 'fixQualityUpperBound', - ], - [ - '00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B37E1EDAC', - 'RequireFullyCanonicalSig', - ], - [ - '25BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A3154FDE4A7676E', - 'fix1781', - ], - [ - '1F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D44243A684103EF88', - 'HardenedValidations', - ], - [ - '4F46DF03559967AC60F2EB272FEFE3928A7594A45FF774B87A7E540DB0F8F068', - 'fixAmendmentMajorityCalc', - ], -] - -describe('nameOfAmendmentID: ', () => { - it.each(nameTable)( - `should resolve amendment id "%s" to "%s"`, - async (id, name) => { - const retrievedName = await nameOfAmendmentID(id) - return expect(retrievedName).toEqual(name) - }, - ) -}) +import { getRippledVersion } from '../amendmentUtils' const versionTable = [ ['NonFungibleTokensV1_1', 'v1.9.2'], diff --git a/src/rippled/lib/rippled.js b/src/rippled/lib/rippled.js index c77c6584d..dc7737309 100644 --- a/src/rippled/lib/rippled.js +++ b/src/rippled/lib/rippled.js @@ -557,6 +557,21 @@ const getAMMInfo = (rippledSocket, asset, asset2) => { }) } +// get feature +const getFeature = (rippledSocket, amendmentId) => { + const request = { + command: 'feature', + feature: amendmentId, + } + return query(rippledSocket, request).then((resp) => { + if (resp == null || resp.error_message) { + return null + } + + return resp + }) +} + const getMPTIssuance = (rippledSocket, tokenId) => query(rippledSocket, { command: 'ledger_entry', @@ -624,6 +639,7 @@ export { getSellNFToffers, getNFTTransactions, getAMMInfo, + getFeature, getMPTIssuance, getAccountMPTs, }