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

Add random module hooks with tests - Closes #6776, #6777, #6778 #6863

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 67 additions & 9 deletions framework/src/modules/random/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,28 @@ import { BaseModule, ModuleInitArgs } from '../base_module';
import { RandomAPI } from './api';
import {
DEFAULT_MAX_LENGTH_REVEALS,
EMPTY_KEY,
MODULE_ID_RANDOM,
SEED_REVEAL_HASH_SIZE,
STORE_PREFIX_RANDOM,
STORE_PREFIX_USED_HASH_ONION,
} from './constants';
import { RandomEndpoint } from './endpoint';
import { blockHeaderAssetRandomModule, usedHashOnionsStoreSchema } from './schemas';
import {
blockHeaderAssetRandomModule,
seedRevealSchema,
usedHashOnionsStoreSchema,
} from './schemas';
import {
BlockHeaderAssetRandomModule,
HashOnionConfig,
RegisteredDelegate,
UsedHashOnion,
UsedHashOnionStoreObject,
ValidatorReveals,
} from './types';
import { Logger } from '../../logger';
import { isSeedValidInput } from './utils';

export class RandomModule extends BaseModule {
public id = MODULE_ID_RANDOM;
Expand All @@ -53,8 +63,6 @@ export class RandomModule extends BaseModule {
this._generatorConfig = generatorConfig;
this._maxLengthReveals =
(moduleConfig.maxLengthReveals as number) ?? DEFAULT_MAX_LENGTH_REVEALS;
// eslint-disable-next-line no-console
console.log(this._generatorConfig.toString(), this._maxLengthReveals);
}

public async initBlock(context: BlockGenerateContext): Promise<void> {
Expand Down Expand Up @@ -105,14 +113,64 @@ export class RandomModule extends BaseModule {
);
}

// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-empty-function
public async verifyBlock(_context: BlockVerifyContext): Promise<void> {}
// eslint-disable-next-line @typescript-eslint/require-await
public async verifyBlock(context: BlockVerifyContext): Promise<void> {
const encodedAsset = context.assets.getAsset(this.id);
if (!encodedAsset) {
throw new Error('Random module asset must exist.');
}
const asset = codec.decode<BlockHeaderAssetRandomModule>(
blockHeaderAssetRandomModule,
encodedAsset,
);
if (asset.seedReveal.length !== SEED_REVEAL_HASH_SIZE) {
throw new Error(
`Size of the seed reveal must be ${SEED_REVEAL_HASH_SIZE}, but received ${asset.seedReveal.length}.`,
);
}
}

public async afterGenesisBlockExecute(context: GenesisBlockExecuteContext): Promise<void> {
const randomDataStore = context.getStore(this.id, STORE_PREFIX_RANDOM);
await randomDataStore.setWithSchema(EMPTY_KEY, { validatorReveals: [] }, seedRevealSchema);
}

// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-empty-function
public async afterGenesisBlockExecute(_context: GenesisBlockExecuteContext): Promise<void> {}
public async afterBlockExecute(context: BlockAfterExecuteContext): Promise<void> {
const encodedAsset = context.assets.getAsset(this.id);
if (!encodedAsset) {
throw new Error('Random module asset must exist.');
}
const asset = codec.decode<BlockHeaderAssetRandomModule>(
blockHeaderAssetRandomModule,
encodedAsset,
);
const randomDataStore = context.getStore(this.id, STORE_PREFIX_RANDOM);
const { validatorReveals } = await randomDataStore.getWithSchema<ValidatorReveals>(
EMPTY_KEY,
seedRevealSchema,
);
const valid = isSeedValidInput(
context.header.generatorAddress,
asset.seedReveal,
validatorReveals,
);
const nextReveals =
validatorReveals.length === this._maxLengthReveals
? validatorReveals.slice(1)
: validatorReveals;

// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-empty-function
public async afterBlockExecute(_context: BlockAfterExecuteContext): Promise<void> {}
nextReveals.push({
seedReveal: asset.seedReveal,
generatorAddress: context.header.generatorAddress,
height: context.header.height,
valid,
});
await randomDataStore.setWithSchema(
EMPTY_KEY,
{ validatorReveals: nextReveals },
seedRevealSchema,
);
}

private _filterUsedHashOnions(
usedHashOnions: UsedHashOnion[],
Expand Down
21 changes: 21 additions & 0 deletions framework/src/modules/random/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ import { intToBuffer } from '@liskhq/lisk-cryptography';
import { SEED_REVEAL_HASH_SIZE } from './constants';
import { ValidatorSeedReveal } from './types';

export const isSeedValidInput = (
generatorAddress: Buffer,
seedReveal: Buffer,
validatorsReveal: ValidatorSeedReveal[],
) => {
let lastSeed: ValidatorSeedReveal | undefined;
// by construction, validatorsReveal is order by height asc. Therefore, looping from end will give highest value.
for (let i = validatorsReveal.length - 1; i >= 0; i -= 1) {
const validatorReveal = validatorsReveal[i];
if (validatorReveal.generatorAddress.equals(generatorAddress)) {
lastSeed = validatorReveal;
break;
}
}
// if the last seed is does not exist, seed reveal is invalid for use
if (!lastSeed) {
return false;
}
return lastSeed.seedReveal.equals(cryptography.hash(seedReveal).slice(0, SEED_REVEAL_HASH_SIZE));
};

export const getSeedRevealValidity = (
generatorAddress: Buffer,
seedReveal: Buffer,
Expand Down
24 changes: 22 additions & 2 deletions framework/src/testing/create_contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,39 @@ export const createBlockContext = (params: {
stateStore?: StateStore;
eventQueue?: EventQueue;
logger?: Logger;
header: BlockHeader;
header?: BlockHeader;
assets?: BlockAssets;
transactions?: Transaction[];
}): BlockContext => {
const logger = params.logger ?? loggerMock;
const stateStore = params.stateStore ?? new StateStore(new InMemoryKVStore());
const eventQueue = params.eventQueue ?? new EventQueue();
const header =
params.header ??
new BlockHeader({
height: 0,
generatorAddress: getRandomBytes(20),
previousBlockID: Buffer.alloc(0),
timestamp: Math.floor(Date.now() / 1000),
version: 0,
transactionRoot: hash(Buffer.alloc(0)),
stateRoot: hash(Buffer.alloc(0)),
maxHeightGenerated: 0,
maxHeightPrevoted: 0,
assetsRoot: hash(Buffer.alloc(0)),
aggregateCommit: {
height: 0,
aggregationBits: Buffer.alloc(0),
certificateSignature: Buffer.alloc(0),
},
validatorsHash: hash(Buffer.alloc(0)),
});
const ctx = new BlockContext({
stateStore,
logger,
eventQueue,
transactions: params.transactions ?? [],
header: params.header,
header,
assets: params.assets ?? new BlockAssets(),
networkIdentifier: getRandomBytes(32),
});
Expand Down
Loading