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

Add API for Random Module - Closes #6779 #6860

Merged
Merged
36 changes: 35 additions & 1 deletion framework/src/modules/random/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,40 @@
* Removal or modification of this copyright notice is prohibited.
*/

import { ImmutableAPIContext } from '../../node/state_machine';
import { BaseAPI } from '../base_api';
import { EMPTY_KEY } from '../validators/constants';
import { STORE_PREFIX_RANDOM } from './constants';
import { seedRevealSchema } from './schemas';
import { ValidatorReveals } from './types';
import { isSeedRevealValidUtil, randomBytesUtil } from './utils';

export class RandomAPI extends BaseAPI {}
export class RandomAPI extends BaseAPI {
public async isSeedRevealValid(
apiContext: ImmutableAPIContext,
generatorAddress: Buffer,
seedReveal: Buffer,
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think i missed this on LIP as well, but this should be blockAssets: BlockAssets, so that dpos module or any other module don't need to know how to decode the asset

): Promise<boolean> {
const randomDataStore = apiContext.getStore(this.moduleID, STORE_PREFIX_RANDOM);
const { validatorReveals } = await randomDataStore.getWithSchema<ValidatorReveals>(
EMPTY_KEY,
seedRevealSchema,
);

return isSeedRevealValidUtil(generatorAddress, seedReveal, validatorReveals);
}

public async getRandomBytes(
apiContext: ImmutableAPIContext,
height: number,
numberOfSeeds: number,
): Promise<Buffer> {
const randomDataStore = apiContext.getStore(this.moduleID, STORE_PREFIX_RANDOM);
const { validatorReveals } = await randomDataStore.getWithSchema<ValidatorReveals>(
EMPTY_KEY,
seedRevealSchema,
);

return randomBytesUtil(height, numberOfSeeds, validatorReveals);
}
}
3 changes: 3 additions & 0 deletions framework/src/modules/random/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ export const DEFAULT_MAX_LENGTH_REVEALS = 206;
export const STORE_PREFIX_RANDOM = 0x0000;
export const STORE_PREFIX_REGISTERED_HASH_ONION = Buffer.from('00', 'hex');
export const STORE_PREFIX_USED_HASH_ONION = Buffer.from('01', 'hex');
export const RANDOM_SEED_BYTE_SIZE = 8;
export const EMPTY_KEY = Buffer.alloc(0);
export const SEED_REVEAL_HASH_SIZE = 16;
2 changes: 1 addition & 1 deletion framework/src/modules/random/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ export const seedRevealSchema = {
required: ['generatorAddress', 'seedReveal', 'height', 'valid'],
},
},
required: ['validatorReveals'],
},
required: ['validatorReveals'],
};

export const blockHeaderAssetRandomModule = {
Expand Down
11 changes: 11 additions & 0 deletions framework/src/modules/random/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,14 @@ export interface GeneratorConfig {
readonly defaultPassword?: string;
readonly waitThreshold: number;
}

export interface ValidatorSeedReveal {
generatorAddress: Buffer;
seedReveal: Buffer;
height: number;
valid: boolean;
}

export interface ValidatorReveals {
validatorReveals: ValidatorSeedReveal[];
}
71 changes: 71 additions & 0 deletions framework/src/modules/random/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,74 @@
*
* Removal or modification of this copyright notice is prohibited.
*/

import * as cryptography from '@liskhq/lisk-cryptography';
import { SEED_REVEAL_HASH_SIZE } from './constants';
import { ValidatorSeedReveal } from './types';

export const isSeedRevealValidUtil = (
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe getSeedRevealValidity or checkSeedRevealValidity as name?

generatorAddress: Buffer,
seedReveal: Buffer,
validatorsReveal: ValidatorSeedReveal[],
) => {
const largestSeedHeight = Math.max(
...validatorsReveal
.filter(sr => sr.generatorAddress.equals(generatorAddress))
.map(s => s.height),
);

const lastSeed = validatorsReveal.find(
(seedObject: ValidatorSeedReveal) =>
seedObject.height === largestSeedHeight &&
seedObject.generatorAddress.equals(generatorAddress),
);
Copy link
Collaborator

Choose a reason for hiding this comment

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

instead of looping multiple times, i think simple one for loop with outside let can solve the problem?


if (
!lastSeed ||
lastSeed.seedReveal.equals(cryptography.hash(seedReveal).slice(0, SEED_REVEAL_HASH_SIZE))
) {
return true;
}

return false;
};

export const randomBytesUtil = (
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe getRandomSeed as name?

height: number,
numberOfSeeds: number,
validatorsReveal: ValidatorSeedReveal[],
) => {
const initRandomBuffer = Buffer.allocUnsafe(4);
initRandomBuffer.writeInt32BE(height + numberOfSeeds, 0);
Copy link
Contributor

Choose a reason for hiding this comment

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

what do you think about using intToBuffer from lisk-cryptography here with signed parameter true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

true I forgot about this func in crypto lib, but I think we need unsigned in this case

Copy link
Contributor

Choose a reason for hiding this comment

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

there is write**U**Int32BE for unsigned, since you didn't use it I thought you don't need unsigned.

let randomSeed = cryptography.hash(initRandomBuffer).slice(0, 16);
const currentSeeds = validatorsReveal.filter(
v => height <= v.height && v.height <= height + numberOfSeeds,
);
for (const seedObject of currentSeeds) {
if (seedObject.valid) {
randomSeed = bitwiseXOR([randomSeed, seedObject.seedReveal]);
}
}

return randomSeed;
};

export const bitwiseXOR = (bufferArray: Buffer[]): Buffer => {
if (bufferArray.length === 1) {
return bufferArray[0];
}

const bufferSizes = new Set(bufferArray.map(buffer => buffer.length));
if (bufferSizes.size > 1) {
throw new Error('All input for XOR should be same size');
}
const outputSize = [...bufferSizes][0];
Comment on lines +75 to +79
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about this? (maybe with better naming)

const isBufferLengthSame = bufferArray.every((elem, _, arr) => elem.length === arr[0].length);
if (!isBufferLengthSame) {
    throw new Error('All input for XOR should be same size');
}
const outputSize = bufferArray[0].length;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can improve this later, I copied this function from other place, it better to resolve it in a separate issue. Also, we can move to common utils if we find more usage of this func

const result = Buffer.alloc(outputSize, 0);

for (let i = 0; i < outputSize; i += 1) {
// eslint-disable-next-line no-bitwise
result[i] = bufferArray.map(b => b[i]).reduce((a, b) => a ^ b, 0);
}

return result;
};
Loading