Skip to content

Commit

Permalink
List community RPC nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
shazarre committed Dec 3, 2024
1 parent 0051202 commit 4953d44
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 0 deletions.
132 changes: 132 additions & 0 deletions packages/cli/src/commands/validatorgroup/rpc-urls-l2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { newKitFromWeb3 } from '@celo/contractkit'
import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts'
import { testWithAnvilL2, withImpersonatedAccount } from '@celo/dev-utils/lib/anvil-test'
import { ClaimTypes, IdentityMetadataWrapper } from '@celo/metadata-claims'
import { ux } from '@oclif/core'
import { setupGroupAndAffiliateValidator } from '../../test-utils/chain-setup'
import { stripAnsiCodesAndTxHashes, testLocallyWithWeb3Node } from '../../test-utils/cliUtils'
import RpcUrls from './rpc-urls'

testWithAnvilL2('validatorgroup:rpc-urls cmd', async (web3) => {
jest.spyOn(IdentityMetadataWrapper, 'fetchFromURL').mockImplementation(async (_, url) => {
const validatorAddress = url.split('/').pop()

return new IdentityMetadataWrapper({
claims: [
{
type: ClaimTypes.RPC_URL,
timestamp: Date.now(),
rpcUrl: `https://example.com:8545/${validatorAddress}`,
},
],
} as any) // that data is enough
})

const setMetadataUrlForValidator = async (
accountsWrapper: AccountsWrapper,
validator: string
) => {
await withImpersonatedAccount(
web3,
validator,
async () => {
await accountsWrapper
.setMetadataURL(`https://example.com/metadata/${validator}`)
.sendAndWaitForReceipt({
from: validator,
})
},
1_000_000_000_000_000n
)
}

const EXISTING_VALIDATORS = [
'0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
'0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
'0x976EA74026E726554dB657fA54763abd0C3a0aa9',
]

beforeEach(async () => {
const kit = newKitFromWeb3(web3)
const accountsWrapper = await kit.contracts.getAccounts()

const [nonElectedGroupAddress, validatorAddress] = await web3.eth.getAccounts()

await setupGroupAndAffiliateValidator(kit, nonElectedGroupAddress, validatorAddress)
await accountsWrapper
.setName('Test group')
.sendAndWaitForReceipt({ from: nonElectedGroupAddress })

for (const validator of [...EXISTING_VALIDATORS, validatorAddress]) {
await setMetadataUrlForValidator(accountsWrapper, validator)
}
})

it('shows the RPC URLs of the elected validator groups', async () => {
const logMock = jest.spyOn(console, 'log')
const writeMock = jest.spyOn(ux.write, 'stdout')

await testLocallyWithWeb3Node(RpcUrls, ['--csv'], web3)

expect(writeMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes)))
.toMatchInlineSnapshot(`
[
[
"Validator Group Name,RPC URL,Validator Address
",
],
[
"cLabs,https://example.com:8545/0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
",
],
[
"cLabs,https://example.com:8545/0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
",
],
[
"cLabs,https://example.com:8545/0x976EA74026E726554dB657fA54763abd0C3a0aa9,0x976EA74026E726554dB657fA54763abd0C3a0aa9
",
],
]
`)
expect(
logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))
).toMatchInlineSnapshot(`[]`)
})

it('shows all the RPC URLs', async () => {
const logMock = jest.spyOn(console, 'log')
const writeMock = jest.spyOn(ux.write, 'stdout')

await testLocallyWithWeb3Node(RpcUrls, ['--all', '--csv'], web3)

expect(writeMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes)))
.toMatchInlineSnapshot(`
[
[
"Validator Group Name,RPC URL,Validator Address
",
],
[
"cLabs,https://example.com:8545/0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
",
],
[
"cLabs,https://example.com:8545/0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
",
],
[
"cLabs,https://example.com:8545/0x976EA74026E726554dB657fA54763abd0C3a0aa9,0x976EA74026E726554dB657fA54763abd0C3a0aa9
",
],
[
"Test group,https://example.com:8545/0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb,0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb
",
],
]
`)
expect(
logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))
).toMatchInlineSnapshot(`[]`)
})
})
132 changes: 132 additions & 0 deletions packages/cli/src/commands/validatorgroup/rpc-urls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { newKitFromWeb3 } from '@celo/contractkit'
import { AccountsWrapper } from '@celo/contractkit/lib/wrappers/Accounts'
import { testWithAnvilL1, withImpersonatedAccount } from '@celo/dev-utils/lib/anvil-test'
import { ClaimTypes, IdentityMetadataWrapper } from '@celo/metadata-claims'
import { ux } from '@oclif/core'
import { setupGroupAndAffiliateValidator } from '../../test-utils/chain-setup'
import { stripAnsiCodesAndTxHashes, testLocallyWithWeb3Node } from '../../test-utils/cliUtils'
import RpcUrls from './rpc-urls'

testWithAnvilL1('validatorgroup:rpc-urls cmd', async (web3) => {
jest.spyOn(IdentityMetadataWrapper, 'fetchFromURL').mockImplementation(async (_, url) => {
const validatorAddress = url.split('/').pop()

return new IdentityMetadataWrapper({
claims: [
{
type: ClaimTypes.RPC_URL,
timestamp: Date.now(),
rpcUrl: `https://example.com:8545/${validatorAddress}`,
},
],
} as any) // that data is enough
})

const setMetadataUrlForValidator = async (
accountsWrapper: AccountsWrapper,
validator: string
) => {
await withImpersonatedAccount(
web3,
validator,
async () => {
await accountsWrapper
.setMetadataURL(`https://example.com/metadata/${validator}`)
.sendAndWaitForReceipt({
from: validator,
})
},
1_000_000_000_000_000n
)
}

const EXISTING_VALIDATORS = [
'0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
'0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
'0x976EA74026E726554dB657fA54763abd0C3a0aa9',
]

beforeEach(async () => {
const kit = newKitFromWeb3(web3)
const accountsWrapper = await kit.contracts.getAccounts()

const [nonElectedGroupAddress, validatorAddress] = await web3.eth.getAccounts()

await setupGroupAndAffiliateValidator(kit, nonElectedGroupAddress, validatorAddress)
await accountsWrapper
.setName('Test group')
.sendAndWaitForReceipt({ from: nonElectedGroupAddress })

for (const validator of [...EXISTING_VALIDATORS, validatorAddress]) {
await setMetadataUrlForValidator(accountsWrapper, validator)
}
})

it('shows the RPC URLs of the elected validator groups', async () => {
const logMock = jest.spyOn(console, 'log')
const writeMock = jest.spyOn(ux.write, 'stdout')

await testLocallyWithWeb3Node(RpcUrls, ['--csv'], web3)

expect(writeMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes)))
.toMatchInlineSnapshot(`
[
[
"Validator Group Name,RPC URL,Validator Address
",
],
[
"cLabs,https://example.com:8545/0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
",
],
[
"cLabs,https://example.com:8545/0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
",
],
[
"cLabs,https://example.com:8545/0x976EA74026E726554dB657fA54763abd0C3a0aa9,0x976EA74026E726554dB657fA54763abd0C3a0aa9
",
],
]
`)
expect(
logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))
).toMatchInlineSnapshot(`[]`)
})

it('shows all the RPC URLs', async () => {
const logMock = jest.spyOn(console, 'log')
const writeMock = jest.spyOn(ux.write, 'stdout')

await testLocallyWithWeb3Node(RpcUrls, ['--all', '--csv'], web3)

expect(writeMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes)))
.toMatchInlineSnapshot(`
[
[
"Validator Group Name,RPC URL,Validator Address
",
],
[
"cLabs,https://example.com:8545/0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65,0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
",
],
[
"cLabs,https://example.com:8545/0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc,0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
",
],
[
"cLabs,https://example.com:8545/0x976EA74026E726554dB657fA54763abd0C3a0aa9,0x976EA74026E726554dB657fA54763abd0C3a0aa9
",
],
[
"Test group,https://example.com:8545/0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb,0x6Ecbe1DB9EF729CBe972C83Fb886247691Fb6beb
",
],
]
`)
expect(
logMock.mock.calls.map((args) => args.map(stripAnsiCodesAndTxHashes))
).toMatchInlineSnapshot(`[]`)
})
})
90 changes: 90 additions & 0 deletions packages/cli/src/commands/validatorgroup/rpc-urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { concurrentMap, StrongAddress } from '@celo/base'
import { ClaimTypes, IdentityMetadataWrapper } from '@celo/metadata-claims'
import { Flags, ux } from '@oclif/core'
import { BaseCommand } from '../../base'

export default class RpcUrls extends BaseCommand {
static description =
'Displays a list of community RPC nodes for the currently elected validator groups'

static aliases: string[] = [
'validator:rpc-urls',
'validatorgroup:rpc-urls',
'network:community-rpc-nodes',
'validator:community-rpc-nodes',
'validatorgroup:community-rpc-nodes',
]

static flags = {
...BaseCommand.flags,
...(ux.table.flags() as object),
all: Flags.boolean({
description:
'Display all community RC nodes, not just the ones from currently elected validator groups',
required: false,
default: false,
}),
}

async run() {
const res = await this.parse(RpcUrls)
const kit = await this.getKit()
const isL2 = await this.isCel2()
const epochManagerWrapper = await kit.contracts.getEpochManager()
const validatorsWrapper = await kit.contracts.getValidators()
const accountsWrapper = await kit.contracts.getAccounts()

let validatorAddresses: StrongAddress[] = []

if (res.flags.all) {
validatorAddresses = (await validatorsWrapper.getRegisteredValidators()).map(
(v) => v.address as StrongAddress
)
} else {
if (isL2) {
validatorAddresses = (await epochManagerWrapper.getElectedAccounts()) as StrongAddress[]
} else {
validatorAddresses = (await validatorsWrapper.currentValidatorAccountsSet()).map(
(v) => v.account as StrongAddress
)
}
}

const metadataURLs = await concurrentMap(5, validatorAddresses, async (address) => {
const metadataURL = await accountsWrapper.getMetadataURL(address)

if (!metadataURL) {
return undefined
}

const metadata = await IdentityMetadataWrapper.fetchFromURL(accountsWrapper, metadataURL)

return metadata.findClaim(ClaimTypes.RPC_URL)?.rpcUrl
})

// TODO cache so we don't have to fetch the same validator group name multiple times
const validatorGroupNames = await concurrentMap(5, validatorAddresses, async (address) => {
return (
await validatorsWrapper.getValidatorGroup(
await validatorsWrapper.getValidatorsGroup(address)
)
).name
})

ux.table(
validatorAddresses
.map((address, idx) => ({
validatorGroupName: validatorGroupNames[idx],
rpcUrl: metadataURLs[idx],
validatorAddress: address,
}))
.filter((row) => row.rpcUrl),
{
validatorGroupName: { header: 'Validator Group Name' },
rpcUrl: { header: 'RPC URL' },
validatorAddress: { header: 'Validator Address' },
},
res.flags
)
}
}

0 comments on commit 4953d44

Please sign in to comment.