Skip to content
This repository was archived by the owner on Jun 11, 2024. It is now read-only.

Commit

Permalink
♻️ Use getBFTParametersActiveValidators for certificate generation
Browse files Browse the repository at this point in the history
♻️ Update based on LIP0053 updates

πŸ› Filter out bftWeight===0 when saving validatorsPreimage

πŸ› Filter out bftWeight===0 when computing validatorsHash

πŸ› Filter out bftWeight===0 when selecting and validating aggregateCommit
  • Loading branch information
ishantiw committed Aug 31, 2023
1 parent bde252d commit cb3c6e2
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>

// Get validatorsData at new block header height
const bftParametersJSON = await this._sendingChainClient.invoke<BFTParametersJSON>(
'consensus_getBFTParameters',
'consensus_getBFTParametersActiveValidators',
{ height: newBlockHeader.height },
);

Expand All @@ -576,9 +576,11 @@ export class ChainConnectorPlugin extends BasePlugin<ChainConnectorPluginConfig>
);
// Save validatorsData if there is a new validatorsHash
if (validatorsDataIndex === -1) {
// Filter out standby validators with bftWeight > BigInt(0)
const activeValidators = bftParametersObj.validators.filter(v => v.bftWeight > BigInt(0));
validatorsHashPreimage.push({
certificateThreshold: bftParametersObj.certificateThreshold,
validators: bftParametersObj.validators,
validators: activeValidators,
validatorsHash: bftParametersObj.validatorsHash,
});
}
Expand Down
13 changes: 13 additions & 0 deletions framework/src/engine/bft/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ export class BFTMethod {
return getBFTParameters(paramsStore, height);
}

public async getBFTParametersActiveValidators(
stateStore: StateStore,
height: number,
): Promise<BFTParameters> {
const bftParams = await this.getBFTParameters(stateStore, height);

return {
...bftParams,
// Filter standby validators with bftWeight === 0
validators: bftParams.validators.filter(v => v.bftWeight > BigInt(0)),
};
}

public async getBFTHeights(stateStore: StateStore): Promise<BFTHeights> {
const votesStore = stateStore.getStore(MODULE_STORE_PREFIX_BFT, STORE_PREFIX_BFT_VOTES);
const bftVotes = await votesStore.getWithSchema<BFTVotes>(EMPTY_KEY, bftVotesSchema);
Expand Down
3 changes: 2 additions & 1 deletion framework/src/engine/bft/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ export const computeValidatorsHash = (validators: Validator[], certificateThresh
}
sortValidatorsByBLSKey(activeValidators);
const input: ValidatorsHashInput = {
activeValidators,
// Exclude standby validators with bftWeight === 0
activeValidators: activeValidators.filter(v => v.bftWeight > BigInt(0)),
certificateThreshold,
};
const encodedValidatorsHashInput = codec.encode(validatorsHashInputSchema, input);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/

import { BlockHeader, Chain, StateStore } from '@liskhq/lisk-chain';
import { dataStructures } from '@liskhq/lisk-utils';
import { dataStructures, objects } from '@liskhq/lisk-utils';
import { bls } from '@liskhq/lisk-cryptography';
import { Database } from '@liskhq/lisk-db';
import { codec } from '@liskhq/lisk-codec';
Expand Down Expand Up @@ -132,7 +132,10 @@ export class CommitPool {
}

// Validation Step 5
const { validators } = await this._bftMethod.getBFTParameters(methodContext, commit.height);
const { validators } = await this._bftMethod.getBFTParametersActiveValidators(
methodContext,
commit.height,
);
const validator = validators.find(v => v.address.equals(commit.validatorAddress));
if (!validator) {
throw new Error('Commit validator was not active for its height.');
Expand Down Expand Up @@ -238,13 +241,13 @@ export class CommitPool {
aggregationBits: aggregateCommit.aggregationBits,
signature: aggregateCommit.certificateSignature,
};
const { validators, certificateThreshold } = await this._bftMethod.getBFTParameters(
stateStore,
aggregateCommit.height,
);
const { validators: activeValidators, certificateThreshold } =
await this._bftMethod.getBFTParametersActiveValidators(stateStore, aggregateCommit.height);

// Filter out all the standby validators with bftWeight === 0
const activeValidators = validators.filter(v => v.bftWeight > BigInt(0));
return verifyAggregateCertificateSignature(
validators,
activeValidators,
certificateThreshold,
this._chain.chainID,
certificate,
Expand All @@ -266,18 +269,29 @@ export class CommitPool {
const { height } = singleCommits[0];

// assuming this list of validators includes all validators corresponding to each singleCommit.validatorAddress
const { validators } = await this._bftMethod.getBFTParameters(methodContext, height);
const { validators } = await this._bftMethod.getBFTParametersActiveValidators(
methodContext,
height,
);
const addressToBlsKey: dataStructures.BufferMap<Buffer> = new dataStructures.BufferMap();
const validatorKeys: Buffer[] = [];

for (const validator of validators) {
// Filter out all the standby validators with bftWeight === 0
const addressesWithBFTWeightZero = validators
.filter(v => v.bftWeight === BigInt(0))
.map(v => v.address);
for (const validator of validators.filter(v => v.bftWeight > BigInt(0))) {
addressToBlsKey.set(validator.address, validator.blsKey);
validatorKeys.push(validator.blsKey);
}

const pubKeySignaturePairs: PkSigPair[] = [];

for (const commit of singleCommits) {
// Skip any standby validator
if (objects.bufferArrayIncludes(addressesWithBFTWeightZero, commit.validatorAddress)) {
continue;
}
const publicKey = addressToBlsKey.get(commit.validatorAddress);
if (!publicKey) {
throw new Error(
Expand Down Expand Up @@ -330,26 +344,46 @@ export class CommitPool {
...this._nonGossipedCommitsLocal.getByHeight(nextHeight),
...this._gossipedCommits.getByHeight(nextHeight),
];
const nextValidators = singleCommits.map(commit => commit.validatorAddress);
let aggregateBFTWeight = BigInt(0);

// Assume BFT parameters exist for next height
const { validators: bftParamValidators, certificateThreshold } =
await this._bftMethod.getBFTParameters(methodContext, nextHeight);
await this._bftMethod.getBFTParametersActiveValidators(methodContext, nextHeight);

const activeValidatorAddresses = bftParamValidators.map(v => v.address);
// Filter out any single commits from standby delegates
const singleCommitsByActiveValidators = singleCommits.filter(commit =>
objects.bufferArrayIncludes(activeValidatorAddresses, commit.validatorAddress),
);
const nextValidators = singleCommitsByActiveValidators.map(commit => commit.validatorAddress);

const filteredSingleCommits = singleCommits.filter(commit => {
const foundValidator = bftParamValidators.find(v =>
v.address.equals(commit.validatorAddress),
);
if (foundValidator && foundValidator.bftWeight === BigInt(0)) {
return false;
}
return true;
});
const nextValidators = filteredSingleCommits.map(commit => commit.validatorAddress);
for (const matchingAddress of nextValidators) {
const bftParamsValidatorInfo = bftParamValidators.find(bftParamValidator =>
bftParamValidator.address.equals(matchingAddress),
);
if (!bftParamsValidatorInfo) {
throw new Error('Validator address not found in commit pool');
}
// Skip validators with BFT Weight zero when someone is running node with standby validators
if (bftParamsValidatorInfo.bftWeight === BigInt(0)) {
continue;
}

aggregateBFTWeight += bftParamsValidatorInfo.bftWeight;
}

if (aggregateBFTWeight >= certificateThreshold) {
return this.aggregateSingleCommits(methodContext, singleCommits);
return this.aggregateSingleCommits(methodContext, singleCommitsByActiveValidators);
}

nextHeight -= 1;
Expand Down Expand Up @@ -408,7 +442,10 @@ export class CommitPool {

// 2. Select commits to gossip
const nextHeight = this._chain.lastBlock.header.height + 1;
const { validators } = await this._bftMethod.getBFTParameters(methodContext, nextHeight);
const { validators } = await this._bftMethod.getBFTParametersActiveValidators(
methodContext,
nextHeight,
);

const maxSelectedCommitsLength = 2 * validators.length;
// Get a list of commits sorted by ascending order of height
Expand Down
28 changes: 28 additions & 0 deletions framework/src/engine/endpoint/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,34 @@ export class ConsensusEndpoint {
};
}

public async getBFTParametersActiveValidators(ctx: RequestContext): Promise<BFTParametersJSON> {
const stateStore = new StateStore(this._blockchainDB);
const {
certificateThreshold,
precommitThreshold,
prevoteThreshold,
validators,
validatorsHash,
} = await this._bftMethod.getBFTParametersActiveValidators(
stateStore,
ctx.params.height as number,
);

const validatorsJSON = validators.map(v => ({
address: address.getLisk32AddressFromAddress(v.address),
bftWeight: v.bftWeight.toString(),
blsKey: v.blsKey.toString('hex'),
}));

return {
validators: validatorsJSON,
certificateThreshold: certificateThreshold.toString(),
precommitThreshold: precommitThreshold.toString(),
prevoteThreshold: prevoteThreshold.toString(),
validatorsHash: validatorsHash.toString('hex'),
};
}

public async getBFTHeights(_ctx: RequestContext): Promise<BFTHeights> {
const stateStore = new StateStore(this._blockchainDB);
const result = await this._bftMethod.getBFTHeights(stateStore);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,17 +335,25 @@ export abstract class BaseInteroperabilityInternalMethod extends BaseInternalMet
'The number of 1s in the bitmap is not equal to the number of new BFT weights.',
);
}

let bftWeightIndex = 0;
for (let i = 0; i < allBLSKeys.length; i += 1) {
// existing key does not need to be checked
if (!objects.bufferArrayIncludes(blsKeysUpdate, allBLSKeys[i])) {
continue;
}
const blsKey = allBLSKeys[i];
// Get digit of bitmap at index idx (starting from the right).
const digit = (bftWeightsUpdateBitmapBin >> BigInt(i)) & BigInt(1);
if (digit !== BigInt(1)) {
throw new Error('New validators must have a BFT weight update.');
if (objects.bufferArrayIncludes(blsKeysUpdate, blsKey)) {
// New validators must have a BFT weight update.
if (digit !== BigInt(1)) {
throw new Error('New validators must have a BFT weight update.');
}
// New validators must have a positive BFT weight.
// The BFT weight of current validator is specified by bftWeightsUpdate[bftWeightIndex].
if (bftWeightsUpdate[bftWeightIndex] === BigInt(0)) {
throw new Error('New validators must have a positive BFT weight.');
}
}
if (bftWeightsUpdate[i] === BigInt(0)) {
throw new Error('New validators must have a positive BFT weight.');
if (digit === BigInt(1)) {
bftWeightIndex += 1;
}
}

Expand Down
Loading

0 comments on commit cb3c6e2

Please sign in to comment.