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 Jan 3, 2022
1 parent 079ac5d commit 454e96f
Show file tree
Hide file tree
Showing 11 changed files with 4,677 additions and 2,491 deletions.
49 changes: 36 additions & 13 deletions framework/src/modules/random/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,30 @@ import { RandomEndpoint } from './endpoint';
import {
blockHeaderAssetRandomModule,
randomModuleConfig,
randomModuleGeneratorConfig,
seedRevealSchema,
usedHashOnionsStoreSchema,
} from './schemas';
import {
BlockHeaderAssetRandomModule,
HashOnionConfig,
RegisteredDelegate,
HashOnion,
UsedHashOnion,
UsedHashOnionStoreObject,
ValidatorReveals,
} from './types';
import { Logger } from '../../logger';
import { isSeedValidInput } from './utils';
import { NotFoundError } from '../../node/generator/errors';
import { JSONObject } from '../../types';

export class RandomModule extends BaseModule {
public id = MODULE_ID_RANDOM;
public name = 'random';
public api = new RandomAPI(this.id);
public endpoint = new RandomEndpoint(this.id);

private _generatorConfig!: Record<string, unknown>;
private _generatorConfig: HashOnion[] = [];
private _maxLengthReveals!: number;

// eslint-disable-next-line @typescript-eslint/require-await
Expand All @@ -67,19 +70,41 @@ export class RandomModule extends BaseModule {
if (errors.length) {
throw new LiskValidationError(errors);
}
this._generatorConfig = generatorConfig;
if (generatorConfig) {
const generatorErrors = validator.validate(randomModuleGeneratorConfig, generatorConfig);
if (generatorErrors.length) {
throw new LiskValidationError(generatorErrors);
}
this._generatorConfig = (generatorConfig.hashOnions as JSONObject<HashOnion>[]).map(ho => ({
...ho,
address: Buffer.from(ho.address, 'hex'),
hashOnion: {
...ho.hashOnion,
hashes: ho.hashOnion.hashes.map(h => Buffer.from(h, 'hex')),
},
}));
}

this._maxLengthReveals = config.maxLengthReveals as number;
}

public async initBlock(context: BlockGenerateContext): Promise<void> {
const generatorSubStore = context.getGeneratorStore(this.id);
// Get used hash onions
const usedHashOnionsData = await generatorSubStore.get(STORE_PREFIX_USED_HASH_ONION);
let usedHashOnions: UsedHashOnion[] = [];
try {
const usedHashOnionsData = await generatorSubStore.get(STORE_PREFIX_USED_HASH_ONION);

({ usedHashOnions } = codec.decode<UsedHashOnionStoreObject>(
usedHashOnionsStoreSchema,
usedHashOnionsData,
));
} catch (error) {
if (!(error instanceof NotFoundError)) {
throw error;
}
}

const { usedHashOnions } = codec.decode<UsedHashOnionStoreObject>(
usedHashOnionsStoreSchema,
usedHashOnionsData,
);
// Get next hash onion
const nextHashOnion = this._getNextHashOnion(
usedHashOnions,
Expand Down Expand Up @@ -264,13 +289,11 @@ export class RandomModule extends BaseModule {
}

private _getHashOnionConfig(address: Buffer): HashOnionConfig {
const delegateConfig = (this._generatorConfig as {
delegates: ReadonlyArray<RegisteredDelegate>;
}).delegates?.find(d => d.address.equals(address));
if (!delegateConfig?.hashOnion) {
const hashOnionConfig = this._generatorConfig.find(d => d.address.equals(address));
if (!hashOnionConfig?.hashOnion) {
throw new Error(`Account ${address.toString('hex')} does not have hash onion in the config`);
}

return delegateConfig.hashOnion;
return hashOnionConfig.hashOnion;
}
}
35 changes: 35 additions & 0 deletions framework/src/modules/random/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,41 @@ export const randomModuleConfig = {
},
};

export const randomModuleGeneratorConfig = {
$id: 'modules/random/generator',
type: 'object',
required: [],
properties: {
hashOnions: {
type: 'array',
required: ['address', 'hashOnion'],
items: {
properties: {
address: {
type: 'string',
format: 'hex',
},
hashOnion: {
type: 'object',
required: ['count', 'distance', 'hashes'],
properties: {
count: { type: 'integer' },
distance: { type: 'integer' },
hashes: {
type: 'array',
items: {
type: 'string',
format: 'hex',
},
},
},
},
},
},
},
},
};

export const isSeedRevealValidParamsSchema = {
$id: 'modules/random/api/isSeedReveal/params',
type: 'object',
Expand Down
10 changes: 1 addition & 9 deletions framework/src/modules/random/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,15 @@ export interface UsedHashOnionStoreObject {
readonly usedHashOnions: UsedHashOnion[];
}

export interface RegisteredDelegate {
export interface HashOnion {
readonly address: Buffer;
readonly encryptedPassphrase: string;
readonly hashOnion: {
readonly count: number;
readonly distance: number;
readonly hashes: Buffer[];
};
}

export interface GeneratorConfig {
readonly force?: boolean;
readonly delegates?: ReadonlyArray<RegisteredDelegate>;
readonly defaultPassword?: string;
readonly waitThreshold: number;
}

export interface ValidatorSeedReveal {
generatorAddress: Buffer;
seedReveal: Buffer;
Expand Down
17 changes: 10 additions & 7 deletions framework/src/modules/reward/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ export class RewardAPI extends BaseAPI {
header: BlockHeader,
assets: BlockAssets,
): Promise<bigint> {
const defaultReward = calculateDefaultReward({
height: header.height,
brackets: this._brackets,
distance: this._distance,
offset: this._offset,
});
if (defaultReward === BigInt(0)) {
return defaultReward;
}

const isValidSeedReveal = await this._randomAPI.isSeedRevealValid(
context,
header.generatorAddress,
Expand All @@ -50,13 +60,6 @@ export class RewardAPI extends BaseAPI {
return BigInt(0);
}

const defaultReward = calculateDefaultReward({
height: header.height,
brackets: this._brackets,
distance: this._distance,
offset: this._offset,
});

const impliesMaximalPrevotes = await this._bftAPI.impliesMaximalPrevotes(context, header);
if (!impliesMaximalPrevotes) {
return defaultReward / BigInt(REWARD_REDUCTION_FACTOR_BFT);
Expand Down
14 changes: 11 additions & 3 deletions framework/src/node/consensus/consensus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,10 @@ export class Consensus {
// Verify validatorsHash
await this._verifyValidatorsHash(apiContext, block);
// Verify stateRoot
const currentState = await this._prepareFinalizingState(stateStore);
const currentState = await this._prepareFinalizingState(
stateStore,
this._chain.lastBlock.header.stateRoot,
);
this._verifyStateRoot(block, currentState.smt.rootHash);

await this._chain.saveBlock(block, currentState, finalizedHeight, {
Expand Down Expand Up @@ -705,20 +708,25 @@ export class Consensus {

// Offset must be set to 1, because lastBlock is still this deleting block
const stateStore = new StateStore(this._db);
const currentState = await this._prepareFinalizingState(stateStore, false);
const currentState = await this._prepareFinalizingState(
stateStore,
this._chain.lastBlock.header.stateRoot,
false,
);
await this._chain.removeBlock(block, currentState, { saveTempBlock });
this.events.emit(CONSENSUS_EVENT_BLOCK_DELETE, block);
}

private async _prepareFinalizingState(
stateStore: StateStore,
stateRoot?: Buffer,
finalize = true,
): Promise<CurrentState> {
const batch = this._db.batch();
const smtStore = new SMTStore(this._db);
const smt = new SparseMerkleTree({
db: smtStore,
rootHash: this._chain.lastBlock.header.stateRoot,
rootHash: stateRoot,
});

// On save, use finalize flag to finalize stores
Expand Down
72 changes: 65 additions & 7 deletions framework/src/testing/block_processing_env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
*
*/

import { Block, Chain, DataAccess, BlockHeader, Transaction } from '@liskhq/lisk-chain';
import { Block, Chain, DataAccess, BlockHeader, Transaction, StateStore } from '@liskhq/lisk-chain';
import { getNetworkIdentifier, getKeys } from '@liskhq/lisk-cryptography';
import { InMemoryKVStore, KVStore } from '@liskhq/lisk-db';
import { objects } from '@liskhq/lisk-utils';

import { codec } from '@liskhq/lisk-codec';
import { BaseModule } from '../modules';
import { InMemoryChannel } from '../controller';
import { loggerMock, channelMock } from './mocks';
Expand All @@ -29,9 +29,18 @@ import { Consensus } from '../node/consensus';
import { APIContext } from '../node/state_machine';
import { Node } from '../node';
import { createNewAPIContext } from '../node/state_machine/api_context';
import { blockAssetsJSON } from './fixtures/genesis-asset';
import { ValidatorsAPI } from '../modules/validators';
import { BFTAPI } from '../modules/bft';
import { TokenModule } from '../modules/token';
import { AuthModule } from '../modules/auth';
import { FeeModule } from '../modules/fee';
import { RewardModule } from '../modules/reward';
import { RandomModule } from '../modules/random';
import { DPoSModule } from '../modules/dpos_v2';

type Options = {
genesisConfig?: GenesisConfig;
genesis?: GenesisConfig;
databasePath?: string;
passphrase?: string;
};
Expand All @@ -46,13 +55,16 @@ export interface BlockProcessingEnv {
createBlock: (transactions?: Transaction[], timestamp?: number) => Promise<Block>;
getConsensus: () => Consensus;
getChain: () => Chain;
getValidatorAPI: () => ValidatorsAPI;
getBFTAPI: () => BFTAPI;
getBlockchainDB: () => KVStore;
process: (block: Block) => Promise<void>;
processUntilHeight: (height: number) => Promise<void>;
getLastBlock: () => Block;
getNextValidatorPassphrase: (blockHeader: BlockHeader) => Promise<string>;
getDataAccess: () => DataAccess;
getNetworkId: () => Buffer;
invoke: <T = void>(path: string, params?: Record<string, unknown>) => Promise<T>;
cleanup: (config: Options) => Promise<void>;
}

Expand All @@ -61,8 +73,8 @@ const getAppConfig = (genesisConfig?: GenesisConfig): ApplicationConfig => {
{},
{
...defaultConfig,
genesisConfig: {
...defaultConfig.genesisConfig,
genesis: {
...defaultConfig.genesis,
...(genesisConfig ?? {}),
},
},
Expand Down Expand Up @@ -108,16 +120,39 @@ const createProcessableBlock = async (
export const getBlockProcessingEnv = async (
params: BlockProcessingParams,
): Promise<BlockProcessingEnv> => {
const appConfig = getAppConfig(params.options?.genesisConfig);
const appConfig = getAppConfig(params.options?.genesis);

removeDB(params.options?.databasePath);
const blockchainDB = createDB('blockchain', params.options?.databasePath);
const forgerDB = createDB('forger', params.options?.databasePath);
const node = new Node({
options: appConfig,
});
const authModule = new AuthModule();
const tokenModule = new TokenModule();
const feeModule = new FeeModule();
const rewardModule = new RewardModule();
const randomModule = new RandomModule();
const dposModule = new DPoSModule();

// resolve dependencies
feeModule.addDependencies(tokenModule.api);
rewardModule.addDependencies(tokenModule.api, randomModule.api, node.bftAPI);
dposModule.addDependencies(randomModule.api, node.bftAPI, node.validatorAPI, tokenModule.api);

// register modules
node.registerModule(authModule);
node.registerModule(tokenModule);
node.registerModule(feeModule);
node.registerModule(rewardModule);
node.registerModule(randomModule);
node.registerModule(dposModule);
const blockAssets = blockAssetsJSON.map(asset => ({
...asset,
data: codec.fromJSON<Record<string, unknown>>(asset.schema, asset.data),
}));
const genesisBlock = await node.generateGenesisBlock({
assets: [],
assets: blockAssets,
});
await node.init({
blockchainDB,
Expand All @@ -138,6 +173,8 @@ export const getBlockProcessingEnv = async (
createProcessableBlock(node, transactions, timestamp),
getChain: () => node['_chain'],
getConsensus: () => node['_consensus'],
getValidatorAPI: () => node['_validatorsModule'].api,
getBFTAPI: () => node['_bftModule'].api,
getBlockchainDB: () => blockchainDB,
process: async (block): Promise<void> => node['_consensus'].execute(block),
processUntilHeight: async (height): Promise<void> => {
Expand All @@ -155,6 +192,27 @@ export const getBlockProcessingEnv = async (

return passphrase;
},
invoke: async <T = void>(path: string, input: Record<string, unknown> = {}): Promise<T> => {
const [mod, method] = path.split('_');
const endpoints = node.getModuleEndpoints();
const endpoint = endpoints[mod];
if (endpoint === undefined) {
throw new Error(`Invalid endpoint ${mod} to invoke`);
}
const handler = endpoint[method];
if (handler === undefined) {
throw new Error(`Invalid endpoint ${method} is not registered for ${mod}`);
}
const stateStore = new StateStore(node['_blockchainDB']);
const result = await handler({
getStore: (moduleID: number, storePrefix: number) =>
stateStore.getStore(moduleID, storePrefix),
logger: node['_logger'],
networkIdentifier: node['_chain'].networkIdentifier,
params: input,
});
return result as T;
},
getNetworkId: () => networkIdentifier,
getDataAccess: () => node['_chain'].dataAccess,
cleanup: async ({ databasePath }): Promise<void> => {
Expand Down
Loading

0 comments on commit 454e96f

Please sign in to comment.