{"record":{"id":"ce792a868f3e65ab","repo":"diem/diem","slug":"previous-epoch-ending-ledgerinfo-doesn-t-end-an-ep","errorCode":null,"errorMessage":"Previous epoch ending LedgerInfo doesn't end an epoch","messagePattern":"Previous epoch ending LedgerInfo doesn't end an epoch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"storage/backup/backup-cli/src/backup_types/epoch_ending/restore.rs","lineNumber":236,"sourceCode":"        if let Some(li) = previous_epoch_ending_ledger_info {\n            ensure!(\n                li.next_block_epoch() == preheat_data.manifest.first_epoch,\n                \"Previous epoch ending LedgerInfo is not the one expected. \\\n                My first epoch: {}, previous LedgerInfo next_block_epoch: {}\",\n                preheat_data.manifest.first_epoch,\n                li.next_block_epoch(),\n            );\n            // Waypoint has been verified in preheat if it's trusted, otherwise try to check\n            // the signatures.\n            if self\n                .controller\n                .trusted_waypoints\n                .get(&first_li.ledger_info().version())\n                .is_none()\n            {\n                li.next_epoch_state()\n                    .ok_or_else(|| {\n                        anyhow!(\"Previous epoch ending LedgerInfo doesn't end an epoch\")\n                    })?\n                    .verify(first_li)?;\n            }\n        }\n\n        let last_li = preheat_data\n            .ledger_infos\n            .last()\n            .expect(\"Verified not empty.\")\n            .ledger_info();\n        match self.controller.run_mode.as_ref() {\n            RestoreRunMode::Restore { restore_handler } => {\n                restore_handler.save_ledger_infos(&preheat_data.ledger_infos)?;\n\n                EPOCH_ENDING_EPOCH.set(last_li.epoch() as i64);\n                EPOCH_ENDING_VERSION.set(last_li.version() as i64);\n            }\n            RestoreRunMode::Verify => {","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/diem/diem/blob/fc4714a8ea273b6efe8b13dbce72ea60aad9a16c/storage/backup/backup-cli/src/backup_types/epoch_ending/restore.rs#L218-L254","documentation":"When the waypoint formed by the backup's first LedgerInfo is not in the trusted waypoints set, the restore tries to cryptographically verify it against the previous LedgerInfo's `next_epoch_state`. That call requires the previous LedgerInfo to actually end an epoch (carry a next-epoch state); if it doesn't, this error is thrown. It means the supplied `previous_epoch_ending_ledger_info` is not an epoch-ending ledger info and cannot serve as a verification anchor.","triggerScenarios":"`run` called with a `previous_epoch_ending_ledger_info` whose `next_epoch_state()` returns None (it's a mid-epoch LedgerInfo, not an epoch change proof), and the first backup LedgerInfo's version is not in `trusted_waypoints`.","commonSituations":"Passing a regular (non-epoch-ending) LedgerInfo fetched from a running node as the previous ledger info; the previous restore checkpoint was truncated mid-epoch; confusing ledger infos from `get_ledger_info` with epoch-ending ones.","solutions":["Ensure `previous_epoch_ending_ledger_info` comes from the end of an epoch-ending backup (or a trusted waypoint), not a live node's current ledger info.","Alternatively add the backup's first LedgerInfo version to the `trusted_waypoints` map so signature verification (and thus the next_epoch_state requirement) is skipped.","Restore the missing preceding epoch-ending chunk so you have a genuine epoch-ending LedgerInfo to pass.","Verify the checkpoint you load the previous LI from actually corresponds to an epoch boundary (check `next_epoch_state` is Some before calling run)."],"exampleFix":"// before: passing a mid-epoch ledger info as the anchor\nlet prev = client.get_ledger_info().await?;\nrestore.run(Some(&prev)).await?;\n// after: require an epoch-ending ledger info or use None\nlet prev = client.get_ledger_info().await?;\nlet anchor = if prev.next_epoch_state().is_some() { Some(&prev) } else { None };\nrestore.run(anchor).await?;","handlingStrategy":"validation","validationCode":"// before calling run, ensure the anchor is an epoch-ending ledger info\n// or register the backup waypoint as trusted\nif let Some(li) = &prev_li {\n    ensure!(li.next_epoch_state().is_some(), \"previous LI must end an epoch\");\n}\n// alternatively: trusted_waypoints.insert(first_li_version, first_li.clone());","typeGuard":"fn is_epoch_ending(li: &LedgerInfo) -> bool {\n    li.next_epoch_state().is_some()\n}","tryCatchPattern":"match restore.run(Some(&prev_li)).await {\n    Err(e) if e.to_string().contains(\"doesn't end an epoch\") => {\n        // switch to a genuine epoch-ending anchor or a trusted waypoint\n        Err(anyhow!(\"invalid anchor: {}\", e))\n    }\n    other => other,\n}","preventionTips":["Only pass LedgerInfos taken from epoch-ending backups (or trusted waypoints) as the previous ledger info.","If trusting the backup's first LI, register its version in `trusted_waypoints` to skip signature verification.","Never use a live node's current ledger info as an epoch-ending anchor.","When in doubt, call `run(None)` (fresh restore from epoch 0) instead of an incremental restore."],"tags":["backup-restore","ledger-info","signature-verification","waypoint"],"backgroundTag":"not-epoch-ending-ledger-info","analyzedSha":"fc4714a8ea273b6efe8b13dbce72ea60aad9a16c","analyzedAt":"2026-09-04T21:07:05.890Z","contentChangedAt":"2026-09-04T21:07:05.890Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}