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

Align getRandomSeed() implementation to protocol changes from LIP-46 #8570

Merged
merged 4 commits into from
Jun 14, 2023
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
12 changes: 4 additions & 8 deletions framework/src/modules/random/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
* Removal or modification of this copyright notice is prohibited.
*/

import * as cryptography from '@liskhq/lisk-cryptography';
import { utils } from '@liskhq/lisk-cryptography';
import { SEED_LENGTH } from './constants';
import { ValidatorSeedReveal } from './stores/validator_reveals';
Expand All @@ -35,7 +34,7 @@ export const isSeedValidInput = (
if (!lastSeed) {
return false;
}
return lastSeed.seedReveal.equals(cryptography.utils.hash(seedReveal).slice(0, SEED_LENGTH));
return lastSeed.seedReveal.equals(utils.hash(seedReveal).slice(0, SEED_LENGTH));
};

export const getSeedRevealValidity = (
Expand All @@ -56,10 +55,7 @@ export const getSeedRevealValidity = (
}
}

return (
!lastSeed ||
lastSeed.seedReveal.equals(cryptography.utils.hash(seedReveal).slice(0, SEED_LENGTH))
);
return !lastSeed || lastSeed.seedReveal.equals(utils.hash(seedReveal).slice(0, SEED_LENGTH));
};

export const getRandomSeed = (
Expand All @@ -77,14 +73,14 @@ export const getRandomSeed = (
throw new Error('Number of seeds cannot be greater than 1000.');
gkoumout marked this conversation as resolved.
Show resolved Hide resolved
}
const initRandomBuffer = utils.intToBuffer(height + numberOfSeeds, 4);
let randomSeed = cryptography.utils.hash(initRandomBuffer).slice(0, 16);
let randomSeed = utils.hash(initRandomBuffer).slice(0, SEED_LENGTH);

let isInFuture = true;
const currentSeeds = [];
for (const validatorReveal of validatorsReveal) {
if (validatorReveal.height >= height) {
isInFuture = false;
if (validatorReveal.height <= height + numberOfSeeds) {
if (validatorReveal.height < height + numberOfSeeds) {
currentSeeds.push(validatorReveal);
}
}
Expand Down
156 changes: 78 additions & 78 deletions framework/test/unit/modules/random/method.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import { bitwiseXOR } from '../../../../src/modules/random/utils';
import { MethodContext } from '../../../../src/state_machine';
import { createTransientMethodContext } from '../../../../src/testing';
import * as genesisValidators from '../../../fixtures/genesis_validators.json';
import { testCases } from './pos_random_seed_generation/pos_random_seed_generation_other_rounds.json';
import * as randomSeedsMultipleRounds from '../../../fixtures/pos_random_seed_generation/pos_random_seed_generation_other_rounds.json';
import { testCases } from '../../../fixtures/pos_random_seed_generation/pos_random_seed_generation_other_rounds.json';
import { RandomModule } from '../../../../src/modules/random';
import {
ValidatorRevealsStore,
Expand All @@ -40,7 +39,7 @@ describe('RandomModuleMethod', () => {
let randomStore: ValidatorRevealsStore;

const randomModule = new RandomModule();
const emptyBytes = Buffer.alloc(0);
const EMPTY_BYTES = Buffer.alloc(0);

describe('isSeedRevealValid', () => {
const twoRoundsValidators: ValidatorSeedReveal[] = [];
Expand Down Expand Up @@ -69,7 +68,7 @@ describe('RandomModuleMethod', () => {
randomMethod = new RandomMethod(randomModule.stores, randomModule.events, randomModule.name);
context = createTransientMethodContext({});
randomStore = randomModule.stores.get(ValidatorRevealsStore);
await randomStore.set(context, emptyBytes, {
await randomStore.set(context, EMPTY_BYTES, {
validatorReveals: twoRoundsValidators.slice(0, 103),
});
});
Expand Down Expand Up @@ -111,7 +110,7 @@ describe('RandomModuleMethod', () => {

it('should return true if no last seed reveal found', async () => {
// Arrange
await randomStore.set(context, emptyBytes, { validatorReveals: [] });
await randomStore.set(context, EMPTY_BYTES, { validatorReveals: [] });
for (const [address, hashes] of Object.entries(twoRoundsValidatorsHashes)) {
const blockAsset: BlockAsset = {
module: randomModule.name,
Expand All @@ -129,7 +128,7 @@ describe('RandomModuleMethod', () => {
});

it('should return false if there is a last revealed seed by generatorAddress in validatorReveals array but it is not equal to the hash of seedReveal', async () => {
await randomStore.set(context, emptyBytes, { validatorReveals: twoRoundsValidators });
await randomStore.set(context, EMPTY_BYTES, { validatorReveals: twoRoundsValidators });
for (const [address, hashes] of Object.entries(twoRoundsValidatorsHashes)) {
// Arrange
const blockAsset: BlockAsset = {
Expand All @@ -152,7 +151,7 @@ describe('RandomModuleMethod', () => {
const { generatorAddress } = twoRoundsValidators[5];
const twoRoundsValidatorsClone1 = objects.cloneDeep(twoRoundsValidators);
twoRoundsValidatorsClone1[5].generatorAddress = Buffer.alloc(0);
await randomStore.set(context, emptyBytes, {
await randomStore.set(context, EMPTY_BYTES, {
validatorReveals: twoRoundsValidatorsClone1.slice(0, 103),
});
const hashes = twoRoundsValidatorsHashes[generatorAddress.toString('hex')];
Expand All @@ -175,7 +174,7 @@ describe('RandomModuleMethod', () => {
const { generatorAddress } = twoRoundsValidators[5];
const twoRoundsValidatorsClone2 = twoRoundsValidators;
twoRoundsValidatorsClone2[5].seedReveal = cryptography.utils.getRandomBytes(17);
await randomStore.set(context, emptyBytes, {
await randomStore.set(context, EMPTY_BYTES, {
validatorReveals: twoRoundsValidatorsClone2.slice(0, 103),
});
const hashes = twoRoundsValidatorsHashes[generatorAddress.toString('hex')];
Expand All @@ -198,7 +197,7 @@ describe('RandomModuleMethod', () => {
const generatorAddress = cryptography.utils.getRandomBytes(21);
const twoRoundsValidatorsClone3 = objects.cloneDeep(twoRoundsValidators);
twoRoundsValidatorsClone3[5].generatorAddress = generatorAddress;
await randomStore.set(context, emptyBytes, {
await randomStore.set(context, EMPTY_BYTES, {
validatorReveals: twoRoundsValidatorsClone3.slice(0, 103),
});
const hashes =
Expand Down Expand Up @@ -262,7 +261,7 @@ describe('RandomModuleMethod', () => {
randomMethod = new RandomMethod(randomModule.stores, randomModule.events, randomModule.name);
context = createTransientMethodContext({});
randomStore = randomModule.stores.get(ValidatorRevealsStore);
await randomStore.set(context, emptyBytes, { validatorReveals: validatorsData });
await randomStore.set(context, EMPTY_BYTES, { validatorReveals: validatorsData });
});

it('should throw error when height is negative', async () => {
Expand Down Expand Up @@ -319,9 +318,9 @@ describe('RandomModuleMethod', () => {
);
});

it('should return XOR random bytes as 16 bytes value for height=11, numberOfSeeds=2', async () => {
it('should return XOR random bytes as 16 bytes value for height=11, numberOfSeeds=3', async () => {
const height = 11;
const numberOfSeeds = 2;
const numberOfSeeds = 3;
// Create a buffer from height + numberOfSeeds
const randomSeed = strippedHashOfIntegerBuffer(height + numberOfSeeds);

Expand All @@ -341,9 +340,9 @@ describe('RandomModuleMethod', () => {
);
});

it('should return XOR random bytes for height=11, numberOfSeeds=3', async () => {
it('should return XOR random bytes for height=11, numberOfSeeds=4', async () => {
shuse2 marked this conversation as resolved.
Show resolved Hide resolved
const height = 11;
const numberOfSeeds = 3;
const numberOfSeeds = 4;
// Create a buffer from height + numberOfSeeds
const randomSeed = strippedHashOfIntegerBuffer(height + numberOfSeeds);

Expand All @@ -363,9 +362,9 @@ describe('RandomModuleMethod', () => {
);
});

it('should return XOR random bytes for height=11, numberOfSeeds=4 excluding invalid seed reveal', async () => {
it('should return XOR random bytes for height=11, numberOfSeeds=5 excluding invalid seed reveal', async () => {
const height = 11;
const numberOfSeeds = 4;
const numberOfSeeds = 5;
// Create a buffer from height + numberOfSeeds
const randomSeed = strippedHashOfIntegerBuffer(height + numberOfSeeds);

Expand All @@ -385,9 +384,9 @@ describe('RandomModuleMethod', () => {
);
});

it('should return XOR random bytes for height=8, numberOfSeeds=3', async () => {
it('should return XOR random bytes for height=8, numberOfSeeds=4', async () => {
const height = 8;
const numberOfSeeds = 3;
const numberOfSeeds = 4;
// Create a buffer from height + numberOfSeeds
const randomSeed = strippedHashOfIntegerBuffer(height + numberOfSeeds);

Expand Down Expand Up @@ -423,67 +422,68 @@ describe('RandomModuleMethod', () => {
});
});

describe('generateRandomSeeds', () => {
describe.each(
[...randomSeedsMultipleRounds.testCases].map(testCase => [testCase.description, testCase]),
)('%s', (_description, testCase) => {
// Arrange
const { config, input, output } = testCase as any;
const validators: ValidatorSeedReveal[] = [];

for (const generator of input.blocks) {
const generatorAddress = cryptography.address.getAddressFromPublicKey(
Buffer.from(generator.generatorPublicKey, 'hex'),
);
const seedReveal = Buffer.from(generator.asset.seedReveal, 'hex');

validators.push({
generatorAddress,
seedReveal,
height: generator.height,
valid: true,
describe('getRandomBytes from protocol specs', () => {
describe.each([...testCases].map(testCase => [testCase.description, testCase]))(
'%s',
(_description, testCase) => {
// Arrange
const { config, input, output } = testCase as any;
const validators: ValidatorSeedReveal[] = [];

for (const generator of input.blocks) {
const generatorAddress = cryptography.address.getAddressFromPublicKey(
Buffer.from(generator.generatorPublicKey, 'hex'),
);
const seedReveal = Buffer.from(generator.asset.seedReveal, 'hex');

validators.push({
generatorAddress,
seedReveal,
height: generator.height,
valid: true,
});
}

beforeEach(async () => {
randomMethod = new RandomMethod(
randomModule.stores,
randomModule.events,
randomModule.name,
);
context = createTransientMethodContext({});
randomStore = randomModule.stores.get(ValidatorRevealsStore);
await randomStore.set(context, EMPTY_BYTES, { validatorReveals: validators });
});
}

beforeEach(async () => {
randomMethod = new RandomMethod(
randomModule.stores,
randomModule.events,
randomModule.name,
);
context = createTransientMethodContext({});
randomStore = randomModule.stores.get(ValidatorRevealsStore);
await randomStore.set(context, emptyBytes, { validatorReveals: validators });
});

it('should generate correct random seeds', async () => {
// Arrange
// For randomSeed 1
const round = Math.floor(
input.blocks[input.blocks.length - 1].height / config.blocksPerRound,
);
const middleThreshold = Math.floor(config.blocksPerRound / 2);
const startOfRound = config.blocksPerRound * (round - 1) + 1;
// To validate seed reveal of any block in the last round we have to check till second last round that doesn't exist for last round
const heightForSeed1 = startOfRound - (round === 2 ? 0 : middleThreshold);
// For randomSeed 2
const endOfLastRound = startOfRound - 1;
const startOfLastRound = endOfLastRound - config.blocksPerRound + 1;
// Act
const randomSeed1 = await randomMethod.getRandomBytes(
context,
heightForSeed1,
round === 2 ? middleThreshold : middleThreshold * 2,
);
// There is previous round for last round when round is 2
const randomSeed2 =
round === 2
? strippedHashOfIntegerBuffer(endOfLastRound)
: await randomMethod.getRandomBytes(context, startOfLastRound, middleThreshold * 2);
// Assert
expect(randomSeed1.toString('hex')).toEqual(output.randomSeed1);
expect(randomSeed2.toString('hex')).toEqual(output.randomSeed2);
});
});
it('should generate correct random seeds', async () => {
// Arrange
// For randomSeed 1
const round = Math.floor(
input.blocks[input.blocks.length - 1].height / config.blocksPerRound,
);
const middleThreshold = Math.floor(config.blocksPerRound / 2);
const startOfRound = config.blocksPerRound * (round - 1) + 1;
// To validate seed reveal of any block in the last round we have to check till second last round that doesn't exist for last round
const heightForSeed1 = startOfRound - (round === 2 ? 0 : middleThreshold);
// For randomSeed 2
const endOfLastRound = startOfRound - 1;
const startOfLastRound = endOfLastRound - config.blocksPerRound + 1;
// Act
const randomSeed1 = await randomMethod.getRandomBytes(
context,
heightForSeed1,
round === 2 ? middleThreshold : middleThreshold * 2,
);
// There is previous round for last round when round is 2
const randomSeed2 =
round === 2
? strippedHashOfIntegerBuffer(endOfLastRound)
: await randomMethod.getRandomBytes(context, startOfLastRound, middleThreshold * 2);
// Assert
expect(randomSeed1.toString('hex')).toEqual(output.randomSeed1);
expect(randomSeed2.toString('hex')).toEqual(output.randomSeed2);
});
},
);
});
});
2 changes: 1 addition & 1 deletion framework/test/unit/modules/random/module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe('RandomModule', () => {
it('should initialize config with default value when module config is empty', async () => {
await expect(
randomModule.init({
genesisConfig: {} as any,
genesisConfig: {} as GenesisConfig,
moduleConfig: {},
}),
).toResolve();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@
]
},
"output": {
"randomSeed1": "d900abc349e48a4ab42922f8a55725d4",
"randomSeed1": "d42310bf046b9b6568b0a12650d774e7",
"randomSeed2": "1ebc2c57112bc594c7ca32a76a89f70e"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@
]
},
"output": {
"randomSeed1": "73997b60c3b9c86aab326e2aed125dc2",
"randomSeed1": "7ebac01c8e36d94577abedf418920cf1",
"randomSeed2": "1ebc2c57112bc594c7ca32a76a89f70e"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1459,7 +1459,7 @@
]
},
"output": {
"randomSeed1": "283f543e68fea3c08e976ef66acd3586",
"randomSeed1": "251cef422571b2ef520eed289f4d64b5",
"randomSeed2": "1ebc2c57112bc594c7ca32a76a89f70e"
}
},
Expand Down Expand Up @@ -3636,8 +3636,8 @@
]
},
"output": {
"randomSeed1": "3258f0441e5d12360684a9c965ce37f0",
"randomSeed2": "354c87fa7674a8061920b9daafce92af"
"randomSeed1": "e2da98258bc71904b892a5c076fb0a35",
"randomSeed2": "cf19a137331cd3a23a4ce47ff8e63906"
}
},
{
Expand Down Expand Up @@ -7255,8 +7255,8 @@
]
},
"output": {
"randomSeed1": "16b7720b20b50ef49b9bd66b6a2034e7",
"randomSeed2": "7838d2b53d7250254d5a2ce5ff36dc5a"
"randomSeed1": "f81be5b77f53a8c23c362ed635739a2c",
"randomSeed2": "09a929cf02f58d22db53873b33ebf5b4"
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A set of test generators for random seed generation by LIP-0022

#### Input

- blocksPerRound: Number of rounds per blocks
- blocksPerRound: Number of blocks per round
- blocks: Blocks of at least last 3 rounds

#### Output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const isValidSeedReveal = (seedReveal, previousSeedReveal) =>
const selectSeedReveal = ({ fromHeight, toHeight, blocksMap, blocksPerRound }) => {
const selected = [];

for (let i = fromHeight; i >= toHeight; i -= 1) {
for (let i = fromHeight - 1; i >= toHeight; i -= 1) {
gkoumout marked this conversation as resolved.
Show resolved Hide resolved
const block = blocksMap[i];
const blockRound = calcRound(block.height, blocksPerRound);

Expand Down