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

Commit

Permalink
Merge pull request #6979 from LiskHQ/6857_unlock_cmd
Browse files Browse the repository at this point in the history
Add `isCertificateGenerated` check to unlock command - Closes #6857
  • Loading branch information
shuse2 authored Jan 19, 2022
2 parents 1bc6d55 + b839eff commit 2ac731f
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 8 deletions.
26 changes: 23 additions & 3 deletions framework/src/modules/dpos_v2/commands/unlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,35 @@ import { CommandExecuteContext } from '../../../node/state_machine/types';
import { BaseCommand } from '../../base_command';
import {
COMMAND_ID_UNLOCK,
defaultConfig,
EMPTY_KEY,
STORE_PREFIX_DELEGATE,
STORE_PREFIX_GENESIS_DATA,
STORE_PREFIX_VOTER,
MODULE_ID_DPOS,
} from '../constants';
import { delegateStoreSchema, voterStoreSchema } from '../schemas';
import { delegateStoreSchema, genesisDataStoreSchema, voterStoreSchema } from '../schemas';
import {
BFTAPI,
DelegateAccount,
GenesisData,
TokenAPI,
TokenIDDPoS,
UnlockCommandDependencies,
VoterData,
} from '../types';
import { hasWaited, isPunished } from '../utils';
import { hasWaited, isPunished, isCertificateGenerated } from '../utils';

export class UnlockCommand extends BaseCommand {
public id = COMMAND_ID_UNLOCK;
public name = 'unlockToken';

private _bftAPI!: BFTAPI;
private _tokenAPI!: TokenAPI;
private _tokenIDDPoS!: TokenIDDPoS;

public addDependencies(args: UnlockCommandDependencies) {
this._bftAPI = args.bftAPI;
this._tokenAPI = args.tokenAPI;
this._tokenIDDPoS = args.tokenIDDPoS;
}
Expand All @@ -53,6 +60,13 @@ export class UnlockCommand extends BaseCommand {
const voterSubstore = getStore(this.moduleID, STORE_PREFIX_VOTER);
const voterData = await voterSubstore.getWithSchema<VoterData>(senderAddress, voterStoreSchema);
const ineligibleUnlocks = [];
const genesisDataStore = context.getStore(MODULE_ID_DPOS, STORE_PREFIX_GENESIS_DATA);
const genesisData = await genesisDataStore.getWithSchema<GenesisData>(
EMPTY_KEY,
genesisDataStoreSchema,
);
const { height: genesisHeight } = genesisData;
const { maxHeightCertified } = await this._bftAPI.getBFTHeights(getAPIContext());

for (const unlockObject of voterData.pendingUnlocks) {
const { pomHeights } = await delegateSubstore.getWithSchema<DelegateAccount>(
Expand All @@ -62,7 +76,13 @@ export class UnlockCommand extends BaseCommand {

if (
hasWaited(unlockObject, senderAddress, height) &&
!isPunished(unlockObject, pomHeights, senderAddress, height)
!isPunished(unlockObject, pomHeights, senderAddress, height) &&
isCertificateGenerated({
unlockObject,
genesisHeight,
maxHeightCertified,
roundLength: defaultConfig.roundLength,
})
) {
await this._tokenAPI.unlock(
getAPIContext(),
Expand Down
15 changes: 15 additions & 0 deletions framework/src/modules/dpos_v2/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
STORE_PREFIX_NAME,
STORE_PREFIX_VOTER,
defaultConfig,
COMMAND_ID_UNLOCK,
} from './constants';
import { DPoSEndpoint } from './endpoint';
import {
Expand Down Expand Up @@ -147,6 +148,20 @@ export class DPoSModule extends BaseModule {
throw new Error("'voteCommand' is missing from DPoS module");
}
voteCommand.init({ tokenIDDPoS: this._moduleConfig.tokenIDDPoS });

const unlockCommand = this.commands.find(command => command.id === COMMAND_ID_UNLOCK) as
| UnlockCommand
| undefined;
if (!unlockCommand) {
throw new Error("'unlockCommand' is missing from DPoS module");
}

// Dependency added here since we need access to moduleConfig for tokenIDDPoS
unlockCommand.addDependencies({
bftAPI: this._bftAPI,
tokenAPI: this._tokenAPI,
tokenIDDPoS: this._moduleConfig.tokenIDDPoS,
});
}

public async initGenesisState(context: GenesisBlockExecuteContext): Promise<void> {
Expand Down
5 changes: 4 additions & 1 deletion framework/src/modules/dpos_v2/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/

import { BlockHeader } from '@liskhq/lisk-chain';
import { BFTHeights } from '../bft/types';
import { Validator } from '../../node/consensus/types';
import { APIContext, ImmutableAPIContext } from '../../node/state_machine/types';

Expand Down Expand Up @@ -54,6 +55,7 @@ export interface BFTAPI {
validators: Validator[];
}>;
areHeadersContradicting(bftHeader1: BlockHeader, bftHeader2: BlockHeader): boolean;
getBFTHeights(context: ImmutableAPIContext): Promise<BFTHeights>;
}

export interface RandomAPI {
Expand Down Expand Up @@ -219,6 +221,7 @@ export interface ValidatorKeys {
export interface UnlockCommandDependencies {
tokenIDDPoS: TokenIDDPoS;
tokenAPI: TokenAPI;
bftAPI: BFTAPI;
}

export interface SnapshotStoreData {
Expand All @@ -234,7 +237,7 @@ export interface PreviousTimestampData {
}

export interface GenesisData {
heigth: number;
height: number;
initRounds: number;
initDelegates: Buffer[];
}
Expand Down
18 changes: 18 additions & 0 deletions framework/src/modules/dpos_v2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,24 @@ export const isPunished = (
);
};

export const lastHeightOfRound = (height: number, genesisHeight: number, roundLength: number) => {
const roundNumber = Math.ceil((height - genesisHeight) / roundLength);

return roundNumber * roundLength + genesisHeight;
};

export const isCertificateGenerated = (options: {
unlockObject: UnlockingObject;
genesisHeight: number;
maxHeightCertified: number;
roundLength: number;
}): boolean =>
lastHeightOfRound(
options.unlockObject.unvoteHeight + 2 * options.roundLength,
options.genesisHeight,
options.roundLength,
) <= options.maxHeightCertified;

export const getMinPunishedHeight = (
senderAddress: Buffer,
delegateAddress: Buffer,
Expand Down
1 change: 1 addition & 0 deletions framework/test/unit/modules/dpos_v2/commands/pom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ describe('ReportDelegateMisbehaviorCommand', () => {
setBFTParameters: jest.fn(),
getBFTParameters: jest.fn(),
areHeadersContradicting: jest.fn(),
getBFTHeights: jest.fn(),
};
mockValidatorsAPI = {
setGeneratorList: jest.fn(),
Expand Down
Loading

0 comments on commit 2ac731f

Please sign in to comment.