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

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
shuse2 committed Nov 3, 2021
1 parent d0350f6 commit 51a388a
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 3 deletions.
68 changes: 67 additions & 1 deletion framework/src/modules/dpos_v2/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ import {
SnapshotStoreData,
} from './types';
import { Rounds } from './rounds';
import { isCurrentlyPunished } from './utils';
import {
isCurrentlyPunished,
selectStandbyDelegates,
shuffleDelegateList,
validtorsEqual,
} from './utils';

export class DPoSModule extends BaseModule {
public id = MODULE_ID_DPOS;
Expand Down Expand Up @@ -117,6 +122,7 @@ export class DPoSModule extends BaseModule {

public async afterBlockExecute(context: BlockAfterExecuteContext): Promise<void> {
await this._createVoteWeightSnapshot(context);
await this._updateValidators(context);
}

private async _createVoteWeightSnapshot(context: BlockAfterExecuteContext): Promise<void> {
Expand Down Expand Up @@ -214,4 +220,64 @@ export class DPoSModule extends BaseModule {
await snapshotStore.del(key);
}
}

private async _updateValidators(context: BlockAfterExecuteContext): Promise<void> {
const round = new Rounds({ blocksPerRound: this._moduleConfig.roundLength });
const { height } = context.header;
const nextRound = round.calcRound(height) + 1;
context.logger.debug(nextRound, 'Updating delegate list for');

const snapshotStore = context.getStore(this.id, STORE_PREFIX_SNAPSHOT);
const snapshot = await snapshotStore.getWithSchema<SnapshotStoreData>(
intToBuffer(nextRound, 4),
snapshotStoreSchema,
);

const apiContext = context.getAPIContext();

// get the last stored BFT parameters, and update them if needed
const currentBFTParams = await this._bftAPI.getBFTParameters(apiContext, height);
// snapshot.activeDelegates order should not be changed here to use it below
const bftWeight = [...snapshot.activeDelegates]
.sort((a, b) => a.compare(b))
.map(address => ({ address, bftWeight: BigInt(1) }));
if (
!validtorsEqual(currentBFTParams.validators, bftWeight) ||
currentBFTParams.precommitThreshold !== BigInt(this._moduleConfig.bftThreshold) ||
currentBFTParams.certificateThreshold !== BigInt(this._moduleConfig.bftThreshold)
) {
await this._bftAPI.setBFTParameters(
apiContext,
this._moduleConfig.bftThreshold,
this._moduleConfig.bftThreshold,
bftWeight,
);
}

// Update the validators
const validators = [...snapshot.activeDelegates];
const randomSeed1 = await this._randomAPI.getRandomBytes(
apiContext,
height + 1 - Math.floor((this._moduleConfig.roundLength * 3) / 2),
this._moduleConfig.roundLength,
);
if (this._moduleConfig.numberStandbyDelegates === 2) {
const randomSeed2 = await this._randomAPI.getRandomBytes(
apiContext,
height + 1 - 2 * this._moduleConfig.roundLength,
this._moduleConfig.roundLength,
);
const standbyDelegates = selectStandbyDelegates(
snapshot.delegateWeightSnapshot,
randomSeed1,
randomSeed2,
);
validators.push(...standbyDelegates);
} else if (this._moduleConfig.numberStandbyDelegates === 1) {
const standbyDelegates = selectStandbyDelegates(snapshot.delegateWeightSnapshot, randomSeed1);
validators.push(...standbyDelegates);
}
const shuffledValidators = shuffleDelegateList(randomSeed1, validators);
await this._validatorsAPI.setGeneratorList(apiContext, shuffledValidators);
}
}
9 changes: 9 additions & 0 deletions framework/src/modules/dpos_v2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ export interface BFTAPI {
certificateThreshold: number,
validators: Validator[],
): Promise<void>;
getBFTParameters(
context: ImmutableAPIContext,
height: number,
): Promise<{
prevoteThreshold: bigint;
precommitThreshold: bigint;
certificateThreshold: bigint;
validators: Validator[];
}>;
}

export interface RandomAPI {
Expand Down
98 changes: 96 additions & 2 deletions framework/src/modules/dpos_v2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { verifyData } from '@liskhq/lisk-cryptography';
import { hash, verifyData } from '@liskhq/lisk-cryptography';
import { NotFoundError } from '@liskhq/lisk-chain';
import { UnlockingObject, VoterData } from './types';
import { SnapshotStoreData, UnlockingObject, VoterData } from './types';
import {
PUNISHMENT_PERIOD,
SELF_VOTE_PUNISH_TIME,
Expand All @@ -24,6 +24,7 @@ import {
} from './constants';
import { SubStore } from '../../node/state_machine/types';
import { voterStoreSchema } from './schemas';
import { Validator } from '../../node/consensus/types';

export const sortUnlocking = (unlocks: UnlockingObject[]): void => {
unlocks.sort((a, b) => {
Expand Down Expand Up @@ -147,3 +148,96 @@ export const getVoterOrDefault = async (voterStore: SubStore, address: Buffer) =
return voterData;
}
};

export interface DelegateWeight {
readonly delegateAddress: Buffer;
readonly delegateWeight: bigint;
}

export const pickStandByDelegate = (
delegateWeights: ReadonlyArray<DelegateWeight>,
randomSeed: Buffer,
): number => {
const seedNumber = randomSeed.readBigUInt64BE();
const totalVoteWeight = delegateWeights.reduce(
(prev, current) => prev + BigInt(current.delegateWeight),
BigInt(0),
);

let threshold = seedNumber % totalVoteWeight;
for (let i = 0; i < delegateWeights.length; i += 1) {
const voteWeight = BigInt(delegateWeights[i].delegateWeight);
if (voteWeight > threshold) {
return i;
}
threshold -= voteWeight;
}

return -1;
};

export const shuffleDelegateList = (
previousRoundSeed1: Buffer,
addresses: ReadonlyArray<Buffer>,
): Buffer[] => {
const delegateList = [...addresses].map(delegate => ({
address: delegate,
})) as { address: Buffer; roundHash: Buffer }[];

for (const delegate of delegateList) {
const seedSource = Buffer.concat([previousRoundSeed1, delegate.address]);
delegate.roundHash = hash(seedSource);
}

delegateList.sort((delegate1, delegate2) => {
const diff = delegate1.roundHash.compare(delegate2.roundHash);
if (diff !== 0) {
return diff;
}

return delegate1.address.compare(delegate2.address);
});

return delegateList.map(delegate => delegate.address);
};

export const selectStandbyDelegates = (
delegateWeights: DelegateWeight[],
randomSeed1: Buffer,
randomSeed2?: Buffer,
): Buffer[] => {
const numberOfCandidates = 1 + (randomSeed2 !== undefined ? 1 : 0);
// if delegate weights is smaller than number selecting, select all
if (delegateWeights.length <= numberOfCandidates) {
return delegateWeights.map(c => c.delegateAddress);
}
const result: Buffer[] = [];
const index = pickStandByDelegate(delegateWeights, randomSeed1);
const [selected] = delegateWeights.splice(index, 1);
result.push(selected.delegateAddress);
// if seed2 is missing, return only 1
if (!randomSeed2) {
return result;
}
const secondIndex = pickStandByDelegate(delegateWeights, randomSeed2);
const [secondStandby] = delegateWeights.splice(secondIndex, 1);
result.push(secondStandby.delegateAddress);

return result;
};

export const validtorsEqual = (v1: Validator[], v2: Validator[]): boolean => {
if (v1.length !== v2.length) {
return false;
}
for (let i = 0; i < v1.length; i += 1) {
if (!v1[i].address.equals(v2[i].address)) {
return false;
}
if (v1[i].bftWeight !== v2[i].bftWeight) {
return false;
}
}

return true;
};

0 comments on commit 51a388a

Please sign in to comment.