@@ -22,6 +22,7 @@ static const char DB_COIN = 'C';
2222static const char DB_COINS = ' c' ;
2323static const char DB_BLOCK_FILES = ' f' ;
2424static const char DB_TXINDEX = ' t' ;
25+ static const char DB_TXINDEX_BLOCK = ' T' ;
2526static const char DB_BLOCK_INDEX = ' b' ;
2627
2728static const char DB_BEST_BLOCK = ' B' ;
@@ -456,3 +457,141 @@ bool TxIndexDB::WriteBestBlock(const CBlockLocator& locator)
456457{
457458 return Write (DB_BEST_BLOCK, locator);
458459}
460+
461+ /*
462+ * Safely persist a transfer of data from the old txindex database to the new one, and compact the
463+ * range of keys updated. This is used internally by MigrateData.
464+ */
465+ static void WriteTxIndexMigrationBatches (TxIndexDB& newdb, CBlockTreeDB& olddb,
466+ CDBBatch& batch_newdb, CDBBatch& batch_olddb,
467+ const std::pair<unsigned char , uint256>& begin_key,
468+ const std::pair<unsigned char , uint256>& end_key)
469+ {
470+ // Sync new DB changes to disk before deleting from old DB.
471+ newdb.WriteBatch (batch_newdb, /* fSync=*/ true );
472+ olddb.WriteBatch (batch_olddb);
473+ olddb.CompactRange (begin_key, end_key);
474+
475+ batch_newdb.Clear ();
476+ batch_olddb.Clear ();
477+ }
478+
479+ bool TxIndexDB::MigrateData (CBlockTreeDB& block_tree_db, const CBlockLocator& best_locator)
480+ {
481+ // The prior implementation of txindex was always in sync with block index
482+ // and presence was indicated with a boolean DB flag. If the flag is set,
483+ // this means the txindex from a previous version is valid and in sync with
484+ // the chain tip. The first step of the migration is to unset the flag and
485+ // write the chain hash to a separate key, DB_TXINDEX_BLOCK. After that, the
486+ // index entries are copied over in batches to the new database. Finally,
487+ // DB_TXINDEX_BLOCK is erased from the old database and the block hash is
488+ // written to the new database.
489+ //
490+ // Unsetting the boolean flag ensures that if the node is downgraded to a
491+ // previous version, it will not see a corrupted, partially migrated index
492+ // -- it will see that the txindex is disabled. When the node is upgraded
493+ // again, the migration will pick up where it left off and sync to the block
494+ // with hash DB_TXINDEX_BLOCK.
495+ bool f_legacy_flag = false ;
496+ block_tree_db.ReadFlag (" txindex" , f_legacy_flag);
497+ if (f_legacy_flag) {
498+ if (!block_tree_db.Write (DB_TXINDEX_BLOCK, best_locator)) {
499+ return error (" %s: cannot write block indicator" , __func__);
500+ }
501+ if (!block_tree_db.WriteFlag (" txindex" , false )) {
502+ return error (" %s: cannot write block index db flag" , __func__);
503+ }
504+ }
505+
506+ CBlockLocator locator;
507+ if (!block_tree_db.Read (DB_TXINDEX_BLOCK, locator)) {
508+ return true ;
509+ }
510+
511+ int64_t count = 0 ;
512+ LogPrintf (" Upgrading txindex database... [0%%]\n " );
513+ uiInterface.ShowProgress (_ (" Upgrading txindex database" ), 0 , true );
514+ int report_done = 0 ;
515+ const size_t batch_size = 1 << 24 ; // 16 MiB
516+
517+ CDBBatch batch_newdb (*this );
518+ CDBBatch batch_olddb (block_tree_db);
519+
520+ std::pair<unsigned char , uint256> key;
521+ std::pair<unsigned char , uint256> begin_key{DB_TXINDEX, uint256 ()};
522+ std::pair<unsigned char , uint256> prev_key = begin_key;
523+
524+ bool interrupted = false ;
525+ std::unique_ptr<CDBIterator> cursor (block_tree_db.NewIterator ());
526+ for (cursor->Seek (begin_key); cursor->Valid (); cursor->Next ()) {
527+ boost::this_thread::interruption_point ();
528+ if (ShutdownRequested ()) {
529+ interrupted = true ;
530+ break ;
531+ }
532+
533+ if (!cursor->GetKey (key)) {
534+ return error (" %s: cannot get key from valid cursor" , __func__);
535+ }
536+ if (key.first != DB_TXINDEX) {
537+ break ;
538+ }
539+
540+ // Log progress every 10%.
541+ if (++count % 256 == 0 ) {
542+ // Since txids are uniformly random and traversed in increasing order, the high 16 bits
543+ // of the hash can be used to estimate the current progress.
544+ const uint256& txid = key.second ;
545+ uint32_t high_nibble =
546+ (static_cast <uint32_t >(*(txid.begin () + 0 )) << 8 ) +
547+ (static_cast <uint32_t >(*(txid.begin () + 1 )) << 0 );
548+ int percentage_done = (int )(high_nibble * 100.0 / 65536.0 + 0.5 );
549+
550+ uiInterface.ShowProgress (_ (" Upgrading txindex database" ), percentage_done, true );
551+ if (report_done < percentage_done/10 ) {
552+ LogPrintf (" Upgrading txindex database... [%d%%]\n " , percentage_done);
553+ report_done = percentage_done/10 ;
554+ }
555+ }
556+
557+ CDiskTxPos value;
558+ if (!cursor->GetValue (value)) {
559+ return error (" %s: cannot parse txindex record" , __func__);
560+ }
561+ batch_newdb.Write (key, value);
562+ batch_olddb.Erase (key);
563+
564+ if (batch_newdb.SizeEstimate () > batch_size || batch_olddb.SizeEstimate () > batch_size) {
565+ // NOTE: it's OK to delete the key pointed at by the current DB cursor while iterating
566+ // because LevelDB iterators are guaranteed to provide a consistent view of the
567+ // underlying data, like a lightweight snapshot.
568+ WriteTxIndexMigrationBatches (*this , block_tree_db,
569+ batch_newdb, batch_olddb,
570+ prev_key, key);
571+ prev_key = key;
572+ }
573+ }
574+
575+ // If these final DB batches complete the migration, write the best block
576+ // hash marker to the new database and delete from the old one. This signals
577+ // that the former is fully caught up to that point in the blockchain and
578+ // that all txindex entries have been removed from the latter.
579+ if (!interrupted) {
580+ batch_olddb.Erase (DB_TXINDEX_BLOCK);
581+ batch_newdb.Write (DB_BEST_BLOCK, locator);
582+ }
583+
584+ WriteTxIndexMigrationBatches (*this , block_tree_db,
585+ batch_newdb, batch_olddb,
586+ begin_key, key);
587+
588+ if (interrupted) {
589+ LogPrintf (" [CANCELLED].\n " );
590+ return false ;
591+ }
592+
593+ uiInterface.ShowProgress (" " , 100 , false );
594+
595+ LogPrintf (" [DONE].\n " );
596+ return true ;
597+ }
0 commit comments