diff --git a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/keyRotation/Net_KeyRotationFinishChain_Handler.java b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/keyRotation/Net_KeyRotationFinishChain_Handler.java index c4c5779d..e76f87e4 100644 --- a/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/keyRotation/Net_KeyRotationFinishChain_Handler.java +++ b/SHiNE-server/shine-server-net-protocol/src/main/java/server/logic/ws_protocol/JSON/handlers/keyRotation/Net_KeyRotationFinishChain_Handler.java @@ -19,6 +19,8 @@ import shine.db.entities.BlockEntry; import shine.db.entities.KeyRotationCandidateBlockEntry; import shine.db.entities.KeyRotationSessionEntry; import shine.db.entities.KeyRotationStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.sql.Connection; import java.util.Arrays; @@ -31,6 +33,8 @@ import java.util.concurrent.locks.ReentrantLock; * затем переводит ротацию COPYING_CHAIN -> CHAIN_READY. */ public final class Net_KeyRotationFinishChain_Handler implements JsonMessageHandler { + private static final Logger log = LoggerFactory.getLogger(Net_KeyRotationFinishChain_Handler.class); + private final KeyRotationSessionsDAO rotations = KeyRotationSessionsDAO.getInstance(); private final KeyRotationCandidateBlocksDAO candidates = KeyRotationCandidateBlocksDAO.getInstance(); private final BlocksDAO blocks = BlocksDAO.getInstance(); @@ -103,6 +107,8 @@ public final class Net_KeyRotationFinishChain_Handler implements JsonMessageHand rotations.clearError(initial.getId()); return KeyRotationApiSupport.response(req.getOp(), req.getRequestId(), ready); } catch (Exception transitionFailure) { + log.warn("KeyRotationFinishChain transition failed: login={}, sessionId={}", + login, initial.getId(), transitionFailure); // Идемпотентность для параллельного FinishChain из другой сессии. try { KeyRotationSessionEntry now = rotations.getActiveByLogin(login); diff --git a/shine-UI/js/services/key-rotation-service.js b/shine-UI/js/services/key-rotation-service.js index 71e088fe..4d19f5ab 100644 --- a/shine-UI/js/services/key-rotation-service.js +++ b/shine-UI/js/services/key-rotation-service.js @@ -79,6 +79,11 @@ export class KeyRotationClient { const cutoff=Number(rotation.forkFromBlock); const source=String(rotation.sourceBlockchainName||''); if(!source || !Number.isInteger(cutoff) || cutoff<0) throw new Error('Некорректное состояние ротации'); if(String(newBundle?.blockchainPair?.publicKeyB64||'') !== String(rotation.newBlockchainKey||'')) throw new Error('Новый пароль выводит другой blockchain key'); + const total=cutoff+2; + if(Number(rotation.progressCurrent)||0 >= total) { + onProgress({ phase:'copy', current:total, total }); + return; + } const owner32=base64ToBytes(newBundle.blockchainPair.publicKeyB64); const privateKey=newBundle.blockchainPair.privateKey; let start=Math.max(0, Math.min(cutoff+1, Number(rotation.progressCurrent)||0)); @@ -106,10 +111,11 @@ export class KeyRotationClient { reasonCode:rotation.reasonCode, comment:rotation.comment, }); - const techFrame=makeFrame({ prevHash:rotation.forkFromHash, blockNumber:cutoff+1, type:0, subType:TECH_FORK_SUBTYPE, version:1, body:forkBody }); + const techTimestampMs=Number(rotation.createdAtMs)||Number(parentTipTimestampMs)+1000; + const techFrame=makeFrame({ prevHash:rotation.forkFromHash, blockNumber:cutoff+1, type:0, subType:TECH_FORK_SUBTYPE, version:1, body:forkBody, timestampMs:techTimestampMs }); const techRaw=await createAns104DataItem({ owner32, privateKey, data:techFrame, tags:[{name:'App',value:'test5590'}] }); await this.addCandidate({ blockNumber:cutoff+1, prevBlockHash:rotation.forkFromHash, blockBytesB64:bytesToBase64(techRaw) }); - onProgress({ phase:'copy', current:cutoff+2, total:cutoff+2 }); + onProgress({ phase:'copy', current:total, total }); } async #sourcePrevHash(source, blockNumber) {