From f9014b087daee63d38738d953bcf34b02d19013a Mon Sep 17 00:00:00 2001 From: Rafael Belchior Date: Wed, 21 Aug 2024 14:01:16 +0100 Subject: [PATCH] chore(satp-hermes): crash recovery architecture Signed-off-by: Rafael Belchior --- .../src/main/typescript/blo/dispatcher.ts | 2 + .../blo/recover/recover-handler-service.ts | 2 + .../blo/recover/rollback-handler-service.ts | 2 + .../typescript/core/recovery/crash-manager.ts | 199 ++++++++++++++++++ .../recovery/crash-recovery-client-service.ts | 5 +- .../core/recovery/crash-recovery-handler.ts | 55 ++--- .../recovery/crash-recovery-server-service.ts | 2 + .../rollback/rollback-strategy-factory.ts | 55 +++++ .../rollback/stage0-rollback-strategy.ts | 45 ++++ .../rollback/stage1-rollback-strategy.ts | 59 ++++++ .../rollback/stage2-rollback-strategy.ts | 59 ++++++ .../rollback/stage3-rollback-strategy.ts | 59 ++++++ .../generated/SATPWrapperContract.ts | 110 +++++----- .../typescript/plugin-satp-hermes-gateway.ts | 14 ++ 14 files changed, 569 insertions(+), 99 deletions(-) create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/recover-handler-service.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/rollback-handler-service.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-manager.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/rollback-strategy-factory.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage0-rollback-strategy.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage1-rollback-strategy.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage2-rollback-strategy.ts create mode 100644 packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage3-rollback-strategy.ts diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/dispatcher.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/dispatcher.ts index 966de6ef1c8..1919ac9b753 100644 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/dispatcher.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/dispatcher.ts @@ -114,6 +114,8 @@ export class BLODispatcher { public async Transact(req: StatusRequest): Promise { return ExecuteGetStatus(this.logger, req); } + + // TODO implement recovery handlers // get channel by caller; give needed client from orchestrator to handler to call // for all channels, find session id on request // TODO implement handlers GetAudit, Transact, Cancel, Routes diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/recover-handler-service.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/recover-handler-service.ts new file mode 100644 index 00000000000..79173614d61 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/recover-handler-service.ts @@ -0,0 +1,2 @@ +// handler to allow a user application to communicate a gateway it crashed and needs to be recovered. It "forces" and update of status with a counterparty gateway +// TODO update the spec with a RecoverForce message that is handled by this handler diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/rollback-handler-service.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/rollback-handler-service.ts new file mode 100644 index 00000000000..edd6a040739 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/blo/recover/rollback-handler-service.ts @@ -0,0 +1,2 @@ +// handler to allow a user application to force a rollback +// TODO update the spec with RollbackForce message that is handled by this handler diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-manager.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-manager.ts new file mode 100644 index 00000000000..993719f3ace --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-manager.ts @@ -0,0 +1,199 @@ +import { + Logger, + LoggerProvider, + Checks, + LogLevelDesc, +} from "@hyperledger/cactus-common"; +import { SessionData } from "../../generated/proto/cacti/satp/v02/common/session_pb"; +import { CrashRecoveryHandler } from "./crash-recovery-handler"; +import { SATPSession } from "../satp-session"; +import { + RollbackState, + RollbackStrategy, + RollbackStrategyFactory, +} from "./rollback/rollback-strategy-factory"; + +enum CrashStatus { + IN_RECOVERY = "IN_RECOVERY", + RECOVERED = "RECOVERED", + NO_CRASH = "NO_CRASH", +} + +class CrashOccurrence { + constructor( + public status: CrashStatus, + public time: Date, + public lastUpdate: Date, + ) {} +} + +export interface ICrashRecoveryManagerOptions { + logLevel?: LogLevelDesc; + instanceId: string; +} + +export class CrashRecoveryManager { + public static readonly CLASS_NAME = "CrashRecoveryManager"; + private readonly log: Logger; + private readonly instanceId: string; + private readonly sessions: Map; + private crashRecoveryHandler: CrashRecoveryHandler; + private factory: RollbackStrategyFactory; + + constructor(public readonly options: ICrashRecoveryManagerOptions) { + const fnTag = `${CrashRecoveryManager.CLASS_NAME}#constructor()`; + Checks.truthy(options, `${fnTag} arg options`); + + const level = this.options.logLevel || "DEBUG"; + const label = this.className; + this.log = LoggerProvider.getOrCreate({ level, label }); + this.instanceId = options.instanceId; + this.sessions = this.getSessions() || new Map(); + this.log.info(`Instantiated ${this.className} OK`); + this.factory = new RollbackStrategyFactory(); + } + + get className(): string { + return CrashRecoveryManager.CLASS_NAME; + } + + private getSessions(): Map { + // todo read from local log to get session data + return new Map(); + } + + // todo create util functoin that retrieves sessionid and checks if it is valid; i believe it is implemented in the satp services, refactor making it reusable + private checkCrash(sessionId: SATPSession): Promise { + // todo implement crash check - check logs and understsands if there was a crash; might use timouts, etc + return Promise.resolve(false); + } + + public async setupCrashManager() { + // todo setup handler, need to create services + this.crashRecoveryHandler = new CrashRecoveryHandler({ + loggerOptions: { + label: "CrashRecoveryHandler", + level: "DEBUG", + }, + serverService: null, + clientService: null, + sessions: this.sessions, + }); + } + + public async checkAndResolveCrash(sessionId: SATPSession): Promise { + const fnTag = `${this.className}#checkAndResolveCrash()`; + this.log.info(`${fnTag} Checking crash status for session ${sessionId}`); + + try { + const didCrash = await this.checkCrash(sessionId); + if (didCrash) { + // create new occurrence + const crashOccurrence = new CrashOccurrence( + CrashStatus.IN_RECOVERY, + new Date(), + new Date(), + ); + this.log.debug(crashOccurrence); + // todo manage occurrence + // call corresponding services via handler for crash recovery + return false; + } else { + this.log.error( + `${fnTag} Failed to resolve crash for session ${sessionId}`, + ); + // should panic and stop the server, for manual inspection + return false; + } + } catch (error) { + this.log.error( + `${fnTag} Error during crash check and resolution: ${error}`, + ); + return false; + } + } + + public async initiateRollback( + session: SATPSession, + forceRollback?: boolean, + ): Promise { + const fnTag = `CrashRecoveryManager#initiateRollback()`; + this.log.info( + `${fnTag} Initiating rollback for session ${session.getSessionId()}`, + ); + + try { + // Implement check for rollback (needs to read logs, etc) OR we assume that at satp handler/service layer this check is done and rollback is good to do + const shouldRollback = true; // todo implement check + + if (forceRollback || shouldRollback) { + // send bridge manager and possibly others to factory + const strategy = this.factory.createStrategy(session); + const rollbackState = await this.executeRollback(strategy, session); + + if (rollbackState) { + const cleanupSuccess = await this.performCleanup( + strategy, + session, + rollbackState, + ); + return cleanupSuccess; + } else { + this.log.error( + `${fnTag} Rollback execution failed for session ${session.getSessionId()}`, + ); + return false; + } + } else { + this.log.info( + `${fnTag} Rollback not needed for session ${session.getSessionId()}`, + ); + return true; + } + } catch (error) { + this.log.error(`${fnTag} Error during rollback initiation: ${error}`); + return false; + } + } + + private async executeRollback( + strategy: RollbackStrategy, + session: SATPSession, + ): Promise { + const fnTag = `CrashRecoveryManager#executeRollback`; + this.log.debug( + `${fnTag} Executing rollback strategy for session ${session.getSessionId()}`, + ); + + try { + return await strategy.execute(session); + } catch (error) { + this.log.error(`${fnTag} Error executing rollback strategy: ${error}`); + } + } + + private async performCleanup( + strategy: RollbackStrategy, + session: SATPSession, + state: RollbackState, + ): Promise { + const fnTag = `CrashRecoveryManager#performCleanup`; + this.log.debug( + `${fnTag} Performing cleanup after rollback for session ${session.getSessionId()}`, + ); + + try { + const updatedState = await strategy.cleanup(session, state); + + // TODO: Handle the updated state, perhaps update session data or perform additional actions + this.log.info( + `${fnTag} Cleanup completed. Updated state: ${JSON.stringify(updatedState)}`, + ); + + return true; + } catch (error) { + this.log.error(`${fnTag} Error during cleanup: ${error}`); + return false; + } + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-client-service.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-client-service.ts index 69eea3595f1..d8a76f46c0f 100644 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-client-service.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-client-service.ts @@ -6,12 +6,11 @@ import { } from "../../generated/proto/cacti/satp/v02/crash_recovery_pb"; import { SessionData } from "../../generated/proto/cacti/satp/v02/common/session_pb"; +// use connect protocol to send messages to the counterparty server; implement protocol export class CrashRecoveryClientService { createRecoverMessage(sessionData: SessionData): RecoverMessage {} - async sendRecoverMessage( - message: RecoverMessage, - ): Promise {} + async sendRecover(message: RecoverMessage): Promise {} async sendRecoverUpdateMessage( message: RecoverUpdateMessage, diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-handler.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-handler.ts index 9b3e4edc5b1..603eefa8f93 100644 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-handler.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-handler.ts @@ -16,6 +16,7 @@ import { } from "@hyperledger/cactus-common"; import { Empty } from "@bufbuild/protobuf"; import { SessionData } from "../../generated/proto/cacti/satp/v02/common/session_pb"; +import { SATPSession } from "../satp-session"; interface HandlerOptions { serverService: CrashRecoveryServerService; @@ -48,9 +49,7 @@ export class CrashRecoveryHandler { return this.logger; } - async RecoverMessageImplementation( - req: RecoverMessage, - ): Promise { + async sendRecover(req: RecoverMessage): Promise { const stepTag = `RecoverV2MessageImplementation()`; const fnTag = `${this.getHandlerIdentifier()}#${stepTag}`; try { @@ -77,7 +76,7 @@ export class CrashRecoveryHandler { } } - async RecoverUpdateMessageImplementation( + async sendRecoverUpdate( req: RecoverUpdateMessage, ): Promise { const fnTag = `${this.getHandlerIdentifier()}#handleRecoverUpdateMessage()`; @@ -107,9 +106,7 @@ export class CrashRecoveryHandler { } } - async RecoverSuccessMessageImplementation( - req: RecoverSuccessMessage, - ): Promise { + async sendRecoverSuccess(req: RecoverSuccessMessage): Promise { const fnTag = `${this.getHandlerIdentifier()}#handleRecoverSuccessMessage()`; try { this.Log.debug(`${fnTag}, Handling Recover Success Message...`); @@ -129,9 +126,7 @@ export class CrashRecoveryHandler { } } - async RollbackMessageImplementation( - req: RollbackMessage, - ): Promise { + async sendRollback(req: RollbackMessage): Promise { const fnTag = `${this.getHandlerIdentifier()}#handleRollbackMessage()`; try { this.Log.debug(`${fnTag}, Handling Rollback Message...`); @@ -152,9 +147,7 @@ export class CrashRecoveryHandler { } } - async RollbackAckMessageImplementation( - req: RollbackAckMessage, - ): Promise { + async sendRollbackAck(req: RollbackAckMessage): Promise { const fnTag = `${this.getHandlerIdentifier()}#handleRollbackAckMessage()`; try { this.Log.debug(`${fnTag}, Handling Rollback Ack Message...`); @@ -176,15 +169,16 @@ export class CrashRecoveryHandler { setupRouter(router: ConnectRouter): void { router.service(CrashRecovery, { - recoverV2Message: this.RecoverMessageImplementation, - recoverV2UpdateMessage: this.RecoverUpdateMessageImplementation, - recoverV2SuccessMessage: this.RecoverSuccessMessageImplementation, - rollbackV2Message: this.RollbackMessageImplementation, - rollbackV2AckMessage: this.RollbackAckMessageImplementation, + recoverV2Message: this.sendRecover, + recoverV2UpdateMessage: this.sendRecoverUpdate, + recoverV2SuccessMessage: this.sendRecoverSuccess, + rollbackV2Message: this.sendRollback, + rollbackV2AckMessage: this.sendRollbackAck, }); } - public async SendRecoverMessage( + // TODO! what is this function for? seems like a service function + public async sendRecoverTODO( sessionId: string, ): Promise { const stepTag = `SendRecoverV2Message()`; @@ -198,8 +192,7 @@ export class CrashRecoveryHandler { } const recoverMessage = this.clientService.createRecoverMessage(session); - const response = - await this.clientService.sendRecoverMessage(recoverMessage); + const response = await this.clientService.sendRecover(recoverMessage); if (!response) { throw new Error(`${fnTag}, Failed to receive RecoverUpdateMessage`); @@ -227,24 +220,4 @@ export class CrashRecoveryHandler { throw new Error(`${fnTag}, Error sending Recover Update: ${error}`); } } - - public async InitiateRollback(sessionId: string): Promise { - const fnTag = `${this.getHandlerIdentifier()}#initiateRollback()`; - try { - this.Log.debug(`${fnTag}, Initiating Rollback...`); - - const session = this.sessions.get(sessionId); - if (!session) { - throw new Error(`${fnTag}, Session not found`); - } - - const rollbackMessage = this.clientService.createRollbackMessage(session); - - this.Log.debug(`${fnTag}, Rollback Message created`); - - return rollbackMessage; - } catch (error) { - throw new Error(`${fnTag}, Error initiating Rollback: ${error}`); - } - } } diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-server-service.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-server-service.ts index 4968d682364..2bba49e26ff 100644 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-server-service.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/crash-recovery-server-service.ts @@ -7,6 +7,8 @@ import { } from "../../generated/proto/cacti/satp/v02/crash_recovery_pb"; import { SessionData } from "../../generated/proto/cacti/satp/v02/common/session_pb"; +// use connect protocol to receive messages from the crashed server; implement protocol +// will need access to local logs and remote logs export class CrashRecoveryServerService { createRecoverUpdateMessage( request: RecoverMessage, diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/rollback-strategy-factory.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/rollback-strategy-factory.ts new file mode 100644 index 00000000000..76243d68b22 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/rollback-strategy-factory.ts @@ -0,0 +1,55 @@ +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { SATPSession } from "../../satp-session"; +import { Stage0RollbackStrategy } from "./stage0-rollback-strategy"; +import { Stage1RollbackStrategy } from "./stage1-rollback-strategy"; +import { Stage2RollbackStrategy } from "./stage2-rollback-strategy"; +import { Stage3RollbackStrategy } from "./stage3-rollback-strategy"; + +export interface RollbackState { + currentStage: string; + // todo add rollback state + // placeholder, should import RollbackLogEntry from protos. + // RollbackLogEntry in spec = RollbackState in code +} + +export interface RollbackStrategy { + execute(session: SATPSession): Promise; + // todo do we want to return any information? + cleanup(session: SATPSession, state: RollbackState): Promise; +} + +export class RollbackStrategyFactory { + private log: Logger; + + constructor() { + this.log = LoggerProvider.getOrCreate({ label: "RollbackStrategyFactory" }); + } + + // todo add bridge manager and possibly others so each strategy can connect to satp bridge + createStrategy(session: SATPSession): RollbackStrategy { + const fnTag = "RollbackStrategyFactory#createStrategy"; + const sessionData = session.hasClientSessionData() + ? session.getClientSessionData()! + : session.getServerSessionData()!; + + if (!sessionData.hashes) { + this.log.debug(`${fnTag} Creating Stage0RollbackStrategy`); + return new Stage0RollbackStrategy(); + } else if ( + !sessionData.hashes.stage2 || + Object.keys(sessionData.hashes.stage2).length === 0 + ) { + this.log.debug(`${fnTag} Creating Stage1RollbackStrategy`); + return new Stage1RollbackStrategy(); + } else if ( + !sessionData.hashes.stage3 || + Object.keys(sessionData.hashes.stage3).length === 0 + ) { + this.log.debug(`${fnTag} Creating Stage2RollbackStrategy`); + return new Stage2RollbackStrategy(); + } else { + this.log.debug(`${fnTag} Creating Stage3RollbackStrategy`); + return new Stage3RollbackStrategy(); + } + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage0-rollback-strategy.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage0-rollback-strategy.ts new file mode 100644 index 00000000000..df0415fd2b2 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage0-rollback-strategy.ts @@ -0,0 +1,45 @@ +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { SATPSession } from "./satp-session"; +import { RollbackState, RollbackStrategy } from "./rollback-strategy-factory"; + +export class Stage0RollbackStrategy implements RollbackStrategy { + private log: Logger; + + constructor() { + this.log = LoggerProvider.getOrCreate({ label: "Stage0RollbackStrategy" }); + } + + // return a rollback state in all strategies + async execute(session: SATPSession): Promise { + const fnTag = "Stage0RollbackStrategy#execute"; + this.log.info(`${fnTag} Executing rollback for Stage 0`); + + // check session exists + if (!session) { + this.log.error(`${fnTag} Session not found`); + return false; + } + try { + // TODO record the rollback on the log. Implement RollbackLogEntry + this.log.debug("Persisting rollback log entry"); + + this.log.info(`Successfully rolled back Stage 0`); + return true; + } catch (error) { + this.log.error(`Failed to rollback Stage 0: ${error}`); + return false; + } + } + + private async cleanup(session: SATPSession): Promise { + const fnTag = "Stage0RollbackStrategy#cleanup"; + // for stage 0, do nothing + const state: RollbackState = { + currentStage: "Stage0", + }; + if (!session) { + this.log.error(`${fnTag} Session not found`); + } + return state; + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage1-rollback-strategy.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage1-rollback-strategy.ts new file mode 100644 index 00000000000..e6ecaa90b75 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage1-rollback-strategy.ts @@ -0,0 +1,59 @@ +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { SATPSession } from "./satp-session"; +import { RollbackState, RollbackStrategy } from "./rollback-strategy-factory"; + +export class Stage1RollbackStrategy implements RollbackStrategy { + private log: Logger; + + constructor() { + this.log = LoggerProvider.getOrCreate({ label: "Stage1RollbackStrategy" }); + } + + async execute(session: SATPSession): Promise { + const fnTag = "Stage1RollbackStrategy#execute"; + this.log.info(`${fnTag} Executing rollback for Stage 1`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return false; + } + + try { + // TODO: Implement Stage 1 specific rollback logic + + // TODO: Record the rollback on the log. Implement RollbackLogEntry + this.log.debug("Persisting rollback log entry"); + + this.log.info(`Successfully rolled back Stage 1`); + return true; + } catch (error) { + this.log.error(`Failed to rollback Stage 1: ${error}`); + return false; + } + } + + async cleanup( + session: SATPSession, + state: RollbackState, + ): Promise { + const fnTag = "Stage1RollbackStrategy#cleanup"; + this.log.info(`${fnTag} Cleaning up after Stage 1 rollback`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return state; + } + + try { + // TODO: Implement Stage 1 specific cleanup logic + + state.currentStage = "Stage1"; + // TODO: Update other state properties as needed + + return state; + } catch (error) { + this.log.error(`${fnTag} Cleanup failed: ${error}`); + return state; + } + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage2-rollback-strategy.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage2-rollback-strategy.ts new file mode 100644 index 00000000000..b984d767b6a --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage2-rollback-strategy.ts @@ -0,0 +1,59 @@ +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { SATPSession } from "./satp-session"; +import { RollbackState, RollbackStrategy } from "./rollback-strategy-factory"; + +export class Stage2RollbackStrategy implements RollbackStrategy { + private log: Logger; + + constructor() { + this.log = LoggerProvider.getOrCreate({ label: "Stage2RollbackStrategy" }); + } + + async execute(session: SATPSession): Promise { + const fnTag = "Stage2RollbackStrategy#execute"; + this.log.info(`${fnTag} Executing rollback for Stage 2`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return false; + } + + try { + // TODO: Implement Stage 2 specific rollback logic + + // TODO: Record the rollback on the log. Implement RollbackLogEntry + this.log.debug("Persisting rollback log entry"); + + this.log.info(`Successfully rolled back Stage 2`); + return true; + } catch (error) { + this.log.error(`Failed to rollback Stage 2: ${error}`); + return false; + } + } + + async cleanup( + session: SATPSession, + state: RollbackState, + ): Promise { + const fnTag = "Stage2RollbackStrategy#cleanup"; + this.log.info(`${fnTag} Cleaning up after Stage 2 rollback`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return state; + } + + try { + // TODO: Implement Stage 2 specific cleanup logic + + state.currentStage = "Stage2"; + // TODO: Update other state properties as needed + + return state; + } catch (error) { + this.log.error(`${fnTag} Cleanup failed: ${error}`); + return state; + } + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage3-rollback-strategy.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage3-rollback-strategy.ts new file mode 100644 index 00000000000..498649cb698 --- /dev/null +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/core/recovery/rollback/stage3-rollback-strategy.ts @@ -0,0 +1,59 @@ +import { Logger, LoggerProvider } from "@hyperledger/cactus-common"; +import { SATPSession } from "./satp-session"; +import { RollbackState, RollbackStrategy } from "./rollback-strategy-factory"; + +export class Stage3RollbackStrategy implements RollbackStrategy { + private log: Logger; + + constructor() { + this.log = LoggerProvider.getOrCreate({ label: "Stage3RollbackStrategy" }); + } + + async execute(session: SATPSession): Promise { + const fnTag = "Stage3RollbackStrategy#execute"; + this.log.info(`${fnTag} Executing rollback for Stage 3`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return false; + } + + try { + // TODO: Implement Stage 3 specific rollback logic + + // TODO: Record the rollback on the log. Implement RollbackLogEntry + this.log.debug("Persisting rollback log entry"); + + this.log.info(`Successfully rolled back Stage 3`); + return true; + } catch (error) { + this.log.error(`Failed to rollback Stage 3: ${error}`); + return false; + } + } + + async cleanup( + session: SATPSession, + state: RollbackState, + ): Promise { + const fnTag = "Stage3RollbackStrategy#cleanup"; + this.log.info(`${fnTag} Cleaning up after Stage 3 rollback`); + + if (!session) { + this.log.error(`${fnTag} Session not found`); + return state; + } + + try { + // TODO: Implement Stage 3 specific cleanup logic + + state.currentStage = "Stage3"; + // TODO: Update other state properties as needed + + return state; + } catch (error) { + this.log.error(`${fnTag} Cleanup failed: ${error}`); + return state; + } + } +} diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/generated/SATPWrapperContract.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/generated/SATPWrapperContract.ts index b1f2d9df5b9..ef157b07151 100755 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/generated/SATPWrapperContract.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/generated/SATPWrapperContract.ts @@ -1,12 +1,12 @@ -import BN from 'bn.js'; -import BigNumber from 'bignumber.js'; +import BN from "bn.js"; +import BigNumber from "bignumber.js"; import { PromiEvent, TransactionReceipt, EventResponse, EventData, Web3ContractContext, -} from 'ethereum-abi-types-generator'; +} from "ethereum-abi-types-generator"; export interface CallOptions { from?: string; @@ -31,12 +31,12 @@ export interface MethodPayableReturnContext { send(options: SendOptions): PromiEvent; send( options: SendOptions, - callback: (error: Error, result: any) => void + callback: (error: Error, result: any) => void, ): PromiEvent; estimateGas(options: EstimateGasOptions): Promise; estimateGas( options: EstimateGasOptions, - callback: (error: Error, result: any) => void + callback: (error: Error, result: any) => void, ): Promise; encodeABI(): string; } @@ -46,7 +46,7 @@ export interface MethodConstantReturnContext { call(options: CallOptions): Promise; call( options: CallOptions, - callback: (error: Error, result: TCallReturn) => void + callback: (error: Error, result: TCallReturn) => void, ): Promise; encodeABI(): string; } @@ -60,60 +60,60 @@ export type ContractContext = Web3ContractContext< SATPWrapperContractEvents >; export type SATPWrapperContractEvents = - | 'Assign' - | 'Burn' - | 'Changed' - | 'Lock' - | 'Mint' - | 'OwnershipTransferred' - | 'Unlock' - | 'Unwrap' - | 'Wrap'; + | "Assign" + | "Burn" + | "Changed" + | "Lock" + | "Mint" + | "OwnershipTransferred" + | "Unlock" + | "Unwrap" + | "Wrap"; export interface SATPWrapperContractEventsContext { Assign( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Burn( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Changed( parameters: { filter?: { id?: string | string[] }; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Lock( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Mint( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; OwnershipTransferred( parameters: { @@ -122,57 +122,57 @@ export interface SATPWrapperContractEventsContext { newOwner?: string | string[]; }; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Unlock( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Unwrap( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; Wrap( parameters: { filter?: {}; fromBlock?: number; - toBlock?: 'latest' | number; + toBlock?: "latest" | number; topics?: string[]; }, - callback?: (error: Error, event: EventData) => void + callback?: (error: Error, event: EventData) => void, ): EventResponse; } export type SATPWrapperContractMethodNames = - | 'new' - | 'assign' - | 'bridge_address' - | 'burn' - | 'getAllAssetsIDs' - | 'getToken' - | 'lock' - | 'mint' - | 'owner' - | 'renounceOwnership' - | 'tokens' - | 'tokensInteractions' - | 'transferOwnership' - | 'unlock' - | 'unwrap' - | 'wrap' - | 'wrap'; + | "new" + | "assign" + | "bridge_address" + | "burn" + | "getAllAssetsIDs" + | "getToken" + | "lock" + | "mint" + | "owner" + | "renounceOwnership" + | "tokens" + | "tokensInteractions" + | "transferOwnership" + | "unlock" + | "unwrap" + | "wrap" + | "wrap"; export interface TokenResponse { contractAddress: string; tokenType: string; @@ -243,7 +243,7 @@ export interface SATPWrapperContract { * Type: constructor * @param _bridge_address Type: address, Indexed: false */ - 'new'(_bridge_address: string): MethodReturnContext; + "new"(_bridge_address: string): MethodReturnContext; /** * Payable: false * Constant: false @@ -256,7 +256,7 @@ export interface SATPWrapperContract { assign( tokenId: string, receiver_account: string, - amount: string + amount: string, ): MethodReturnContext; /** * Payable: false @@ -339,7 +339,7 @@ export interface SATPWrapperContract { */ tokensInteractions( parameter0: string, - parameter1: string | number + parameter1: string | number, ): MethodConstantReturnContext; /** * Payable: false @@ -382,7 +382,7 @@ export interface SATPWrapperContract { tokenType: string | number, tokenId: string, owner: string, - interactions: InteractionsRequest[] + interactions: InteractionsRequest[], ): MethodReturnContext; /** * Payable: false @@ -398,6 +398,6 @@ export interface SATPWrapperContract { contractAddress: string, tokenType: string | number, tokenId: string, - owner: string + owner: string, ): MethodReturnContext; } diff --git a/packages/cactus-plugin-satp-hermes/src/main/typescript/plugin-satp-hermes-gateway.ts b/packages/cactus-plugin-satp-hermes/src/main/typescript/plugin-satp-hermes-gateway.ts index c0b71257dd2..29bca6e039d 100644 --- a/packages/cactus-plugin-satp-hermes/src/main/typescript/plugin-satp-hermes-gateway.ts +++ b/packages/cactus-plugin-satp-hermes/src/main/typescript/plugin-satp-hermes-gateway.ts @@ -53,6 +53,11 @@ import { ISATPBridgesOptions, SATPBridgesManager, } from "./gol/satp-bridges-manager"; +import { + CrashOcurrence, + CrashStatusManager, + ICrashRecoveryManagerOptions, +} from "./core/recovery/crash-manager"; export class SATPGateway implements IPluginWebService, ICactusPlugin { // todo more checks; example port from config is between 3000 and 9000 @@ -88,6 +93,7 @@ export class SATPGateway implements IPluginWebService, ICactusPlugin { public localRepository?: ILocalLogRepository; public remoteRepository?: IRemoteLogRepository; private readonly shutdownHooks: ShutdownHook[]; + private readonly crashManager: CrashStatusManager; constructor(public readonly options: SATPGatewayConfig) { const fnTag = `${this.className}#constructor()`; @@ -170,6 +176,14 @@ export class SATPGateway implements IPluginWebService, ICactusPlugin { if (!this.OAS) { this.logger.warn("Error loading OAS"); } + + // After setup, initialize crash manager and check if we crashed; + const crashOptions: ICrashRecoveryManagerOptions = { + instanceId: this.instanceId, + logLevel: this.config.logLevel, + }; + this.crashManager = new CrashStatusManager(); + this.crashManager.checkAndResolveCrash(); } /* ICactus Plugin methods */