Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validator:status command to check if a validator signer is online and producing blocks #1906

Merged
merged 45 commits into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fca5fde
create validator:online command to check if a validator is alive
Nov 26, 2019
33674b7
add a couple more checks
Nov 26, 2019
9ac6868
add a check
Nov 26, 2019
ebfa798
rename online to status
Nov 26, 2019
0ecea6b
remove k
Nov 26, 2019
94c8248
remove extra ts-ignore
Nov 26, 2019
af8e695
fix web3.eth.getBlock
Nov 26, 2019
1afa4df
update docs
Nov 26, 2019
b1bd6f8
provide more helpful message about election status
Nov 26, 2019
3a67701
allow specifying validator and lookback
Nov 26, 2019
ffe6a22
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Nov 26, 2019
1a61d52
add note to running-a-validator doc
Nov 26, 2019
45e819e
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Nov 26, 2019
06b06ad
add address import
Nov 26, 2019
806e11b
Merge branch 'master' into victor/vx-online
Nov 27, 2019
a6a5fa9
checkpoint
Dec 3, 2019
e0cc118
Merge branch 'victor/vx-online' of github.com:celo-org/celo-monorepo …
Dec 3, 2019
3027d15
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Dec 3, 2019
2962cd9
add fuinctions to parse Istanbul extra data to utils
Dec 3, 2019
ace681e
remove obviated isvalidator command
Dec 3, 2019
5851e85
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Dec 3, 2019
2bdf52f
fix current and run commands
Dec 3, 2019
a09192c
update docs
Dec 3, 2019
d5fc096
fix relative path
Dec 3, 2019
57c9640
Merge branch 'master' into victor/vx-online
Dec 4, 2019
d862886
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Dec 4, 2019
ddad057
Merge branch 'victor/vx-online' of github.com:celo-org/celo-monorepo …
Dec 4, 2019
93aa9c7
Merge branch 'master' into victor/vx-online
Dec 4, 2019
bb7cdf5
Merge branch 'master' into victor/vx-online
Dec 4, 2019
41ba66b
export IstanbulUtils
Dec 4, 2019
73985d9
Merge branch 'victor/vx-online' of github.com:celo-org/celo-monorepo …
Dec 4, 2019
ec42fa3
Merge branch 'master' into victor/vx-online
Dec 4, 2019
4e962e7
Merge branch 'master' into victor/vx-online
Dec 4, 2019
3958aad
Merge branch 'master' into victor/vx-online
Dec 4, 2019
e3e2c8c
Merge branch 'master' into victor/vx-online
Dec 4, 2019
23d61fe
Merge branch 'master' into victor/vx-online
Dec 4, 2019
825d3e1
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Dec 4, 2019
91a7f5d
Merge branch 'master' into victor/vx-online
Dec 4, 2019
0de04ca
Merge branch 'master' into victor/vx-online
Dec 5, 2019
4247518
Merge branch 'victor/vx-online' of github.com:celo-org/celo-monorepo …
Dec 5, 2019
eec6e8f
address review comments
Dec 5, 2019
d64020b
fix type error
Dec 5, 2019
a9220dc
fix typo
Dec 5, 2019
80eb702
update docs
Dec 5, 2019
1f0c5c5
Merge branch 'master' of github.com:celo-org/celo-monorepo into victo…
Dec 5, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions packages/cli/src/commands/account/isvalidator.ts

This file was deleted.

4 changes: 1 addition & 3 deletions packages/cli/src/commands/election/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import { validatorTable } from '../validator/list'

export default class ElectionCurrent extends BaseCommand {
static description =
'Outputs the set of validators currently participating in BFT to create blocks. The validator set is re-elected at the end of every epoch.'
'Outputs the set of validators currently participating in BFT to create blocks. An election is run to select the validator set at the end of every epoch.'

static flags = {
...BaseCommand.flags,
}

static examples = ['current']

async run() {
cli.action.start('Fetching currently elected Validators')
const election = await this.kit.contracts.getElection()
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/commands/election/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ export default class ElectionRun extends BaseCommand {
...BaseCommand.flags,
}

static examples = ['run']

async run() {
cli.action.start('Running mock election')
const election = await this.kit.contracts.getElection()
const validators = await this.kit.contracts.getValidators()
const signers = await election.getCurrentValidatorSigners()
const signers = await election.electValidatorSigners()
const validatorList = await Promise.all(
signers.map((addr) => validators.getValidatorFromSigner(addr))
)
Expand Down
113 changes: 113 additions & 0 deletions packages/cli/src/commands/validator/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Address } from '@celo/contractkit'
import { eqAddress } from '@celo/utils/lib/address'
import { concurrentMap } from '@celo/utils/lib/async'
import { bitIsSet, parseBlockExtraData } from '@celo/utils/lib/istanbul'
import { flags } from '@oclif/command'
import { cli } from 'cli-ux'
import { BaseCommand } from '../../base'
import { newCheckBuilder } from '../../utils/checks'
import { Flags } from '../../utils/command'

export default class ValidatorStatus extends BaseCommand {
static description =
'Show information about whether the validator signer is elected and validating. This command will check that the validator meets the registration requirements, and its signer is currently elected and actively signing blocks.'
asaj marked this conversation as resolved.
Show resolved Hide resolved

static flags = {
...BaseCommand.flags,
signer: Flags.address({
asaj marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make these flags exclusive? Example in validatorgroup:member

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

description: 'address of the validator to check if elected and validating',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these descriptions swapped?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Fixed.

}),
validator: Flags.address({
description: 'address of the signer to check if elected and validating',
}),
lookback: flags.integer({
description: 'how many blocks to look back for signer activity',
default: 100,
}),
}

static examples = [
'status --validator 0x5409ED021D9299bf6814279A6A1411A7e866A631',
'status --signer 0x738337030fAeb1E805253228881d844b5332fB4c',
'status --signer 0x738337030fAeb1E805253228881d844b5332fB4c --lookback 100',
]

requireSynced = true

async run() {
const res = this.parse(ValidatorStatus)

// Check that the specified validator or signer meets the validator requirements.
const checker = newCheckBuilder(this, res.flags.signer)
if (res.flags.validator) {
const account = res.flags.validator
checker.isValidator(account).meetsValidatorBalanceRequirements(account)
} else if (res.flags.signer) {
checker.signerMeetsValidatorBalanceRequirements().signerAccountIsValidator()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than add additional checks, why don't we just pull the account from the signer and run the same checks on the account?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point in the code it is unclear whether the accounts given are valid and I wanted to avoid an opaque call reversion. I realized that as coded it still has that issue, so I've added an isAccount check.

} else {
this.error('Either validator or signer must be specified')
}
await checker.runChecks()

// Assign and verify the signer.
let signer: Address
if (res.flags.signer) {
signer = res.flags.signer
if (res.flags.validator) {
const accounts = await this.kit.contracts.getAccounts()
if ((await accounts.signerToAccount(signer)) !== res.flags.validator) {
this.error(
`Signer ${signer} has never been authorized for account ${res.flags.validator}`
)
}
}
} else {
const accounts = await this.kit.contracts.getAccounts()
signer = await accounts.getValidatorSigner(res.flags.validator!)
console.info(`Identified ${signer} as the authorized validator signer`)
}

// Determine if the signer is elected, and get their index in the validator set.
const election = await this.kit.contracts.getElection()
const signers = await election.getCurrentValidatorSigners()
const signerIndex = signers.map((a) => eqAddress(a, signer)).indexOf(true)
if (signerIndex < 0) {
// Determine whether the signer will be elected at the next epoch to provide a helpful error.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Can we put this functionality in the election module instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add a command like election:validator-is-elected, but I personally don't see a ton of value in that because election:current and election:run exist and combined with grep you can tell whether a specific validator is in the set.

const frontrunners = await election.electValidatorSigners()
if (frontrunners.some((a) => eqAddress(a, signer))) {
this.error(
`Signer ${signer} is not elected for this epoch, but is currently winning in the upcoming election. Wait for the next epoch.`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but would be elected to the validator set for the next epoch if an election were to be held now. Please wait until the next epoch.

)
} else {
this.error(
`Signer ${signer} is not elected for this epoch, and is not currently winning the upcoming election.`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and would not be elected to the validator set for the next epoch if an election were to be held now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

)
}
}
console.info('Signer has been elected for this epoch')

if (((res.flags && res.flags.lookback) || 0) <= 0) {
return
}

// Retrieve blocks to examine for the singers signature.
nategraf marked this conversation as resolved.
Show resolved Hide resolved
cli.action.start(`Retreiving the last ${res.flags.lookback} blocks`)
const latest = await this.web3.eth.getBlock('latest')
const blocks = await concurrentMap(10, [...Array(res.flags.lookback).keys()].slice(1), (i) =>
this.web3.eth.getBlock(latest.number - i)
)
blocks.splice(0, 0, latest)
cli.action.stop()

const signedCount = blocks.filter((b) =>
bitIsSet(parseBlockExtraData(b.extraData).parentAggregatedSeal.bitmap, signerIndex)
).length
if (signedCount === 0) {
this.error(`Signer has not signed any of the last ${res.flags.lookback} blocks`)
}
console.info(`Signer has signed ${signedCount} of the last ${res.flags.lookback} blocks`)

const proposedCount = blocks.filter((b) => b.miner === signer).length
console.info(`Signer has proposed ${proposedCount} of the last ${res.flags.lookback} blocks`)
}
}
16 changes: 14 additions & 2 deletions packages/cli/src/utils/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class CheckBuilder {
}
}

withAccounts<A>(f: (lockedGold: AccountsWrapper) => A): () => Promise<Resolve<A>> {
withAccounts<A>(f: (accounts: AccountsWrapper) => A): () => Promise<Resolve<A>> {
return async () => {
const accounts = await this.kit.contracts.getAccounts()
return f(accounts) as Resolve<A>
Expand Down Expand Up @@ -122,12 +122,24 @@ class CheckBuilder {

signerMeetsValidatorGroupBalanceRequirements = () =>
this.addCheck(
`Signer's account has enough locked gold for registration`,
`Signer's account has enough locked gold for group registration`,
this.withValidators((v, _signer, account) =>
v.meetsValidatorGroupBalanceRequirements(account)
)
)

meetsValidatorBalanceRequirements = (account: Address) =>
this.addCheck(
`${account} has enough locked gold for registration`,
this.withValidators((v) => v.meetsValidatorBalanceRequirements(account))
)

meetsValidatorGroupBalanceRequirements = (account: Address) =>
this.addCheck(
`${account} has enough locked gold for group registration`,
this.withValidators((v) => v.meetsValidatorGroupBalanceRequirements(account))
)

isNotAccount = (address: Address) =>
this.addCheck(
`${address} is not an Account`,
Expand Down
14 changes: 0 additions & 14 deletions packages/docs/command-line-interface/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,20 +191,6 @@ EXAMPLE

_See code: [packages/cli/src/commands/account/get-metadata.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/get-metadata.ts)_

### Isvalidator

Check whether a given address is elected to be validating in the current epoch

```
USAGE
$ celocli account:isvalidator ADDRESS

EXAMPLE
isvalidator 0x5409ed021d9299bf6814279a6a1411a7e866a631
```

_See code: [packages/cli/src/commands/account/isvalidator.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/account/isvalidator.ts)_

### New

Creates a new account locally and print out the key information. Save this information for local transaction signing or import into a Celo node.
Expand Down
8 changes: 1 addition & 7 deletions packages/docs/command-line-interface/election.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@ _See code: [packages/cli/src/commands/election/activate.ts](https://github.com/c

### Current

Outputs the set of validators currently participating in BFT to create blocks. The validator set is re-elected at the end of every epoch.
Outputs the set of validators currently participating in BFT to create blocks. An election is run to select the validator set at the end of every epoch.

```
USAGE
$ celocli election:current

EXAMPLE
current
```

_See code: [packages/cli/src/commands/election/current.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/current.ts)_
Expand Down Expand Up @@ -78,9 +75,6 @@ Runs a "mock" election and prints out the validators that would be elected if th
```
USAGE
$ celocli election:run

EXAMPLE
run
```

_See code: [packages/cli/src/commands/election/run.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/election/run.ts)_
Expand Down
24 changes: 24 additions & 0 deletions packages/docs/command-line-interface/validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,30 @@ EXAMPLE

_See code: [packages/cli/src/commands/validator/show.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/show.ts)_

### Status

Show information about whether the validator signer is elected and validating. This command will check that the validator meets the registration requirements, and its signer is currently elected and actively signing blocks.

```
USAGE
$ celocli validator:status

OPTIONS
--lookback=lookback [default: 100] how many blocks to look back for signer
activity

--signer=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d address of the validator to check if elected and validating

--validator=0xc1912fEE45d61C87Cc5EA59DaE31190FFFFf232d address of the signer to check if elected and validating

EXAMPLES
status --validator 0x5409ED021D9299bf6814279A6A1411A7e866A631
status --signer 0x738337030fAeb1E805253228881d844b5332fB4c
status --signer 0x738337030fAeb1E805253228881d844b5332fB4c --lookback 100
```

_See code: [packages/cli/src/commands/validator/status.ts](https://github.com/celo-org/celo-monorepo/tree/master/packages/cli/src/commands/validator/status.ts)_

### Update-bls-public-key

Update the BLS public key for a Validator to be used in consensus. Regular (ECDSA and BLS) key rotation is recommended for Validator operational security.
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/getting-started/running-a-validator.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,11 @@ celocli election:list

If you find your Validator still not getting elected you may need to faucet yourself more funds and lock more gold in order to be able to cast more votes for your Validator Group!

At any moment you can check the currently elected validators by running the following command:
You can check the status of your validator, including whether it is elected and signing blocks, by running:

```bash
# On your local machine
celocli election:current
celocli validator:status --validator $CELO_VALIDATOR_ADDRESS
```

### Running the Attestation Service
Expand Down
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './dappkit'
export { ECIES } from './ecies'
export { PhoneNumberUtils } from './phoneNumbers'
export { SignatureUtils } from './signatureUtils'
export { IstanbulUtils } from './istanbul'
64 changes: 64 additions & 0 deletions packages/utils/src/istanbul.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import BigNumber from 'bignumber.js'
import { bitIsSet, Bitmap, IstanbulExtra, parseBlockExtraData } from './istanbul'

describe('Istanbul utilities', () => {
describe('parseBlockExtraData', () => {
const testExtraData =
'0xd983010817846765746888676f312e31312e358664617277696e000000000000f90127d594fd0893e334' +
'c6401188ae77072546979b94d91813f862b8604fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36' +
'a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053' +
'cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff0001b84188022a71c12a801a4318e2' +
'7eeb5c82aa923160632c63b0eae4457ed120356ddb549fb7c4e4865728478aa61c19b9abe10ec7db34c866' +
'2b003b139188e99edcd400f30db040c083f6b6e29a6a2cab4498f50d37d458a2458b5438c9faeae8598cd4' +
'7f4ed6e17ca10e1f87c6faa14d5e3e393f0e0080f30db06107252c187052f8212ef5cfc9052fe59c7af040' +
'e77a09b762fd51060220511e93d1c681be8883043f8a93ea637492818080'

const expected: IstanbulExtra = {
addedValidators: ['0xFd0893E334C6401188Ae77072546979B94d91813'],
addedValidatorsPublicKeys: [
'0x4fa3f67fc913878b068d1fa1cdddc54913d3bf988dbe5a36a20fa888f20d4894c408a6773f3d7bde11154f2a3076b700d345a42fd25a0e5e83f4db5586ac7979ac2053cd95d8f2efd3e959571ceccaa743e02cf4be3f5d7aaddb0b06fc9aff00',
],
removedValidators: new BigNumber(1),
seal:
'0x88022a71c12a801a4318e27eeb5c82aa923160632c63b0eae4457ed120356ddb549fb7c4e4865728478aa61c19b9abe10ec7db34c8662b003b139188e99edcd400',
aggregatedSeal: {
bitmap: new BigNumber(13),
signature:
'0x40c083f6b6e29a6a2cab4498f50d37d458a2458b5438c9faeae8598cd47f4ed6e17ca10e1f87c6faa14d5e3e393f0e00',
round: new BigNumber(0),
},
parentAggregatedSeal: {
bitmap: new BigNumber(13),
signature:
'0x6107252c187052f8212ef5cfc9052fe59c7af040e77a09b762fd51060220511e93d1c681be8883043f8a93ea63749281',
round: new BigNumber(0),
},
}

it('should decode the Istanbul extra data correctly', () => {
expect(parseBlockExtraData(testExtraData)).toEqual(expected)
})
})

describe('bitIsSet', () => {
const testBitmap: Bitmap = new BigNumber('0x40d1', 16)
const testBitmapAsBinary = ('0100' + '0000' + '1101' + '0001')
.split('')
.map((b) => b === '1')
.reverse()

it('should correctly identify set bits within expected index', () => {
for (let i = 0; i < testBitmapAsBinary.length; i++) {
expect(bitIsSet(testBitmap, i)).toBe(testBitmapAsBinary[i])
}
})

it('should return false when the index is too large', () => {
expect(bitIsSet(testBitmap, 1000)).toBe(false)
})

it('should throw an error when the index is negative', () => {
expect(() => bitIsSet(testBitmap, -1)).toThrow()
})
})
})
Loading