Skip to content

Commit

Permalink
Use feature rpc for EnableAmendment (#1052)
Browse files Browse the repository at this point in the history
## High Level Overview of Change

<!--
Please include a summary/list of the changes.
If too broad, please consider splitting into multiple PRs.
-->

With the new feature RPC introduced in [rippled
2.2.0](https://github.com/XRPLF/rippled/releases/tag/2.2.0),
EnableAmendment tx Simple page can populate its data using amendment id
without dependence on xrpl.org and rippled docs (which change pretty
frequently):


https://github.com/ripple/explorer/blob/staging/src/containers/shared/amendmentUtils.ts

The logic would be simplified and consistency would be improved

This resolves: #1001

### Type of Change

<!--
Please check relevant options, delete irrelevant ones.
-->

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Refactor (non-breaking change that only restructures code)
- [ ] Tests (You added tests for code that already exists, or your new
feature included in this PR)
- [ ] Documentation Updates
- [ ] Translation Updates
- [ ] Release
  • Loading branch information
pdp2121 authored Oct 15, 2024
1 parent 6d1d9d1 commit 2279513
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 257 deletions.
21 changes: 17 additions & 4 deletions src/containers/Transactions/test/SimpleTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Router>
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<SimpleTab
data={{ processed: tx, summary: summarize(tx, true).details }}
width={width}
/>
<SocketContext.Provider value={client}>
<SimpleTab
data={{ processed: tx, summary: summarize(tx, true).details }}
width={width}
/>
</SocketContext.Provider>
</I18nextProvider>
</QueryClientProvider>
</Router>,
)

beforeEach(() => {
client = new MockWsClient()
})

afterEach(() => {
client.close()
})

it('renders EnableAmendment without crashing', () => {
const wrapper = createWrapper(EnableAmendment)
wrapper.unmount()
Expand Down
41 changes: 0 additions & 41 deletions src/containers/shared/amendmentUtils.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -22,31 +20,21 @@ export const Simple = ({ data }: TransactionSimpleProps<EnableAmendment>) => {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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<typeof getFeature>

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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -153,6 +166,6 @@ describe('EnableAmendment: Simple', () => {
wrapper.update()

expectSimpleRowText(wrapper, 'name', 'Unknown')
expectSimpleRowText(wrapper, 'version', 'Unknown')
expectSimpleRowText(wrapper, 'version', 'v1.7.3')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856": {
"enabled": false,
"name": "ExpandedSignerList",
"supported": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076": {
"enabled": false,
"name": "NegativeUNL",
"supported": true
}
}
Loading

0 comments on commit 2279513

Please sign in to comment.