use super::{
capability::{DbRead, DbVersion, DbWrite, MigrationStatus},
router::Router,
};
use crate::{
chain_index::{
finalised_state::{
capability::DbMetadata,
entry::{StoredEntryFixed, StoredEntryVar},
finalised_source::v1::SYNC_CHECKPOINT_INTERVAL,
router::EphemeralMode,
},
source::BlockchainSource,
types::GENESIS_HEIGHT,
},
config::ChainIndexConfig,
error::FinalisedStateError,
CommitmentTreeData, Height, TransparentTxList, TxLocation, TxidList, ZainoVersionedSerde as _,
};
use lmdb::{Transaction, WriteFlags};
use crate::SendFut;
use std::sync::Arc;
use tracing::info;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MigrationType {
Patch,
Minor,
Major,
}
pub trait Migration<T: BlockchainSource> {
const CURRENT_VERSION: DbVersion;
const TO_VERSION: DbVersion;
fn current_version(&self) -> DbVersion {
Self::CURRENT_VERSION
}
fn to_version(&self) -> DbVersion {
Self::TO_VERSION
}
fn migration_type(&self) -> MigrationType {
MigrationType::Patch
}
fn migrate(
&self,
router: Arc<Router<T>>,
_cfg: ChainIndexConfig,
_source: T,
) -> impl SendFut<Result<(), FinalisedStateError>> {
async move {
info!(
from = %Self::CURRENT_VERSION,
to = %Self::TO_VERSION,
"starting metadata-only migration"
);
let mut metadata: DbMetadata = router.get_metadata().await?;
metadata.version = Self::TO_VERSION;
metadata.schema_hash =
crate::chain_index::finalised_state::finalised_source::v1::DB_SCHEMA_V1_HASH;
metadata.migration_status = MigrationStatus::Empty;
router.update_metadata(metadata).await?;
info!(
from = %Self::CURRENT_VERSION,
to = %Self::TO_VERSION,
"metadata-only migration complete"
);
Ok(())
}
}
}
pub(super) struct MigrationManager<T: BlockchainSource> {
pub(super) router: Arc<Router<T>>,
pub(super) cfg: ChainIndexConfig,
pub(super) current_version: DbVersion,
pub(super) target_version: DbVersion,
pub(super) source: T,
}
impl<T: BlockchainSource> MigrationManager<T> {
pub(super) async fn migrate(&mut self) -> Result<(), FinalisedStateError> {
while self.current_version < self.target_version {
let migration = self.get_migration()?;
let migration_type = migration.migration_type::<T>();
match migration_type {
MigrationType::Patch => {
migration
.migrate(
Arc::clone(&self.router),
self.cfg.clone(),
self.source.clone(),
)
.await?;
}
MigrationType::Minor | MigrationType::Major => {
let primary = self.router.primary_backend();
let db_height = primary.db_height().await?;
let _ephemeral_reference = self
.router
.init_or_take_ephemeral(
self.source.clone(),
self.cfg.network.clone(),
EphemeralMode::Full,
db_height,
)
.await?;
migration
.migrate(
Arc::clone(&self.router),
self.cfg.clone(),
self.source.clone(),
)
.await?;
}
}
self.current_version = migration.to_version::<T>();
}
Ok(())
}
fn get_migration(&self) -> Result<MigrationStep, FinalisedStateError> {
match (
self.current_version.major,
self.current_version.minor,
self.current_version.patch,
) {
(1, 0, 0) => Ok(MigrationStep::Migration1_0_0To1_1_0(Migration1_0_0To1_1_0)),
(1, 1, 0) => Ok(MigrationStep::Migration1_1_0To1_2_0(Migration1_1_0To1_2_0)),
(1, 2, 0) => Ok(MigrationStep::Migration1_2_0To1_2_1(Migration1_2_0To1_2_1)),
(1, 2, 1) => Ok(MigrationStep::Migration1_2_1To1_3_0(Migration1_2_1To1_3_0)),
(_, _, _) => Err(FinalisedStateError::Custom(format!(
"Missing migration from version {}",
self.current_version
))),
}
}
}
enum MigrationStep {
Migration1_0_0To1_1_0(Migration1_0_0To1_1_0),
Migration1_1_0To1_2_0(Migration1_1_0To1_2_0),
Migration1_2_0To1_2_1(Migration1_2_0To1_2_1),
Migration1_2_1To1_3_0(Migration1_2_1To1_3_0),
}
impl MigrationStep {
fn to_version<T: BlockchainSource>(&self) -> DbVersion {
match self {
MigrationStep::Migration1_0_0To1_1_0(_step) => {
<Migration1_0_0To1_1_0 as Migration<T>>::TO_VERSION
}
MigrationStep::Migration1_1_0To1_2_0(_step) => {
<Migration1_1_0To1_2_0 as Migration<T>>::TO_VERSION
}
MigrationStep::Migration1_2_0To1_2_1(_step) => {
<Migration1_2_0To1_2_1 as Migration<T>>::TO_VERSION
}
MigrationStep::Migration1_2_1To1_3_0(_step) => {
<Migration1_2_1To1_3_0 as Migration<T>>::TO_VERSION
}
}
}
fn migration_type<T: BlockchainSource>(&self) -> MigrationType {
match self {
MigrationStep::Migration1_0_0To1_1_0(step) => {
<Migration1_0_0To1_1_0 as Migration<T>>::migration_type(step)
}
MigrationStep::Migration1_1_0To1_2_0(step) => {
<Migration1_1_0To1_2_0 as Migration<T>>::migration_type(step)
}
MigrationStep::Migration1_2_0To1_2_1(step) => {
<Migration1_2_0To1_2_1 as Migration<T>>::migration_type(step)
}
MigrationStep::Migration1_2_1To1_3_0(step) => {
<Migration1_2_1To1_3_0 as Migration<T>>::migration_type(step)
}
}
}
async fn migrate<T: BlockchainSource>(
&self,
router: Arc<Router<T>>,
cfg: ChainIndexConfig,
source: T,
) -> Result<(), FinalisedStateError> {
match self {
MigrationStep::Migration1_0_0To1_1_0(step) => step.migrate(router, cfg, source).await,
MigrationStep::Migration1_1_0To1_2_0(step) => step.migrate(router, cfg, source).await,
MigrationStep::Migration1_2_0To1_2_1(step) => step.migrate(router, cfg, source).await,
MigrationStep::Migration1_2_1To1_3_0(step) => step.migrate(router, cfg, source).await,
}
}
}
struct Migration1_0_0To1_1_0;
impl<T: BlockchainSource> Migration<T> for Migration1_0_0To1_1_0 {
const CURRENT_VERSION: DbVersion = DbVersion {
major: 1,
minor: 0,
patch: 0,
};
const TO_VERSION: DbVersion = DbVersion {
major: 1,
minor: 1,
patch: 0,
};
}
struct Migration1_1_0To1_2_0;
fn put_idempotent(
txn: &mut lmdb::RwTransaction<'_>,
db: lmdb::Database,
key: &[u8],
value: &[u8],
describe: impl FnOnce() -> String,
) -> Result<(), FinalisedStateError> {
match txn.put(db, &key, &value, WriteFlags::NO_OVERWRITE) {
Ok(()) => Ok(()),
Err(lmdb::Error::KeyExist) => {
let existing = txn.get(db, &key).map_err(FinalisedStateError::LmdbError)?;
if existing == value {
Ok(())
} else {
Err(FinalisedStateError::Custom(describe()))
}
}
Err(error) => Err(FinalisedStateError::LmdbError(error)),
}
}
fn flush_migration_spent_batch(
env: &lmdb::Environment,
spent_db: lmdb::Database,
metadata_db: lmdb::Database,
progress_key: &[u8],
buffer: &mut Vec<(Vec<u8>, TxLocation)>,
up_to_height: Height,
) -> Result<(), FinalisedStateError> {
buffer.sort_by(|a, b| a.0.cmp(&b.0));
let mut txn = env.begin_rw_txn()?;
for (outpoint_bytes, tx_location) in buffer.iter() {
let entry_bytes = StoredEntryFixed::new(outpoint_bytes, *tx_location).to_bytes()?;
put_idempotent(&mut txn, spent_db, outpoint_bytes, &entry_bytes, || {
format!(
"conflicting existing spent entry during batched migration for outpoint {}",
hex::encode(outpoint_bytes)
)
})?;
}
let progress = StoredEntryFixed::new(progress_key, up_to_height + 1);
txn.put(
metadata_db,
&progress_key,
&progress.to_bytes()?,
WriteFlags::empty(),
)?;
txn.commit()?;
env.sync(true)?;
buffer.clear();
Ok(())
}
impl<T: BlockchainSource> Migration<T> for Migration1_1_0To1_2_0 {
const CURRENT_VERSION: DbVersion = DbVersion {
major: 1,
minor: 1,
patch: 0,
};
const TO_VERSION: DbVersion = DbVersion {
major: 1,
minor: 2,
patch: 0,
};
fn migration_type(&self) -> MigrationType {
MigrationType::Minor
}
async fn migrate(
&self,
router: Arc<Router<T>>,
cfg: ChainIndexConfig,
_source: T,
) -> Result<(), FinalisedStateError> {
const MIGRATION_TXID_LOCATION_PROGRESS_KEY: &[u8] =
b"_migration_txid_location_progress_1_2_0_next_height";
const MIGRATION_SPENT_PROGRESS_KEY: &[u8] = b"_migration_spent_progress_1_2_0_next_height";
info!("Starting v1.1.0 → v1.2.0 migration.");
let backend = router.primary_backend();
let env = backend.env()?;
let metadata_db = backend.metadata_db()?;
let txids_db = backend.txids_db()?;
let transparent_db = backend.transparent_db()?;
let spent_db = backend.spent_db()?;
let txid_location_db = backend.txid_location_db()?;
{
let mut metadata: DbMetadata = backend.get_metadata().await?;
if metadata.migration_status == MigrationStatus::Empty {
metadata.migration_status = MigrationStatus::PartialBuidInProgress;
backend.update_metadata(metadata).await?;
}
}
let read_progress = |key: &[u8]| -> Result<Option<u32>, FinalisedStateError> {
let txn = env.begin_ro_txn()?;
match txn.get(metadata_db, &key) {
Ok(bytes) => {
let entry = StoredEntryFixed::<Height>::from_bytes(bytes).map_err(|error| {
FinalisedStateError::Custom(format!(
"corrupt v1.2.0 migration progress entry: {error}"
))
})?;
if !entry.verify(key) {
return Err(FinalisedStateError::Custom(
"v1.2.0 migration progress checksum mismatch".to_string(),
));
}
Ok(Some(entry.inner().0))
}
Err(lmdb::Error::NotFound) => Ok(None),
Err(error) => Err(FinalisedStateError::LmdbError(error)),
}
};
if let Some(db_tip) = backend.db_height().await? {
let db_tip = db_tip.0;
let mut next_height =
read_progress(MIGRATION_TXID_LOCATION_PROGRESS_KEY)?.unwrap_or(GENESIS_HEIGHT.0);
info!(
resume_height = next_height,
db_tip, "v1.2.0 migration Stage A: building txid_location index"
);
let stage_a_started = std::time::Instant::now();
while next_height <= db_tip {
let height = Height::try_from(next_height)
.map_err(|error| FinalisedStateError::Custom(error.to_string()))?;
let height_bytes = height.to_bytes()?;
let txids = {
let txn = env.begin_ro_txn()?;
let raw = txn
.get(txids_db, &height_bytes)
.map_err(FinalisedStateError::LmdbError)?;
let entry = StoredEntryVar::<TxidList>::from_bytes(raw).map_err(|error| {
FinalisedStateError::Custom(format!("txids corrupt data: {error}"))
})?;
if !entry.verify(&height_bytes) {
return Err(FinalisedStateError::Custom(
"txids checksum mismatch".to_string(),
));
}
entry.inner().txids().to_vec()
};
let mut entries: Vec<([u8; 32], TxLocation)> = Vec::with_capacity(txids.len());
for (tx_index, txid) in txids.iter().enumerate() {
let tx_index = u16::try_from(tx_index).map_err(|_| {
FinalisedStateError::Custom(format!(
"transaction index out of range at height {}",
height.0
))
})?;
entries.push(((*txid).into(), TxLocation::new(height.0, tx_index)));
}
entries.sort_by_key(|entry| entry.0);
{
let mut txn = env.begin_rw_txn()?;
for (txid_bytes, tx_location) in &entries {
let entry_bytes =
StoredEntryFixed::new(txid_bytes, *tx_location).to_bytes()?;
put_idempotent(
&mut txn,
txid_location_db,
txid_bytes,
&entry_bytes,
|| {
format!(
"conflicting or corrupt existing txid_location entry at height {}",
height.0
)
},
)?;
}
let progress =
StoredEntryFixed::new(MIGRATION_TXID_LOCATION_PROGRESS_KEY, height + 1);
txn.put(
metadata_db,
&MIGRATION_TXID_LOCATION_PROGRESS_KEY,
&progress.to_bytes()?,
WriteFlags::empty(),
)?;
txn.commit()?;
}
if next_height % SYNC_CHECKPOINT_INTERVAL == 0 {
env.sync(true)?;
}
if next_height % 50_000 == 0 {
info!(
height = next_height,
db_tip,
elapsed = ?stage_a_started.elapsed(),
"v1.2.0 migration Stage A progress"
);
}
next_height = height.0 + 1;
}
env.sync(true)?;
info!(
db_tip,
elapsed = ?stage_a_started.elapsed(),
"v1.2.0 migration Stage A complete"
);
let mut next_height_to_migrate = match read_progress(MIGRATION_SPENT_PROGRESS_KEY)? {
Some(height) => height,
None => {
let mut txn = env.begin_rw_txn()?;
let progress =
StoredEntryFixed::new(MIGRATION_SPENT_PROGRESS_KEY, GENESIS_HEIGHT);
txn.put(
metadata_db,
&MIGRATION_SPENT_PROGRESS_KEY,
&progress.to_bytes()?,
WriteFlags::empty(),
)?;
txn.commit()?;
GENESIS_HEIGHT.0
}
};
let db_tip = router
.db_height()
.await?
.map(|height| height.0)
.unwrap_or(db_tip);
info!(
resume_height = next_height_to_migrate,
db_tip, "v1.2.0 migration Stage B: backfilling spent index"
);
let stage_b_started = std::time::Instant::now();
let batch_budget =
(cfg.storage.database.sync_write_batch_size.to_byte_count() as u64).max(1);
let mut spent_buffer: Vec<(Vec<u8>, TxLocation)> = Vec::new();
let mut spent_buffer_bytes: u64 = 0;
while next_height_to_migrate <= db_tip {
let height = Height::try_from(next_height_to_migrate)
.map_err(|error| FinalisedStateError::Custom(error.to_string()))?;
let height_bytes = height.to_bytes()?;
let transparent_tx_list = {
let txn = env.begin_ro_txn()?;
let raw = txn
.get(transparent_db, &height_bytes)
.map_err(FinalisedStateError::LmdbError)?;
let entry =
StoredEntryVar::<TransparentTxList>::from_bytes(raw).map_err(|error| {
FinalisedStateError::Custom(format!(
"transparent corrupt data: {error}"
))
})?;
if !entry.verify(&height_bytes) {
return Err(FinalisedStateError::Custom(
"transparent checksum mismatch".to_string(),
));
}
entry.inner().clone()
};
let transparent = transparent_tx_list.tx().to_vec();
let mut spent_map = std::collections::HashMap::new();
for (tx_index, tx_opt) in transparent.iter().enumerate() {
let Some(transparent_tx) = tx_opt else {
continue;
};
let tx_index = u16::try_from(tx_index).map_err(|_| {
FinalisedStateError::Custom(format!(
"transaction index out of range at height {}",
height.0
))
})?;
let tx_location = TxLocation::new(height.0, tx_index);
for outpoint in transparent_tx.spent_outpoints() {
if spent_map.insert(outpoint, tx_location).is_some() {
return Err(FinalisedStateError::Custom(format!(
"duplicate transparent spend for outpoint {:?} at height {}",
outpoint, height.0
)));
}
}
}
for (outpoint, tx_location) in &spent_map {
let outpoint_bytes = outpoint.to_bytes()?;
spent_buffer_bytes =
spent_buffer_bytes.saturating_add(outpoint_bytes.len() as u64 + 64);
spent_buffer.push((outpoint_bytes, *tx_location));
}
if spent_buffer_bytes >= batch_budget {
flush_migration_spent_batch(
&env,
spent_db,
metadata_db,
MIGRATION_SPENT_PROGRESS_KEY,
&mut spent_buffer,
height,
)?;
spent_buffer_bytes = 0;
}
if next_height_to_migrate % 10_000 == 0 {
info!(
height = next_height_to_migrate,
db_tip,
elapsed = ?stage_b_started.elapsed(),
"v1.2.0 migration Stage B progress"
);
}
next_height_to_migrate = height.0 + 1;
}
if !spent_buffer.is_empty() {
let tip_height = Height::try_from(db_tip)
.map_err(|error| FinalisedStateError::Custom(error.to_string()))?;
flush_migration_spent_batch(
&env,
spent_db,
metadata_db,
MIGRATION_SPENT_PROGRESS_KEY,
&mut spent_buffer,
tip_height,
)?;
}
info!(
db_tip,
elapsed = ?stage_b_started.elapsed(),
"v1.2.0 migration Stage B complete"
);
backend.run_v1_2_migration_accumulator_stage(db_tip).await?;
}
env.sync(true)?;
let mut metadata: DbMetadata = backend.get_metadata().await?;
metadata.version = <Self as Migration<T>>::TO_VERSION;
metadata.schema_hash =
crate::chain_index::finalised_state::finalised_source::v1::DB_SCHEMA_V1_HASH;
metadata.migration_status = MigrationStatus::Empty;
backend.update_metadata(metadata).await?;
env.sync(true)?;
{
let mut txn = env.begin_rw_txn()?;
for key in [
MIGRATION_TXID_LOCATION_PROGRESS_KEY,
MIGRATION_SPENT_PROGRESS_KEY,
] {
match txn.del(metadata_db, &key, None) {
Ok(()) | Err(lmdb::Error::NotFound) => {}
Err(error) => return Err(FinalisedStateError::LmdbError(error)),
}
}
txn.commit()?;
}
env.sync(true)?;
info!("v1.1.0 to v1.2.0 migration complete.");
Ok(())
}
}
struct Migration1_2_0To1_2_1;
impl<T: BlockchainSource> Migration<T> for Migration1_2_0To1_2_1 {
const CURRENT_VERSION: DbVersion = DbVersion {
major: 1,
minor: 2,
patch: 0,
};
const TO_VERSION: DbVersion = DbVersion {
major: 1,
minor: 2,
patch: 1,
};
}
struct Migration1_2_1To1_3_0;
impl<T: BlockchainSource> Migration<T> for Migration1_2_1To1_3_0 {
const CURRENT_VERSION: DbVersion = DbVersion {
major: 1,
minor: 2,
patch: 1,
};
const TO_VERSION: DbVersion = DbVersion {
major: 1,
minor: 3,
patch: 0,
};
fn migration_type(&self) -> MigrationType {
MigrationType::Minor
}
async fn migrate(
&self,
router: Arc<Router<T>>,
cfg: ChainIndexConfig,
source: T,
) -> Result<(), FinalisedStateError> {
use lmdb::DatabaseFlags;
use crate::chain_index::finalised_state::{
build_indexed_block_from_source,
finalised_source::v1::write_core::build_block_ironwood_entry, PoolActivationHeights,
};
const MIGRATION_CTD_PROGRESS_KEY: &[u8] =
b"_migration_commitment_tree_data_progress_1_3_0_next_height";
info!("Starting v1.2.1 → v1.3.0 migration (Ironwood).");
let backend = router.primary_backend();
let env = backend.env()?;
let metadata_db = backend.metadata_db()?;
let new_ctd_db = backend.commitment_tree_data_db()?;
let ironwood_db = backend.ironwood_db()?;
let legacy_ctd_db =
crate::chain_index::finalised_state::finalised_source::open_or_create_db(
&env,
"commitment_tree_data_1_0_0",
DatabaseFlags::empty(),
)
.await?;
let network = cfg.network.clone();
let pool_activations = PoolActivationHeights::resolve(&network);
let sapling_activation_height = pool_activations.sapling;
let nu5_activation_height = pool_activations.nu5;
let nu6_3_activation_height = pool_activations.nu6_3;
{
let mut metadata: DbMetadata = backend.get_metadata().await?;
if metadata.migration_status == MigrationStatus::Empty {
metadata.migration_status = MigrationStatus::PartialBuidInProgress;
backend.update_metadata(metadata).await?;
}
}
let read_progress = |key: &[u8]| -> Result<Option<u32>, FinalisedStateError> {
let txn = env.begin_ro_txn()?;
match txn.get(metadata_db, &key) {
Ok(bytes) => {
let entry = StoredEntryFixed::<Height>::from_bytes(bytes).map_err(|error| {
FinalisedStateError::Custom(format!(
"corrupt v1.3.0 migration progress entry: {error}"
))
})?;
if !entry.verify(key) {
return Err(FinalisedStateError::Custom(
"v1.3.0 migration progress checksum mismatch".to_string(),
));
}
Ok(Some(entry.inner().0))
}
Err(lmdb::Error::NotFound) => Ok(None),
Err(error) => Err(FinalisedStateError::LmdbError(error)),
}
};
if let Some(db_tip) = backend.db_height().await? {
let db_tip = db_tip.0;
let mut next_height =
read_progress(MIGRATION_CTD_PROGRESS_KEY)?.unwrap_or(GENESIS_HEIGHT.0);
info!(
resume_height = next_height,
db_tip,
"v1.3.0 migration: rebuilding commitment_tree_data into StoredEntryVar (V2)"
);
let started = std::time::Instant::now();
while next_height <= db_tip {
let height = Height::try_from(next_height)
.map_err(|error| FinalisedStateError::Custom(error.to_string()))?;
let height_bytes = height.to_bytes()?;
let ironwood_active =
nu6_3_activation_height.is_some_and(|activation| next_height >= activation.0);
let commitment_bytes: Vec<u8>;
let ironwood_bytes: Option<Vec<u8>>;
if ironwood_active {
let sapling_activation_height = sapling_activation_height.ok_or_else(|| {
FinalisedStateError::Custom(
"Sapling activation height must be set to backfill ironwood"
.to_string(),
)
})?;
let block = build_indexed_block_from_source(
&source,
network.clone(),
sapling_activation_height,
nu5_activation_height,
nu6_3_activation_height,
next_height,
None,
)
.await
.map_err(|error| {
FinalisedStateError::Custom(format!(
"v1.3.0 ironwood backfill failed at height {next_height}: {error}. \
This backfill refetches every stored block from NU6.3 activation \
through the database tip; ensure the backing validator serves that \
range, or wipe the finalised-state directory and re-index from the \
validator."
))
})?;
commitment_bytes =
StoredEntryVar::new(&height_bytes, *block.commitment_tree_data())
.to_bytes()?;
ironwood_bytes = match build_block_ironwood_entry(&block, &height_bytes)? {
Some(entry) => Some(entry.to_bytes()?),
None => None,
};
} else {
let commitment_tree_data: CommitmentTreeData = {
let txn = env.begin_ro_txn()?;
let raw = txn
.get(legacy_ctd_db, &height_bytes)
.map_err(FinalisedStateError::LmdbError)?;
let entry = StoredEntryFixed::<CommitmentTreeData>::from_bytes(raw)
.map_err(|error| {
FinalisedStateError::Custom(format!(
"legacy commitment_tree_data corrupt data: {error}"
))
})?;
if !entry.verify(&height_bytes) {
return Err(FinalisedStateError::Custom(
"legacy commitment_tree_data checksum mismatch".to_string(),
));
}
*entry.inner()
};
commitment_bytes =
StoredEntryVar::new(&height_bytes, commitment_tree_data).to_bytes()?;
ironwood_bytes = None;
}
{
let mut txn = env.begin_rw_txn()?;
put_idempotent(
&mut txn,
new_ctd_db,
&height_bytes,
&commitment_bytes,
|| {
format!(
"conflicting rebuilt commitment_tree_data at height {}",
height.0
)
},
)?;
if let Some(bytes) = &ironwood_bytes {
put_idempotent(&mut txn, ironwood_db, &height_bytes, bytes, || {
format!("conflicting rebuilt ironwood at height {}", height.0)
})?;
}
let progress = StoredEntryFixed::new(MIGRATION_CTD_PROGRESS_KEY, height + 1);
txn.put(
metadata_db,
&MIGRATION_CTD_PROGRESS_KEY,
&progress.to_bytes()?,
WriteFlags::empty(),
)?;
txn.commit()?;
}
if next_height % SYNC_CHECKPOINT_INTERVAL == 0 {
env.sync(true)?;
}
if next_height % 50_000 == 0 {
info!(
height = next_height,
db_tip,
elapsed = ?started.elapsed(),
"v1.3.0 migration progress"
);
}
next_height = height.0 + 1;
}
env.sync(true)?;
info!(
db_tip,
elapsed = ?started.elapsed(),
"v1.3.0 migration: commitment_tree_data rebuild complete"
);
{
let mut txn = env.begin_rw_txn()?;
txn.clear_db(legacy_ctd_db)?;
txn.commit()?;
}
env.sync(true)?;
}
env.sync(true)?;
let mut metadata: DbMetadata = backend.get_metadata().await?;
metadata.version = <Self as Migration<T>>::TO_VERSION;
metadata.schema_hash =
crate::chain_index::finalised_state::finalised_source::v1::DB_SCHEMA_V1_HASH;
metadata.migration_status = MigrationStatus::Empty;
backend.update_metadata(metadata).await?;
env.sync(true)?;
{
let mut txn = env.begin_rw_txn()?;
match txn.del(metadata_db, &MIGRATION_CTD_PROGRESS_KEY, None) {
Ok(()) | Err(lmdb::Error::NotFound) => {}
Err(error) => return Err(FinalisedStateError::LmdbError(error)),
}
txn.commit()?;
}
env.sync(true)?;
info!("v1.2.1 to v1.3.0 migration complete.");
Ok(())
}
}