lisk-framework
Version:
Lisk blockchain application platform
396 lines • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ABIHandler = exports.EVENT_ENGINE_READY = void 0;
const events_1 = require("events");
const lisk_chain_1 = require("@liskhq/lisk-chain");
const lisk_db_1 = require("@liskhq/lisk-db");
const lisk_codec_1 = require("@liskhq/lisk-codec");
const lisk_cryptography_1 = require("@liskhq/lisk-cryptography");
const abi_1 = require("../abi");
const state_machine_1 = require("../state_machine");
const generator_context_1 = require("../state_machine/generator_context");
const prefixed_state_read_writer_1 = require("../state_machine/prefixed_state_read_writer");
const genesis_block_1 = require("../utils/genesis_block");
const constants_1 = require("../constants");
const backup_1 = require("../utils/backup");
exports.EVENT_ENGINE_READY = 'EVENT_ENGINE_READY';
class ABIHandler {
constructor(args) {
this.event = new events_1.EventEmitter();
this._config = args.config;
this._logger = args.logger;
this._stateMachine = args.stateMachine;
this._stateDB = args.stateDB;
this._moduleDB = args.moduleDB;
this._modules = args.modules;
this._channel = args.channel;
this._chainID = args.chainID;
}
get chainID() {
if (!this._chainID) {
throw new Error('Chain ID is not set.');
}
return this._chainID;
}
async cacheGenesisState() {
var _a, _b;
const { version, root } = await this._stateDB.getCurrentState();
if (version !== 0 || !root.equals(lisk_cryptography_1.utils.hash(Buffer.alloc(0)))) {
this._logger.debug({ version, root: root.toString('hex') }, 'Skip caching genesis state as state is already initialized');
return;
}
const genesisBlock = (0, genesis_block_1.readGenesisBlock)(this._config, this._logger);
this._logger.info({
stateRoot: (_a = genesisBlock.header.stateRoot) === null || _a === void 0 ? void 0 : _a.toString('hex'),
height: genesisBlock.header.height,
}, 'Start caching genesis state');
const stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter());
const context = new state_machine_1.GenesisBlockContext({
eventQueue: new state_machine_1.EventQueue(genesisBlock.header.height),
header: genesisBlock.header,
logger: this._logger,
stateStore,
assets: genesisBlock.assets,
chainID: this.chainID,
});
let stateRoot;
try {
await this._stateMachine.executeGenesisBlock(context);
stateRoot = await this._stateDB.commit(stateStore.inner, genesisBlock.header.height, lisk_cryptography_1.utils.hash(Buffer.alloc(0)), {
readonly: false,
expectedRoot: genesisBlock.header.stateRoot,
checkRoot: true,
});
}
finally {
stateStore.inner.close();
}
this._cachedGenesisState = {
stateRoot,
events: context.eventQueue.getEvents().map(e => e.toObject()),
certificateThreshold: context.nextValidators.certificateThreshold,
nextValidators: context.nextValidators.validators,
preCommitThreshold: context.nextValidators.precommitThreshold,
};
this._logger.info({
stateRoot: (_b = genesisBlock.header.stateRoot) === null || _b === void 0 ? void 0 : _b.toString('hex'),
height: genesisBlock.header.height,
}, 'Cached genesis state');
}
async init(req) {
this._chainID = Buffer.from(this._config.genesis.chainID, 'hex');
const currentState = await this._stateDB.getCurrentState();
if (req.lastBlockHeight > currentState.version) {
throw new Error(`Invalid engine state. Conflict at engine height ${req.lastBlockHeight} and application state ${currentState.version}.`);
}
if (req.lastBlockHeight < currentState.version) {
this._logger.info({ engineHeight: req.lastBlockHeight, applicationHeight: currentState.version }, 'Application is in invalid state. Trying to recover');
const diff = currentState.version - req.lastBlockHeight;
for (let i = 0; i < diff; i += 1) {
const previousRoot = await this._stateDB.revert(currentState.root, currentState.version);
currentState.root = previousRoot;
currentState.version -= 1;
}
if (!currentState.root.equals(req.lastStateRoot)) {
throw new Error(`State cannot be recovered. Conflict at height ${req.lastBlockHeight} Engine state ${req.lastStateRoot.toString('hex')} and application state ${currentState.root.toString('hex')}.`);
}
}
this.event.emit(exports.EVENT_ENGINE_READY);
return {};
}
async initStateMachine(req) {
if (this._executionContext !== undefined) {
throw new Error(`Execution context is already initialized with ${this._executionContext.id.toString('hex')}`);
}
const id = lisk_cryptography_1.utils.hash(lisk_codec_1.codec.encode(abi_1.blockHeaderSchema, req.header));
this._executionContext = {
id,
header: new lisk_chain_1.BlockHeader(req.header),
stateStore: new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter()),
contextStore: new Map(),
moduleStore: new lisk_chain_1.StateStore(this._moduleDB),
};
return {
contextID: id,
};
}
async initGenesisState(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
if (this._cachedGenesisState) {
this._logger.debug({ stateRoot: this._cachedGenesisState.stateRoot }, 'Responding with cached genesis state');
return this._cachedGenesisState;
}
const genesisBlock = (0, genesis_block_1.readGenesisBlock)(this._config, this._logger);
const context = new state_machine_1.GenesisBlockContext({
eventQueue: new state_machine_1.EventQueue(genesisBlock.header.height),
header: genesisBlock.header,
logger: this._logger,
stateStore: this._executionContext.stateStore,
assets: genesisBlock.assets,
chainID: this.chainID,
});
await this._stateMachine.executeGenesisBlock(context);
return {
events: context.eventQueue.getEvents().map(e => e.toObject()),
certificateThreshold: context.nextValidators.certificateThreshold,
nextValidators: context.nextValidators.validators,
preCommitThreshold: context.nextValidators.precommitThreshold,
};
}
async insertAssets(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const context = new generator_context_1.GenerationContext({
header: this._executionContext.header,
logger: this._logger,
stateStore: this._executionContext.stateStore,
contextStore: this._executionContext.contextStore,
chainID: this.chainID,
generatorStore: this._executionContext.moduleStore,
finalizedHeight: req.finalizedHeight,
});
await this._stateMachine.insertAssets(context);
const batch = new lisk_db_1.Batch();
this._executionContext.moduleStore.finalize(batch);
await this._moduleDB.write(batch);
return {
assets: context.assets.getAll(),
};
}
async verifyAssets(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const context = new state_machine_1.BlockContext({
header: this._executionContext.header,
logger: this._logger,
stateStore: this._executionContext.stateStore,
contextStore: this._executionContext.contextStore,
chainID: this.chainID,
assets: new lisk_chain_1.BlockAssets(req.assets),
eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height),
transactions: [],
});
await this._stateMachine.verifyAssets(context);
return {};
}
async beforeTransactionsExecute(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const context = new state_machine_1.BlockContext({
header: this._executionContext.header,
logger: this._logger,
stateStore: this._executionContext.stateStore,
contextStore: this._executionContext.contextStore,
chainID: this.chainID,
assets: new lisk_chain_1.BlockAssets(req.assets),
eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height),
transactions: [],
});
await this._stateMachine.beforeTransactionsExecute(context);
return {
events: context.eventQueue.getEvents().map(e => e.toObject()),
};
}
async afterTransactionsExecute(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const context = new state_machine_1.BlockContext({
header: this._executionContext.header,
logger: this._logger,
stateStore: this._executionContext.stateStore,
contextStore: this._executionContext.contextStore,
chainID: this.chainID,
assets: new lisk_chain_1.BlockAssets(req.assets),
eventQueue: new state_machine_1.EventQueue(this._executionContext.header.height),
transactions: req.transactions.map(tx => new lisk_chain_1.Transaction(tx)),
});
await this._stateMachine.afterExecuteBlock(context);
return {
certificateThreshold: context.nextValidators.certificateThreshold,
nextValidators: context.nextValidators.validators,
preCommitThreshold: context.nextValidators.precommitThreshold,
events: context.eventQueue.getEvents().map(e => e.toObject()),
};
}
async verifyTransaction(req) {
var _a, _b, _c, _d;
let stateStore;
let contextStore;
let header;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter());
contextStore = new Map();
header = new lisk_chain_1.BlockHeader(req.header);
}
else {
stateStore = this._executionContext.stateStore;
contextStore = this._executionContext.contextStore;
header = this._executionContext.header;
}
const context = new state_machine_1.TransactionContext({
eventQueue: new state_machine_1.EventQueue(0),
logger: this._logger,
transaction: new lisk_chain_1.Transaction(req.transaction),
stateStore,
contextStore,
chainID: this.chainID,
header,
});
const result = await this._stateMachine.verifyTransaction(context, req.onlyCommand);
if (!((_b = this._executionContext) === null || _b === void 0 ? void 0 : _b.id.equals(req.contextID))) {
stateStore.inner.close();
}
return {
result: result.status,
errorMessage: (_d = (_c = result.error) === null || _c === void 0 ? void 0 : _c.message) !== null && _d !== void 0 ? _d : '',
};
}
async executeTransaction(req) {
var _a;
let stateStore;
let contextStore;
let header;
if (!req.dryRun) {
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
stateStore = this._executionContext.stateStore;
contextStore = this._executionContext.contextStore;
header = this._executionContext.header;
}
else {
stateStore = new prefixed_state_read_writer_1.PrefixedStateReadWriter(this._stateDB.newReadWriter());
contextStore = new Map();
header = new lisk_chain_1.BlockHeader(req.header);
}
const context = new state_machine_1.TransactionContext({
eventQueue: new state_machine_1.EventQueue(header.height),
logger: this._logger,
transaction: new lisk_chain_1.Transaction(req.transaction),
stateStore,
contextStore,
chainID: this.chainID,
assets: new lisk_chain_1.BlockAssets(req.assets),
header,
});
const status = await this._stateMachine.executeTransaction(context);
if (req.dryRun) {
stateStore.inner.close();
}
return {
events: context.eventQueue.getEvents().map(e => e.toObject()),
result: status,
};
}
async commit(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const currentState = await this._stateDB.getCurrentState();
if (req.expectedStateRoot.equals(currentState.root)) {
this._logger.debug({ stateRoot: currentState.root }, 'Skipping commit. Current state is already expected state');
this._executionContext.stateStore.inner.close();
return { stateRoot: currentState.root };
}
try {
const stateRoot = await this._stateDB.commit(this._executionContext.stateStore.inner, this._executionContext.header.height, req.stateRoot, {
checkRoot: req.expectedStateRoot.length > 0,
readonly: req.dryRun,
expectedRoot: req.expectedStateRoot,
});
return {
stateRoot,
};
}
finally {
this._executionContext.stateStore.inner.close();
if (this._config.system.backup.height > 0 &&
this._executionContext.header.height === this._config.system.backup.height) {
(0, backup_1.backupDatabase)(this._config.system.dataPath, constants_1.STATE_DB_NAME, this._stateDB).catch(err => {
this._logger.error({ err: err, height: this._config.system.backup.height }, 'Failed to create backup for stateDB');
});
}
}
}
async revert(req) {
var _a;
if (!((_a = this._executionContext) === null || _a === void 0 ? void 0 : _a.id.equals(req.contextID))) {
throw new Error(`Invalid context id ${req.contextID.toString('hex')}. Context is not initialized or different.`);
}
const stateRoot = await this._stateDB.revert(req.stateRoot, this._executionContext.header.height);
return {
stateRoot,
};
}
async clear(_req) {
this._executionContext = undefined;
return {};
}
async finalize(req) {
if (req.finalizedHeight === 0) {
return {};
}
await this._stateDB.finalize(req.finalizedHeight);
return {};
}
async getMetadata(_req) {
const modules = this._modules.map(mod => {
const meta = mod.metadata();
return {
...meta,
name: mod.name,
};
});
modules.sort((a, b) => a.name.localeCompare(b.name, 'en'));
const data = Buffer.from(JSON.stringify({ modules }), 'utf-8');
return {
data,
};
}
async query(req) {
const params = JSON.parse(req.params.toString('utf8'));
try {
const resp = await this._channel.invoke({
context: {
header: req.header,
},
methodName: req.method,
params,
});
this._logger.info({ method: req.method }, 'Called ABI query successfully');
return {
data: Buffer.from(JSON.stringify(resp !== null && resp !== void 0 ? resp : '{}'), 'utf-8'),
};
}
catch (error) {
this._logger.info({ method: req.method, err: error }, 'Failed to call ABI query');
return {
data: Buffer.from(JSON.stringify({
error: {
message: error.message,
},
}), 'utf-8'),
};
}
}
async prove(req) {
const proof = await this._stateDB.prove(req.stateRoot, req.keys);
return {
proof,
};
}
}
exports.ABIHandler = ABIHandler;
//# sourceMappingURL=abi_handler.js.map