-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
354 additions
and
0 deletions.
There are no files selected for viewing
132 changes: 132 additions & 0 deletions
132
packages/cli/src/commands/validatorgroup/rpc-urls-l2.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
132
packages/cli/src/commands/validatorgroup/rpc-urls.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(`[]`) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) | ||
} | ||
} |