lisk-framework
Version:
Lisk blockchain application platform
239 lines • 13.9 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.MainchainInteroperabilityModule = void 0;
const lisk_utils_1 = require("@liskhq/lisk-utils");
const lisk_validator_1 = require("@liskhq/lisk-validator");
const lisk_codec_1 = require("@liskhq/lisk-codec");
const base_interoperability_module_1 = require("../base_interoperability_module");
const method_1 = require("./method");
const cc_method_1 = require("./cc_method");
const endpoint_1 = require("./endpoint");
const schemas_1 = require("../schemas");
const chain_account_1 = require("../stores/chain_account");
const channel_data_1 = require("../stores/channel_data");
const own_chain_account_1 = require("../stores/own_chain_account");
const terminated_state_1 = require("../stores/terminated_state");
const terminated_outbox_1 = require("../stores/terminated_outbox");
const commands_1 = require("./commands");
const internal_method_1 = require("./internal_method");
const initialize_message_recovery_1 = require("./commands/initialize_message_recovery");
const cc_commands_1 = require("./cc_commands");
const recover_state_1 = require("./commands/recover_state");
const constants_1 = require("../constants");
const utils_1 = require("../utils");
const registered_names_1 = require("../stores/registered_names");
const errors_1 = require("../errors");
class MainchainInteroperabilityModule extends base_interoperability_module_1.BaseInteroperabilityModule {
constructor() {
super(...arguments);
this.crossChainMethod = new cc_method_1.MainchainCCMethod(this.stores, this.events);
this.internalMethod = new internal_method_1.MainchainInteroperabilityInternalMethod(this.stores, this.events, this.interoperableCCMethods);
this.method = new method_1.MainchainInteroperabilityMethod(this.stores, this.events, this.interoperableCCMethods, this.internalMethod);
this.endpoint = new endpoint_1.MainchainInteroperabilityEndpoint(this.stores, this.offchainStores);
this._sidechainRegistrationCommand = new commands_1.RegisterSidechainCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this._messageRecoveryInitializationCommand = new initialize_message_recovery_1.InitializeMessageRecoveryCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this._crossChainUpdateCommand = new commands_1.SubmitMainchainCrossChainUpdateCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this._messageRecoveryCommand = new commands_1.RecoverMessageCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this._stateRecoveryCommand = new recover_state_1.RecoverStateCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this._terminateSidechainForLivenessCommand = new commands_1.TerminateSidechainForLivenessCommand(this.stores, this.events, this.interoperableCCMethods, this.interoperableCCCommands, this.internalMethod);
this.commands = [
this._crossChainUpdateCommand,
this._messageRecoveryInitializationCommand,
this._messageRecoveryCommand,
this._sidechainRegistrationCommand,
this._stateRecoveryCommand,
this._terminateSidechainForLivenessCommand,
];
this.crossChainCommand = [
new cc_commands_1.MainchainCCRegistrationCommand(this.stores, this.events, this.interoperableCCMethods, this.internalMethod),
new cc_commands_1.MainchainCCChannelTerminatedCommand(this.stores, this.events, this.interoperableCCMethods, this.internalMethod),
];
}
addDependencies(tokenMethod, feeMethod) {
this._sidechainRegistrationCommand.addDependencies(feeMethod, tokenMethod);
this._crossChainUpdateCommand.init(this.method, tokenMethod);
this.internalMethod.addDependencies(tokenMethod);
this.tokenMethod = tokenMethod;
}
metadata() {
return {
...this.baseMetadata(),
endpoints: [
{
name: this.endpoint.getChainAccount.name,
request: schemas_1.getChainAccountRequestSchema,
response: chain_account_1.chainDataSchema,
},
{
name: this.endpoint.getAllChainAccounts.name,
request: schemas_1.getChainAccountRequestSchema,
response: chain_account_1.allChainAccountsSchema,
},
{
name: this.endpoint.getChannel.name,
request: schemas_1.getChannelRequestSchema,
response: channel_data_1.channelSchema,
},
{
name: this.endpoint.getOwnChainAccount.name,
response: own_chain_account_1.ownChainAccountSchema,
},
{
name: this.endpoint.getTerminatedStateAccount.name,
request: schemas_1.getTerminatedStateAccountRequestSchema,
response: terminated_state_1.terminatedStateSchema,
},
{
name: this.endpoint.getTerminatedOutboxAccount.name,
request: schemas_1.getTerminatedOutboxAccountRequestSchema,
response: terminated_outbox_1.terminatedOutboxSchema,
},
{
name: this.endpoint.getRegistrationFee.name,
response: schemas_1.getRegistrationFeeSchema,
},
{
name: this.endpoint.getMinimumMessageFee.name,
response: schemas_1.getMinimumMessageFeeResponseSchema,
},
{
name: this.endpoint.getChainValidators.name,
request: schemas_1.getChainValidatorsRequestSchema,
response: schemas_1.getChainValidatorsResponseSchema,
},
{
name: this.endpoint.isChainIDAvailable.name,
request: schemas_1.isChainIDAvailableRequestSchema,
response: schemas_1.isChainIDAvailableResponseSchema,
},
{
name: this.endpoint.isChainNameAvailable.name,
request: schemas_1.isChainNameAvailableRequestSchema,
response: schemas_1.isChainNameAvailableResponseSchema,
},
{
name: this.endpoint.getCCMSchema.name,
response: schemas_1.getCCMSchemaResponseSchema,
},
],
assets: [
{
version: 0,
data: schemas_1.genesisInteroperabilitySchema,
},
],
};
}
async initGenesisState(ctx) {
const genesisBlockAssetBytes = ctx.assets.getAsset(constants_1.MODULE_NAME_INTEROPERABILITY);
if (!genesisBlockAssetBytes) {
return;
}
const genesisInteroperability = lisk_codec_1.codec.decode(schemas_1.genesisInteroperabilitySchema, genesisBlockAssetBytes);
lisk_validator_1.validator.validate(schemas_1.genesisInteroperabilitySchema, genesisInteroperability);
const { ownChainName, ownChainNonce, chainInfos, terminatedStateAccounts, terminatedOutboxAccounts, } = genesisInteroperability;
const mainchainID = (0, utils_1.getMainchainID)(ctx.chainID);
if (ownChainName !== constants_1.CHAIN_NAME_MAINCHAIN) {
throw new Error(`ownChainName must be equal to ${constants_1.CHAIN_NAME_MAINCHAIN}.`);
}
if (chainInfos.length === 0 && ownChainNonce !== BigInt(0)) {
throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`);
}
else if (chainInfos.length !== 0 && ownChainNonce <= BigInt(0)) {
throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`);
}
this._verifyChainInfos(ctx, chainInfos, terminatedStateAccounts);
this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts, mainchainID);
this._verifyTerminatedOutboxAccounts(chainInfos, terminatedStateAccounts, terminatedOutboxAccounts);
await this.processGenesisState(ctx, genesisInteroperability);
}
_verifyChainInfos(ctx, chainInfos, terminatedStateAccounts) {
const chainIDs = chainInfos.map(info => info.chainID);
if (!lisk_utils_1.objects.bufferArrayUniqueItems(chainIDs)) {
throw new Error(`chainInfos doesn't hold unique chainID.`);
}
if (!lisk_utils_1.objects.isBufferArrayOrdered(chainIDs)) {
throw new Error('chainInfos is not ordered lexicographically by chainID.');
}
const chainDataNames = chainInfos.map(info => info.chainData.name);
if (new Set(chainDataNames).size !== chainDataNames.length) {
throw new Error(`chainData.name must be pairwise distinct.`);
}
const mainchainID = (0, utils_1.getMainchainID)(ctx.chainID);
for (const chainInfo of chainInfos) {
this._verifyChainID(chainInfo.chainID, mainchainID, 'chainInfo.');
this._verifyChainData(ctx, chainInfo, terminatedStateAccounts);
this._verifyChannelData(ctx, chainInfo);
this._verifyChainValidators(chainInfo);
}
}
_verifyChainData(ctx, chainInfo, terminatedStateAccounts) {
const validStatuses = [0, 1, 2];
const { chainData } = chainInfo;
if (chainData.lastCertificate.timestamp >= ctx.header.timestamp) {
throw new Error(`chainData.lastCertificate.timestamp must be less than header.timestamp.`);
}
if (!(0, utils_1.isValidName)(chainData.name)) {
throw new errors_1.InvalidNameError('chainData.name');
}
if (!validStatuses.includes(chainData.status)) {
throw new Error(`chainData.status must be one of ${validStatuses.join(', ')}`);
}
if (chainData.status === 2) {
const accountWithChainID = terminatedStateAccounts.find(accountWithChainIDTemp => accountWithChainIDTemp.chainID.equals(chainInfo.chainID));
if (!accountWithChainID) {
throw new Error('For each chainInfo with status terminated there should be a corresponding entry in terminatedStateAccounts.');
}
}
}
_verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts, mainchainID) {
this._verifyTerminatedStateAccountsIDs(terminatedStateAccounts.map(a => a.chainID));
for (const terminatedStateAccountWithChainID of terminatedStateAccounts) {
this._verifyChainID(terminatedStateAccountWithChainID.chainID, mainchainID, 'stateAccount.');
const correspondingChainInfo = chainInfos.find(chainInfo => chainInfo.chainID.equals(terminatedStateAccountWithChainID.chainID));
if (!correspondingChainInfo ||
correspondingChainInfo.chainData.status !== 2) {
throw new Error('For each terminatedStateAccount there should be a corresponding chainInfo at TERMINATED state.');
}
const stateAccount = terminatedStateAccountWithChainID.terminatedStateAccount;
if (!stateAccount.stateRoot.equals(correspondingChainInfo.chainData.lastCertificate.stateRoot)) {
throw new Error("stateAccount.stateRoot doesn't match chainInfo.chainData.lastCertificate.stateRoot.");
}
if (!stateAccount.mainchainStateRoot.equals(constants_1.EMPTY_HASH)) {
throw new Error(`stateAccount.mainchainStateRoot is not equal to ${constants_1.EMPTY_HASH.toString('hex')}.`);
}
if (!stateAccount.initialized) {
throw new Error('stateAccount is not initialized.');
}
}
}
_verifyTerminatedOutboxAccounts(_chainInfos, terminatedStateAccounts, terminatedOutboxAccounts) {
const chainIDs = terminatedOutboxAccounts.map(a => a.chainID);
if (!lisk_utils_1.objects.bufferArrayUniqueItems(chainIDs)) {
throw new Error('terminatedOutboxAccounts do not hold unique chainID.');
}
if (!lisk_utils_1.objects.isBufferArrayOrdered(chainIDs)) {
throw new Error('terminatedOutboxAccounts must be ordered lexicographically by chainID.');
}
for (const outboxAccount of terminatedOutboxAccounts) {
if (terminatedStateAccounts.find(a => a.chainID.equals(outboxAccount.chainID)) === undefined) {
throw new Error(`Each entry outboxAccount in terminatedOutboxAccounts must have a corresponding entry in terminatedStateAccount. outboxAccount with chainID: ${outboxAccount.chainID.toString('hex')} does not exist in terminatedStateAccounts`);
}
}
}
async processGenesisState(ctx, genesisInteroperability) {
await super.processGenesisState(ctx, genesisInteroperability);
const { chainInfos } = genesisInteroperability;
const namesSubStore = this.stores.get(registered_names_1.RegisteredNamesStore);
for (const chainInfo of chainInfos) {
await namesSubStore.set(ctx, Buffer.from(chainInfo.chainData.name, 'ascii'), {
chainID: chainInfo.chainID,
});
}
await namesSubStore.set(ctx, Buffer.from(constants_1.CHAIN_NAME_MAINCHAIN, 'ascii'), {
chainID: (0, utils_1.getMainchainID)(ctx.chainID),
});
}
}
exports.MainchainInteroperabilityModule = MainchainInteroperabilityModule;
//# sourceMappingURL=module.js.map
;