Skip to content

Commit 13c5211

Browse files
authored
Fix trusted-waypoint restore history handling (#341)
## Summary - skip ledger-info signature validation for transaction/state restore at or before the highest trusted waypoint - persist ledger pruner progress at the requested ledger history start version during `bootstrap-db` restore - add regression coverage for the pre-waypoint verification bypass ## Why `bootstrap-db` with `--trust-waypoint` was failing in two ways on Movement backups: 1. historical transaction chunk proofs could still hit pre-waypoint validator/signature verification even though the restore should trust data up to the supplied waypoint 2. even after a successful restore, a node booting from a DB restored with `--ledger-history-start-version 0` could expose `oldest_ledger_version` at the snapshot boundary because missing pruner metadata was inferred from the first `VersionData` row ## Validation - `cargo test -p aptos-backup-cli epoch_history_skips_verification_before_highest_trusted_waypoint -- --nocapture` - `cargo test -p aptos-backup-cli trusted_waypoints -- --nocapture` - seeded oneoff Movement testnet backup from genesis through version `36050159` - restored with: - `target/release/aptos-debugger aptos-db restore bootstrap-db --local-fs-dir /private/tmp/movement-testnet-backup-seed.wop5l4 --target-db-dir /private/tmp/movement-testnet-restore-seed-fixed.WAMrR2/db --ledger-history-start-version 0 --target-version 36050159 --trust-waypoint 26050159:45860ebee4fe4b8f945bda1fd9d5d0c03778afd2cfa178a7e1806bd0caa823e4 --replay-all` - started a fullnode on the restored DB and verified: - `GET /v1` reports `oldest_ledger_version = 0` - `GET /v1/transactions/by_version/0` succeeds
1 parent 437d37f commit 13c5211

5 files changed

Lines changed: 78 additions & 2 deletions

File tree

storage/aptosdb/src/backup/restore_handler.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ impl RestoreHandler {
6363
restore_utils::save_ledger_infos(self.aptosdb.ledger_db.metadata_db(), ledger_infos, None)
6464
}
6565

66+
pub fn save_ledger_pruner_progress(&self, version: Version) -> Result<()> {
67+
self.ledger_db.write_pruner_progress(version)
68+
}
69+
6670
pub fn confirm_or_save_frozen_subtrees(
6771
&self,
6872
num_leaves: LeafCount,

storage/backup/backup-cli/src/backup_types/epoch_ending/restore.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ pub struct EpochHistory {
285285
impl EpochHistory {
286286
pub fn verify_ledger_info(&self, li_with_sigs: &LedgerInfoWithSignatures) -> Result<()> {
287287
let epoch = li_with_sigs.ledger_info().epoch();
288+
let version = li_with_sigs.ledger_info().version();
288289
ensure!(!self.epoch_endings.is_empty(), "Empty epoch history.",);
289290
if epoch > self.epoch_endings.len() as u64 {
290291
// TODO(aldenhu): fix this from upper level
@@ -303,7 +304,7 @@ impl EpochHistory {
303304
);
304305
} else if let Some(wp_trusted) = self
305306
.trusted_waypoints
306-
.get(&li_with_sigs.ledger_info().version())
307+
.get(&version)
307308
{
308309
let wp_li = Waypoint::new_any(li_with_sigs.ledger_info());
309310
ensure!(
@@ -312,6 +313,15 @@ impl EpochHistory {
312313
wp_li,
313314
wp_trusted,
314315
);
316+
} else if self
317+
.trusted_waypoints
318+
.keys()
319+
.max()
320+
.is_some_and(|max_wp| version <= *max_wp)
321+
{
322+
// For historical restore, anything up to the highest trusted waypoint is treated as
323+
// trusted data. This keeps transaction/state restore aligned with epoch-ending
324+
// preheat, which already bypasses signature verification for this range.
315325
} else {
316326
self.epoch_endings[epoch as usize - 1]
317327
.next_epoch_state()

storage/backup/backup-cli/src/backup_types/epoch_ending/tests.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@ use crate::{
1818
};
1919
use aptos_backup_service::start_backup_service;
2020
use aptos_config::utils::get_available_port;
21+
use aptos_crypto::hash::HashValue;
2122
use aptos_db::AptosDB;
2223
use aptos_storage_interface::DbReader;
2324
use aptos_temppath::TempPath;
2425
use aptos_types::{
2526
aggregate_signature::AggregateSignature,
26-
ledger_info::LedgerInfoWithSignatures,
27+
block_info::BlockInfo,
28+
ledger_info::{LedgerInfo, LedgerInfoWithSignatures},
2729
proptest_types::{AccountInfoUniverse, LedgerInfoWithSignaturesGen},
2830
waypoint::Waypoint,
2931
};
3032
use proptest::{collection::vec, prelude::*};
3133
use std::{
34+
collections::HashMap,
3235
convert::TryInto,
3336
io::Write,
3437
net::{IpAddr, Ipv4Addr, SocketAddr},
@@ -269,3 +272,47 @@ proptest! {
269272
Runtime::new().unwrap().block_on(test_trusted_waypoints_impl(lis, trusted_waypoints, should_fail_without))
270273
}
271274
}
275+
276+
#[test]
277+
fn epoch_history_skips_verification_before_highest_trusted_waypoint() {
278+
let pre_waypoint_li = LedgerInfoWithSignatures::new(
279+
LedgerInfo::new(
280+
BlockInfo::new(
281+
1,
282+
0,
283+
HashValue::zero(),
284+
HashValue::zero(),
285+
10,
286+
0,
287+
None,
288+
),
289+
HashValue::zero(),
290+
),
291+
AggregateSignature::empty(),
292+
);
293+
let trusted_waypoint_li = LedgerInfoWithSignatures::new(
294+
LedgerInfo::new(
295+
BlockInfo::new(
296+
2,
297+
0,
298+
HashValue::zero(),
299+
HashValue::zero(),
300+
20,
301+
0,
302+
None,
303+
),
304+
HashValue::zero(),
305+
),
306+
AggregateSignature::empty(),
307+
);
308+
309+
let epoch_history = crate::backup_types::epoch_ending::restore::EpochHistory {
310+
epoch_endings: vec![LedgerInfo::dummy(), LedgerInfo::dummy(), LedgerInfo::dummy()],
311+
trusted_waypoints: Arc::new(HashMap::from([(
312+
20,
313+
Waypoint::new_any(trusted_waypoint_li.ledger_info()),
314+
)])),
315+
};
316+
317+
epoch_history.verify_ledger_info(&pre_waypoint_li).unwrap();
318+
}

storage/backup/backup-cli/src/coordinators/restore.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ impl RestoreCoordinator {
135135
COORDINATOR_TARGET_VERSION.set(target_version as i64);
136136
let lhs = self.ledger_history_start_version();
137137

138+
// Make the readable ledger history boundary explicit in the restored DB.
139+
// Otherwise, when these metadata keys are missing, node startup infers
140+
// the ledger pruner progress from the first VersionData row, which is
141+
// typically the state snapshot version rather than the requested lhs.
142+
self.global_opt.run_mode.save_ledger_pruner_progress(lhs)?;
143+
138144
let latest_tree_version = self
139145
.global_opt
140146
.run_mode

storage/backup/backup-cli/src/utils/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,15 @@ impl RestoreRunMode {
262262
}
263263
}
264264

265+
pub fn save_ledger_pruner_progress(&self, version: Version) -> Result<()> {
266+
match self {
267+
RestoreRunMode::Restore { restore_handler } => {
268+
restore_handler.save_ledger_pruner_progress(version)
269+
},
270+
RestoreRunMode::Verify => Ok(()),
271+
}
272+
}
273+
265274
pub fn get_state_snapshot_before(&self, version: Version) -> Option<(Version, HashValue)> {
266275
match self {
267276
RestoreRunMode::Restore { restore_handler } => restore_handler

0 commit comments

Comments
 (0)