Build tx index in parallel with validation #11857

pull jimpo wants to merge 12 commits into bitcoin:master from jimpo:txindex-refactor changing 18 files +753 −97
  1. jimpo commented at 11:12 pm on December 8, 2017: contributor

    This refactors the tx index code to be in it’s own class and get built concurrently with validation code. The main benefit is decoupling and moving the txindex into a separate DB. The primary motivation is to lay the groundwork for other indexers that might be desired (such as the compact filters). The basic idea is that the TxIndex spins up its own thread, which first syncs the txindex to the current block index, then once in sync the BlockConnected ValidationInterface hook writes new blocks.

    DB changes

    At the suggestion of some other developers, the txindex has been split out into a separate database. A data migration runs at startup on any nodes with a legacy txindex. Currently the migration blocks node initialization until complete.

    Open questions

    • Should the migration of txindex data from the old DB to the new DB block in init or should it happen in a background thread? The downside to backgrounding it is that getrawtransaction would return an error message saying the txindex is syncing while the migration is running.

    Impact

    In a sample size n=1 test where I synced nodes from scratch, the average time Index writing was 3.36ms in master and 1.72ms in this branch. The average time between UpdateTip log lines for sequential blocks between 400,000 and IBD end on mainnet was 0.297204s in master and 0.286134s in this branch. Most likely this is just variance in IBD times, but I can try with some more trials if people want.

  2. in src/rest.cpp:355 in ae276cead5 outdated
    350@@ -350,6 +351,10 @@ static bool rest_tx(HTTPRequest* req, const std::string& strURIPart)
    351     if (!ParseHashStr(hashStr, hash))
    352         return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
    353 
    354+    if (g_txindex) {
    355+        g_txindex->BlockUntilSyncedToCurrentChain();
    


    promag commented at 1:32 am on December 9, 2017:
    Instead of blocking, give 503?

    jimpo commented at 0:12 am on December 12, 2017:
    It’s not really unavailable, just that the background process needs to catch up, which should happen quickly.

    TheBlueMatt commented at 5:37 pm on March 28, 2018:
    Should we not check the return of BlockUntilSyncedToCurrentChain and maybe always return an error before its even gotten close to caught up?

    TheBlueMatt commented at 6:01 pm on April 19, 2018:
    Still needs to be addressed.

    jimpo commented at 9:47 pm on April 19, 2018:
  3. in src/init.cpp:1471 in ae276cead5 outdated
    1432@@ -1426,9 +1433,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
    1433 
    1434                 if (fRequestShutdown) break;
    1435 
    1436-                // LoadBlockIndex will load fTxIndex from the db, or set it if
    1437+                // LoadBlockIndex will load fHavePruned if we've ever removed a
    


    jamesob commented at 6:51 am on December 9, 2017:
    Comment incomplete or outdated?

    jimpo commented at 1:14 am on December 13, 2017:
    Thanks, fixed.
  4. TheBlueMatt commented at 4:03 pm on December 9, 2017: member

    High-Level Concept ACK. As for your two notes:

    an alternative would be to split the txindex into a separate DB and do a data migration on upgrade.

    I’d vote strongly for this. Keeping them separate is good.

    though it may be fine to just do the TxIndex write directly in the BlockConnected method.

    Yes, I think you should just do this. I’d like to move the CValidationInterface semantics to no longer be a single thread run on the scheduler but instead multiple scheduler threads that process events and only guarantee order for individual clients instead of globally.

  5. jimpo force-pushed on Dec 9, 2017
  6. in src/index/txindex.cpp:278 in 41022ec277 outdated
    221+    m_interrupt();
    222+}
    223+
    224+void TxIndex::Start()
    225+{
    226+    RegisterValidationInterface(this);
    


    promag commented at 1:58 am on December 10, 2017:
    Move after Init?

    jimpo commented at 0:11 am on December 12, 2017:
    No, this has to be registered before m_synced is set to true in Init.

    ryanofsky commented at 4:20 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    No, this has to be registered before m_synced is set to true in Init.

    Definitely worth noting this in a code comment. Also maybe m_synced should be called m_initialized or m_started to be clearer that it changes from false to true just once on startup, and isn’t updated in an ongoing way.

  7. jimpo force-pushed on Dec 13, 2017
  8. jimpo force-pushed on Dec 13, 2017
  9. laanwj added the label UTXO Db and Indexes on Dec 13, 2017
  10. laanwj commented at 7:37 am on December 13, 2017: member

    Concept ACK

    an alternative would be to split the txindex into a separate DB and do a data migration on upgrade.

    I agree that would be preferable (though not necessarily in this PR, I don’t think the scope here should be extended further). The transaction index has a completely different access pattern from the block index. This came up in #10922 and other places.

    Also for safety and flexibility it would be good to have separate indexers, and to not have it integrated into consensus-critical block handling.

  11. TheBlueMatt commented at 4:58 pm on December 13, 2017: member
    I’m not actaully sure that its unrelated - currently txindex is required to be kept in-sync and be present in the block tree DB before the coins for a block are flushed to the utxodb. Moving it to the background without bending over backwards to keep things in-sync would result in “corrupt” (I assume just missing entries) tx index in some cases on downgrade. Its not a huge deal, but I think moving the txindex into a separate DB would be really nice to do before (or in the same PR) things are background-flushed.
  12. jimpo commented at 5:42 pm on December 13, 2017: contributor
    @TheBlueMatt @laanwj I updated this PR to split the database and added a migration. The migration took ~103 min on an AWS m4.large with data on a gp2 EBS volume (non-local SSD). So that’s kind of painful. I could change the migration to happen in the background thread, but the UX might be weird for people upgrading because the RPC endpoint would report that the txindex is catching up. Open to suggestions here.
  13. TheBlueMatt commented at 6:54 pm on December 13, 2017: member

    Seems to be missing migration (forget to push?). Doing migration in the background after start seems fine, better to wait with most things working than it just hanging startup, just have to make sure it doesn’t overwrite new entries or you’d end up pointing to a reorg’d-out block’s copy (though I think thats probably not technically a problem, it seems strange).

    As for threading, it would be really nice to have the flushing for new blocks happen directly in the scheduler/validation interface threading, with the upgrade either in the init thread at the end (it just hangs around for the entire program’s execution waiting for shutdown to start, so as long as the migration is interruptible that’d be a fine place for it) or a new thread.

  14. jimpo commented at 8:10 pm on December 13, 2017: contributor

    @TheBlueMatt Migration is here: https://github.com/bitcoin/bitcoin/pull/11857/commits/a252d466ca0c44a455051d01c43e50e15643c423#diff-81e4f16a1b5d5b7ca25351a63d07cb80R446. Called from Start(), though it could be moved to ThreadSync (the dedicated thread for the txindex initial sync).

    I also changed the new block flushing to happen directly in the BlockConnected callback as you suggested.

  15. in src/txdb.cpp:526 in e95ed5ac89 outdated
    521+            WriteBatch(batch_newdb);
    522+            batch_newdb.Clear();
    523+            CompactRange(prev_key_newdb, key);
    524+            prev_key_newdb = key;
    525+        }
    526+        if (batch_olddb.SizeEstimate() > batch_size) {
    


    TheBlueMatt commented at 8:16 pm on December 13, 2017:
    Separating these if statements loses coherency - if we crash we may have a bogus txindex and no way to detect it. Should likely instead do if (batch_newdb.SizeEstimate() + batch_olddb.SizeEstimate() > batch_size)……..

    jimpo commented at 10:26 pm on December 13, 2017:
    Good catch.
  16. jimpo force-pushed on Dec 14, 2017
  17. jimpo force-pushed on Dec 15, 2017
  18. jimpo force-pushed on Dec 15, 2017
  19. jimpo force-pushed on Dec 15, 2017
  20. jimpo force-pushed on Jan 4, 2018
  21. jimpo force-pushed on Jan 4, 2018
  22. in src/txdb.cpp:440 in 1ae2904ea5 outdated
    435+}
    436+
    437+bool TxIndexDB::WriteTxns(const std::vector<std::pair<uint256, CDiskTxPos>>& v_pos)
    438+{
    439+    CDBBatch batch(*this);
    440+    for (auto tuple : v_pos) {
    


    ryanofsky commented at 3:01 pm on January 5, 2018:

    In commit “[db] Create separate database for txindex.”

    Maybe change to const auto& tuple to avoid a copy while iterating.


    jimpo commented at 11:53 pm on January 8, 2018:
    Done.
  23. in src/txdb.cpp:460 in 1ae2904ea5 outdated
    455+
    456+    int64_t count = 0;
    457+    LogPrintf("Upgrading txindex database...\n");
    458+    LogPrintf("[0%%]...");
    459+    int report_done = 0;
    460+    size_t batch_size = 1 << 24;
    


    ryanofsky commented at 3:06 pm on January 5, 2018:

    In commit “[db] Create separate database for txindex.”

    Maybe declare const and add comment (16MiB).


    jimpo commented at 11:55 pm on January 8, 2018:
    Done.
  24. in src/txdb.cpp:495 in 1ae2904ea5 outdated
    472+        boost::this_thread::interruption_point();
    473+        if (ShutdownRequested()) {
    474+            break;
    475+        }
    476+
    477+        if (!pcursor->GetKey(key)) {
    


    ryanofsky commented at 3:09 pm on January 5, 2018:

    In commit “[db] Create separate database for txindex.”

    Maybe log an error in this case. I don’t think it would be expected for pcursor->Valid() to return true but pcursor->GetKey to fail.


    jimpo commented at 11:56 pm on January 8, 2018:
    I think you are right that this should never happen, so I opted to return an error instead of logging and completing the migration.
  25. in src/txdb.cpp:540 in 1ae2904ea5 outdated
    512+    }
    513+
    514+    WriteBatch(batch_newdb);
    515+    CompactRange(begin_key, key);
    516+    block_tree_db.WriteBatch(batch_olddb);
    517+    block_tree_db.CompactRange(begin_key, key);
    


    ryanofsky commented at 3:20 pm on January 5, 2018:

    In commit “[db] Create separate database for txindex.”

    This is duplicating code inside the for loop, and also skipping the last compact range on the old db. Maybe just flush and compact once at the top of the loop:

    0for (pcursor->Seek(begin_key);; pcursor->Next()) {
    1    if (!pcursor->Valid() || batch_newdb.SizeEstimate()...) {
    2        ...flush and compact...
    3    }
    4    if (!pcursor->Valid()) break;
    5    ...move entry at cursor...
    6}
    

    jimpo commented at 11:59 pm on January 8, 2018:

    Hmm, the logic gets kind of tricky since there’s also the early break if the cursor is valid but iterates past the DB_TXINDEX range. Also the CompactRange after the loop compacts over the entire range, not just from the previous batch write point. What do you mean that it skips the last compact range on the old DB? It shouldn’t.

    This code was mostly copied from CCoinsViewDB::Upgrade if that wasn’t clear.


    ryanofsky commented at 3:58 pm on March 14, 2018:

    #11857 (review)

    In commit “[db] Create separate database for txindex.”

    Hmm, the logic gets kind of tricky since there’s also the early break.

    This doesn’t seem that bad in light of the code duplication in the current version of this code, and the fact that the originally duplicated code had a bug that needed to be fixed two places instead of one (missing the “Sync new DB changes to disk before deleting from old DB” behavior).

    I think adding new done bool variable set to as !pcursor->Valid() || key.first != DB_TXINDEX, would make the code change I suggested above even shorter and clearer.

    What do you mean that it skips the last compact range on the old DB? It shouldn’t.

    My mistake, I don’t think I saw the actual line of code I was commenting on.


    jimpo commented at 0:49 am on March 19, 2018:

    I have tried a few things, and the approach you describe is not worth the additional control flow complexity IMO. Consider also that the CompactRange calls are over a different range inside and outside of the loop (as noted above) and the newest version of the code adds an additional write to the batch outside of the loop to write the block hash.

    However, I understand the argument against duplication, so I instead added a private method extracting the logic:

    0static void WriteTxIndexMigrationBatches(TxIndexDB& newdb, CBlockTreeDB& olddb,
    1                                         CDBBatch& batch_newdb, CDBBatch& batch_olddb,
    2                                         const std::pair<unsigned char, uint256>& begin_key,
    3                                         const std::pair<unsigned char, uint256>& end_key)
    4{
    5    ...
    6}
    

    How do you feel about this approach?


    ryanofsky commented at 10:45 pm on March 19, 2018:

    #11857 (review)

    How do you feel about this approach?

    Better, I’m still not sure why my suggestion doesn’t work out, but this takes care of my concern.

  26. in src/txdb.cpp:487 in 1ae2904ea5 outdated
    443+    return WriteBatch(batch);
    444+}
    445+
    446+bool TxIndexDB::MigrateData(CBlockTreeDB& block_tree_db)
    447+{
    448+    // The prior implementation of txindex was always in sync with block index
    


    ryanofsky commented at 3:26 pm on January 5, 2018:

    In commit “[db] Create separate database for txindex.”

    Unclear why migration should be tied to txindex flag. At first glance, it would seem simpler to reason about possible states and also more robust if the code just always moved DB_TXINDEX entries from the old location to the new location independent of the flag. Maybe add a code comment here to clarify rationale.


    jimpo commented at 11:54 pm on January 8, 2018:
    I elaborated on the comment. I think this is the best way to determine whether a migration is necessary. LMK if you think it needs further explanation.

    ryanofsky commented at 5:49 pm on January 11, 2018:

    In commit “[db] Create separate database for txindex.”

    #11857 (review)

    Unclear why migration should be tied to txindex flag. At first glance, it would seem simpler to reason about possible states and also more robust if the code just always moved DB_TXINDEX entries from the old location to the new location independent of the flag. Maybe add a code comment here to clarify rationale.

    I elaborated on the comment. I think this is the best way to determine whether a migration is necessary. LMK if you think it needs further explanation.

    This still doesn’t seem to be saying why the flag needs to be checked. Maybe add “Do not migrate data if the txindex flag is false, because any data that is found will be stale and not in sync with DB_BEST_BLOCK” to the comment if that’s the explanation.

  27. in src/txdb.h:145 in 0f77eab3ce outdated
    149+    bool WriteBestBlockHash(const uint256& block_hash);
    150+
    151     /// Migrate txindex data from the block tree DB, where it may be for older nodes that have not
    152     /// been upgraded yet to the new database.
    153-    bool MigrateData(CBlockTreeDB& block_tree_db);
    154+    bool MigrateData(CBlockTreeDB& block_tree_db, const uint256& block_hash);
    


    ryanofsky commented at 3:34 pm on January 5, 2018:

    In commit “[db] Methods on TxIndexDB to persist best block hash. "

    Is this a mistake? Probably shouldn’t be adding an unused parameter to an unrelated method in this commit.


    jimpo commented at 0:01 am on January 9, 2018:
    It does get used. The MigrateData implementation is modified to write the block hash in this commit before deleting the txindex flag from the old DB.

    ryanofsky commented at 4:22 pm on March 14, 2018:

    #11857 (review)

    In commit “[db] Methods on TxIndexDB to persist best block hash. "

    It does get used.

    My mistake. IMO, it would make review a little easier if this commit were squashed into the previous commit. I don’t think there’s benefit to adding the new TxIndexDB class in one commit, and then modifying it to add this simple feature separately.

  28. in src/index/txindex.cpp:68 in 669d39e0df outdated
    44+    if (!m_db->ReadBestBlockHash(best_block_hash)) {
    45+        FatalError("%s: Failed to read from tx index database", __func__);
    46+        return false;
    47+    }
    48+
    49+    if (best_block_hash.IsNull()) {
    


    ryanofsky commented at 4:07 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    Maybe restructure the code to get rid of this early successful return. It’s a little confusing, and also not clear if it’s 100% correct. For example I would think that if best_block_hash and chain_tip are both null, m_synced should still be set to true below.


    jimpo commented at 11:50 pm on January 8, 2018:
    Thanks, I moved the check for null chain_tip up to fix that case. However, I still think the early return here is necessary.
  29. in src/index/txindex.cpp:73 in 669d39e0df outdated
    68+bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
    69+{
    70+    CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
    71+    std::vector<std::pair<uint256, CDiskTxPos>> vPos;
    72+    vPos.reserve(block.vtx.size());
    73+    for (auto tx : block.vtx) {
    


    ryanofsky commented at 4:09 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    Could const auto& to avoid a copy.


    jimpo commented at 11:50 pm on January 8, 2018:
    Done
  30. in src/index/txindex.cpp:162 in 669d39e0df outdated
    72+    vPos.reserve(block.vtx.size());
    73+    for (auto tx : block.vtx) {
    74+        vPos.push_back(std::make_pair(tx->GetHash(), pos));
    75+        pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION);
    76+    }
    77+    return m_db->WriteTxns(vPos) && m_db->WriteBestBlockHash(pindex->GetBlockHash());
    


    ryanofsky commented at 4:19 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    Consider moving WriteBestBlockHash call out to caller so the stored best block and m_best_block_index get updated together. It’s a little unexpected to see one value written without the other. Might want to rename this method to WriteBlockTxns if you do this.


    jimpo commented at 0:06 am on January 9, 2018:

    I kind of prefer to keep the DB writes together. I’d hoped that the access pattern would make it clear:

    0if (WriteBlock(*block, pindex)) {
    1    m_best_block_index = pindex;
    2}
    
  31. in src/txdb.cpp:32 in 669d39e0df outdated
    28@@ -29,6 +29,7 @@ static const char DB_HEAD_BLOCKS = 'H';
    29 static const char DB_FLAG = 'F';
    30 static const char DB_REINDEX_FLAG = 'R';
    31 static const char DB_LAST_BLOCK = 'l';
    32+static const char DB_TXINDEX_BEST_BLOCK = 'T';
    


    ryanofsky commented at 4:27 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    Why use a different code for DB_TXINDEX_BEST_BLOCK than DB_BEST_BLOCK? Why even define a new constant at all? It seems strange that the new txindex format would diverge unnecessarily from the old format here when it isn’t doing do that in other places. Should add a code comment explaining if there is a reason.


    jimpo commented at 11:52 pm on January 8, 2018:
    Yeah, that wasn’t even used. Left over from a previous version where the database wasn’t split out.
  32. in src/index/txindex.cpp:161 in 669d39e0df outdated
    92+                       __func__, pindex->nHeight);
    93+            return;
    94+        }
    95+    } else {
    96+        if (best_block_index->GetAncestor(pindex->nHeight - 1) != pindex->pprev) {
    97+            FatalError("%s: Block %s does not connect to an ancestor of known best chain (tip=%s)",
    


    ryanofsky commented at 4:50 pm on January 5, 2018:

    In commit “[index] Create new TxIndex class.”

    I’m not sure this case and the one above should be errors given that BlockConnected calls are queued up in the notification thread and may not be up to date with chainActive (which is what ThreadSync syncs toward). Seems like it would be right (and simpler) to just call WriteBlock unconditionally here.


    jimpo commented at 11:52 pm on January 8, 2018:
    This isn’t checking against chainActive, it is just asserting that BlockConnected gets called with blocks in order (even if chainActive is ahead). It’s important to note that if the BlockConnected callbacks are running, then m_synced is true an ThreadSync has exited. I added more comments to these fields/methods in the header file to hopefully make that more clear.
  33. ryanofsky commented at 5:14 pm on January 5, 2018: member
    Looks great! I reviewed most of this but rushed through the last few commits and want to look more closely before adding ack.
  34. jimpo force-pushed on Jan 8, 2018
  35. laanwj commented at 6:50 pm on February 8, 2018: member
    Needs rebase (probably a simple one, only init.cpp conflicted).
  36. laanwj assigned laanwj on Feb 8, 2018
  37. jimpo force-pushed on Feb 8, 2018
  38. in src/rpc/rawtransaction.cpp:190 in c4401fc414 outdated
    182@@ -177,10 +183,12 @@ UniValue getrawtransaction(const JSONRPCRequest& request)
    183                 throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
    184             }
    185             errmsg = "No such transaction found in the provided block";
    186+        } else if (!g_txindex) {
    187+            errmsg = "No such mempool transaction. Use -txindex to enable blockchain transaction queries";
    188+        } else if (!f_txindex_ready) {
    189+            errmsg = "No such mempool transaction. Blockchain transactions are still in the process of being indexed";
    


    Sjors commented at 2:35 pm on February 9, 2018:
    Should this have a distinct error code, rather than RPC_INVALID_ADDRESS_OR_KEY? If I understand correctly, an RPC consumer should wait a little and try again if this happens.

    jimpo commented at 8:50 pm on February 9, 2018:

    Yes, that’s right, an RPC consumer should wait until the index is built. Using the RPC_INVALID_ADDRESS_OR_KEY error code for the "No such mempool transaction. Use -txindex to enable blockchain transaction queries" clause also seems odd to me.

    Perhaps the RPC_IN_WARMUP makes sense here, or perhaps a new RPC_INDEX_UNAVAILABLE error code.


    TheBlueMatt commented at 5:47 pm on March 28, 2018:
    A distinct error code may make sense, though no opinion on what. Further, I’m not sure we should let GetTransaction call into the g_txindex if !f_txindex_ready (as it may return data from a stale block, but its also just generally not so useful). Maybe GetTransaction/FindTx can check if we’re in sync yet.

    jimpo commented at 1:23 am on March 30, 2018:
    Yeah, it’s best effort, just like the lookup from CCoinsCacheView. If there’s a response, great, otherwise the error message says that the not found may be inclusive. I agree that’s kind of a weird behavior for the RPC, but if we change it, I could make the same argument for just removing the CCoinsCacheView lookup.
  39. in src/txdb.cpp:546 in c4401fc414 outdated
    499+        }
    500+        if (key.first != DB_TXINDEX) {
    501+            break;
    502+        }
    503+
    504+        // Log progress every 10%.
    


    Sjors commented at 2:43 pm on February 9, 2018:
    If it really takes 103 minutes on a fast EC2 instance, maybe make it 1%? Or once a minute? QT already shows 1% intervals during the upgrade.

    jimpo commented at 9:07 pm on February 9, 2018:
    The percentage shown in the UI is updated with every percent, it’s just the log line that shows increments of 10, as it appends to the same line: [0%]...[10%]...[20%...]. Alternatively, it could log progress on a separate line every 30 seconds or something like: “Upgrading txindex database: n% complete\n”. I kind of prefer that approach, but I copied how it was done for CCoinsViewDB upgrade.

    jimpo commented at 6:37 pm on March 8, 2018:
    I opted to log every 30s.
  40. Sjors commented at 3:46 pm on February 9, 2018: member

    Concept ACK. It’s very nice to able to add and remove txindex without a full reindex, which takes weeks on my Bitseed node. Worthy of a Performance label?

    It tested (with testnet3) adding txindex=1 on this branch, as well as adding txindex=1 on master and then migrating using this branch.

    Possibly unrelated, but upon switching back to master, deleting /indexes, launching src/qt/bitcoin-qt -txindex=1, confirming that I wanted to do a reindex, it failed with “Error opening block database”. I then run bitcoind -txindex=1 -reindex for a minute, closed it and launched QT again, at which point indexing proceeded as expected.

    In terms of storage something seems off (testnet3):

    • chainstate without txindex: 968.7 MB
    • chainstate with “legacy” txindex: 966.8 MB
    • chainstate on this branch, after migration: 986.4 MB
    • indexes, after migration: 870 MB
    • indexes, without migration: 873 MB

    So it looks like txindex only adds 30 MB on master, but the separate indexes directory adds almost 800 MB. Or I’m doing something wrong.

    Can you expand the progress logger beyond migration? It’s also useful for when txindex=1 is added after IBD.

    Would be nice to have functional tests for the various getrawtransaction error messages.

    It’s a bit confusing to call the directory /indexes when the block index file isn’t there, but maybe that can be moved later (or never, if it’s too risky).

  41. Sjors commented at 4:15 pm on February 9, 2018: member

    I also noticed that indexes isn’t emptied if you set txindex=0. That’s probably a good thing, e.g. perhaps someone doesn’t want to read / update them for a while, but should be documented.

    I was able to reproduce the “Error opening block database” QT error (on master) after a fresh reindex (on this branch). No need to use bitcoind to make it go away, simply launch with -reindex. That shouldn’t be necessary.

    This also seems to happens on master so might be unrelated:

    02018-02-09 16:23:44 init message: Loading block index...
    12018-02-09 16:23:44 Wiping LevelDB in /Users/bitcoin/Library/Application Support/Bitcoin/testnet3/blocks/index
    22018-02-09 16:23:44 IO error: lock /Users/bitcoin/Library/Application Support/Bitcoin/testnet3/blocks/index/LOCK: already held by process
    32018-02-09 16:23:44 Database I/O error
    
  42. Sjors commented at 7:01 pm on February 9, 2018: member

    I was comparing the wrong directory sizes above. chainstate is for the UTXO set, txindex is in blocks/index (on master).

    Also the error I was seeing was unrelated, and hopefully fixed in #12401

  43. jimpo force-pushed on Feb 9, 2018
  44. jimpo commented at 9:49 pm on February 9, 2018: contributor

    @Sjors Thanks for the review!

    I added f1b8b8d to log the txindex build status periodically while it is catching up.

    I agree functional tests would be nice, but I’m having trouble figuring out how to exercise the case where the txindex is catchup up in the test harness. Could maybe sync a node with a few blocks, stop it, delete the txindex database files, restart and then hit the RPC, but it is going to be racey no matter what. Let me know if you have suggestions.

    I’m curious what other people think, but having the block index database elsewhere doesn’t bother me too much, because it is special and required. I am imagining that indexes/ will be for optional indexes that can be built and synced in the background. Also, it doesn’t seem worth it to make a migration just to move around the block index directory path.

  45. Sjors commented at 10:43 am on February 10, 2018: member
    @jnewbery: any thoughts on how to tackle functional tests?
  46. hkjn referenced this in commit 5d33ab6c11 on Feb 21, 2018
  47. in src/init.cpp:1589 in f1b8b8d676 outdated
    1573@@ -1568,6 +1574,12 @@ bool AppInitMain()
    1574                 return InitError(strLoadError);
    1575             }
    1576         }
    1577+
    1578+        if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
    1579+            auto txindex_db = MakeUnique<TxIndexDB>(nTxIndexCache, false, fReset);
    1580+            g_txindex.reset(new TxIndex(std::move(txindex_db)));
    


    eklitzke commented at 8:45 pm on March 5, 2018:

    This is a lot of moves, since you already move TxIndex::TxIndex. Can you change to create the unique_ptr in the constructor call, e.g.:

    0g_txindex.reset(new TxIndex(MakeUnique<...>(....));
    

    Or even better, use argument forwarding.


    jimpo commented at 3:25 pm on March 6, 2018:

    I’d rather have them on separate lines because I think otherwise there is too much happening on one line (and it would get pretty long).

    Regarding argument forwarding, even if TxIndex takes an rvalue-ref, the std::move is necessary.

  48. in src/index/txindex.h:59 in f1b8b8d676 outdated
    49+protected:
    50+    void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex,
    51+                        const std::vector<CTransactionRef>& txn_conflicted) override;
    52+
    53+public:
    54+    explicit TxIndex(std::unique_ptr<TxIndexDB> db);
    


    eklitzke commented at 8:46 pm on March 5, 2018:

    Can you make this forwardable?

    0TxIndex(std::unique_ptr<TxIndexDB> &&db);
    

    Or just create the unique_ptr here, see my other comment regarding g_txindex.reset()


    jimpo commented at 3:27 pm on March 6, 2018:
    I find it clearer to pass by value here, as is recommended by this StackOverflow post. Is there a big benefit to changing the parameter to an rvalue-ref?

    eklitzke commented at 7:22 am on March 11, 2018:
    You’re right, disregard.
  49. in src/index/txindex.cpp:239 in f1b8b8d676 outdated
    212+        if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
    213+            return true;
    214+        }
    215+    }
    216+
    217+    SyncWithValidationInterfaceQueue();
    


    eklitzke commented at 8:49 pm on March 5, 2018:
    I think it makes sense to add a logging statement in the path here where the method actually blocks.

    jimpo commented at 3:27 pm on March 6, 2018:
    Done.
  50. in src/index/txindex.cpp:241 in f1b8b8d676 outdated
    236+    if (!Init()) {
    237+        return;
    238+    }
    239+
    240+    m_thread_sync = std::thread(&TraceThread<std::function<void()>>, "txindex",
    241+                                std::function<void()>(std::bind(&TxIndex::ThreadSync, this)));
    


    eklitzke commented at 8:54 pm on March 5, 2018:
    Doesn’t std::bind already return the right type here?

    jimpo commented at 2:47 pm on March 6, 2018:
    Good catch. This came from a copy-paste.
  51. in src/txdb.cpp:435 in f1b8b8d676 outdated
    429+    CDBWrapper(GetDataDir() / "indexes" / "txindex", n_cache_size, f_memory, f_wipe)
    430+{}
    431+
    432+bool TxIndexDB::ReadTxPos(const uint256 &txid, CDiskTxPos& pos) const
    433+{
    434+    return Read(std::make_pair(DB_TXINDEX, txid), pos);
    


    eklitzke commented at 8:56 pm on March 5, 2018:
    I believe you can do {DB_TXINDEX, txid} in C++11.

    jimpo commented at 2:48 pm on March 6, 2018:
    That doesn’t seem to work because Read is templated and can’t infer that the initializer list should be cast to a std::pair.
  52. eklitzke commented at 8:56 pm on March 5, 2018: contributor
    Mostly style stuff, will do another pass later to review the logic more closely.
  53. laanwj referenced this in commit 9903537750 on Mar 6, 2018
  54. jimpo force-pushed on Mar 6, 2018
  55. sipa commented at 6:50 pm on March 6, 2018: member

    Concept ACK.

    Having the transaction index as a modular, optional, separate database that gets updated in the background is a major improvement over how things work now.

    It’s also the approach that similar optional indexes or redundant data can take; for example a rolling UTXO set hash or a per-scriptPubKey UTXO index (see #9806) could follow the same approach.

  56. Sjors commented at 3:15 pm on March 7, 2018: member

    Tested adding an index in Bitcoin-QT and it seems to work. I didn’t try upgrading an existing index.

    It doesn’t delete the index when you set txindex=0. That might actually be better than the current behavior to prevent accidents, but should be explained in the release notes and such.

  57. jimpo force-pushed on Mar 8, 2018
  58. jimpo force-pushed on Mar 8, 2018
  59. jimpo force-pushed on Mar 8, 2018
  60. jimpo commented at 6:31 pm on March 8, 2018: contributor
    @Sjors Thanks for testing. I fixed the issue switching from pruned to unpruned w/o txindex with an amend to 33ebb98. You raise an interesting point about dropping the txindex database. Not sure what the right behavior should be. One approach might be to add a -dropindex=txindex flag or to add an RPC and bitcoin-cli command that errors if the txindex is active. I like the CLI approach personally, but the downside is it requires the RPC server to be running.
  61. Sjors commented at 7:08 pm on March 8, 2018: member
    I think your release note is fine. Deleting a directory doesn’t seem worth the complexity of another RPC command / command line flag. I don’t think it’s too much to ask.
  62. jonasschnelli commented at 1:11 am on March 9, 2018: contributor

    Concept ACK!

    • Is there a reason for the extensive use of the c++11 auto specifier (for things like CBlockIndex*)?
    • Maybe a stupid question, but why blocking the thread / RPC / REST during txindex generation? I just had the thought why not building the txindex reserve (from tip to genesis) and allow access anytime while eventually add a txindex status report call txindexstatus (if this makes sense, then probably not in this PR).
  63. jimpo commented at 1:23 am on March 9, 2018: contributor

    @jonasschnelli I wrote this before the discussion on #12120. Happy to go through the commits and reduce the usage.

    As for the RPC behavior, it actually works similar to how you suggest. BlockUntilSyncedToCurrentChain only blocks if the txindex is caught up and just waits for the ValidationInterface queue to clear. If the txindex is syncing from genesis, BlockUntilSyncedToCurrentChain immediately returns false. The getrawtransaction behavior, however, is to optimistically query the index and only if it can’t find the tx does it return a not found error saying the index is catching up. Basically, the index has an initial sync phase, then a staying in sync phase, where RPC calls might block for a short amount of time just for consistency with other RPC responses.

  64. in src/index/txindex.cpp:14 in 1f9c92d5f7 outdated
     9+#include <ui_interface.h>
    10+#include <util.h>
    11+#include <validation.h>
    12+#include <warnings.h>
    13+
    14+static constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds
    


    eklitzke commented at 7:14 am on March 11, 2018:
    remove static, that’s the opposite of what you want (it reserves .bss space)
  65. in src/index/txindex.cpp:52 in 1f9c92d5f7 outdated
    47+        tip_hash = chain_tip->GetBlockHash();
    48+    }
    49+
    50+    // Attempt to migrate txindex from the old database to the new one. Even if
    51+    // chain_tip is null, the node could be reindexing and we still want to
    52+    // delete txindex records the old database.
    


    eklitzke commented at 7:15 am on March 11, 2018:
    typo, “in the old database”
  66. in src/index/txindex.cpp:149 in 1f9c92d5f7 outdated
    144+    }
    145+
    146+    if (pindex) {
    147+        LogPrintf("txindex is enabled at height %d\n", pindex->nHeight);
    148+    } else {
    149+        LogPrintf("txindex is enabled\n", pindex->nHeight);
    


    eklitzke commented at 7:17 am on March 11, 2018:
    This is wrong, you have an extra argument that dereferences a nullptr.

    jimpo commented at 10:12 pm on March 11, 2018:
    Thanks.
  67. in src/index/txindex.cpp:135 in 1f9c92d5f7 outdated
    152+
    153+bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
    154+{
    155+    CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
    156+    std::vector<std::pair<uint256, CDiskTxPos>> vPos;
    157+    vPos.reserve(block.vtx.size());
    


    eklitzke commented at 7:18 am on March 11, 2018:
    nit: move this to the ctor (it can take a size to reserve)

    jimpo commented at 10:12 pm on March 11, 2018:
    I’m pretty sure the std::vector constructor that takes a size_t creates the vector with n elements rather than having a size of 0 and a capacity of n.

    eklitzke commented at 6:20 am on March 13, 2018:

    That’s not strictly accurate:

     0$ g++ --version
     1g++ (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)
     2Copyright (C) 2017 Free Software Foundation, Inc.
     3This is free software; see the source for copying conditions.  There is NO
     4warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
     5
     6$ cat reserve.cc 
     7#include <iostream>
     8#include <vector>
     9
    10struct Foo {
    11    int x;
    12    Foo() :x(1) {}
    13};
    14
    15int main(int argc, char **argv) {
    16    std::vector<Foo> foovec;
    17    foovec.reserve(10);
    18    std::cout << foovec[5].x << std::endl;
    19    std::cout << foovec.at(5).x << std::endl;
    20    return 0;
    21}
    22
    23$ g++ reserve.cc 
    24
    25$ ./a.out 
    260
    27terminate called after throwing an instance of 'std::out_of_range'
    28  what():  vector::_M_range_check: __n (which is 5) >= this->size() (which is 0)
    29Aborted (core dumped)
    

    In this test Foo has a constructor that sets x to 1, but as you can see from the first line of output it prints 0 implying that the ctor wasn’t actually called. The .at() check fails because the element is out of bounds compared to the true size. The standard calls this “default insertable” which I think just means “not allocator aware” in which case the default allocator is allowed to just create space for the item.

    Either way it’s a nit that you can ignore, but I think the suggestion I made is safe and slightly faster, assuming you actually initialize the elements before accessing them.


    jimpo commented at 4:11 pm on March 19, 2018:
    Have you tried that code sample with std::vector<Foo> foovec(10); instead of std::vector<Foo> foovec; foovec.reserve(10);? The behavior is different. I assume that is the suggestion you are making, though maybe I misunderstood.
  68. in src/index/txindex.cpp:159 in 1f9c92d5f7 outdated
    154+{
    155+    CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
    156+    std::vector<std::pair<uint256, CDiskTxPos>> vPos;
    157+    vPos.reserve(block.vtx.size());
    158+    for (const auto& tx : block.vtx) {
    159+        vPos.push_back(std::make_pair(tx->GetHash(), pos));
    


    eklitzke commented at 7:19 am on March 11, 2018:
    nit: emplace_back
  69. in src/txdb.h:143 in 1f9c92d5f7 outdated
    138+    /// Read the disk location of the transaction data with the given hash. Returns false if the
    139+    /// transaction hash is not indexed.
    140+    bool ReadTxPos(const uint256& txid, CDiskTxPos& pos) const;
    141+
    142+    /// Write a batch of transaction positions to the DB.
    143+    bool WriteTxns(const std::vector<std::pair<uint256, CDiskTxPos>>& v_pos);
    


    eklitzke commented at 7:20 am on March 11, 2018:
    IMO it’s weird to mix tx and txn, I would just use tx (and txs) throughout. Up to you though, purely a style thing.

    jimpo commented at 9:53 pm on March 11, 2018:
    I agree, will change.
  70. in src/txdb.cpp:523 in 1f9c92d5f7 outdated
    518+        }
    519+        batch_newdb.Write(key, value);
    520+        batch_olddb.Erase(key);
    521+
    522+        if (batch_newdb.SizeEstimate() > batch_size || batch_olddb.SizeEstimate() > batch_size) {
    523+            WriteBatch(batch_newdb);
    


    eklitzke commented at 7:29 am on March 11, 2018:

    The transaction logic throughout is wrong here because there’s a data ordering dependency.

    During the data migration:

    • Write to new new with fsync = True
    • After new write finishes, you can erase from old db (fsync setting can be left false)

    This ensures that you can can always scan through the old database and catch up to what’s in the new database if there’s a crash or you are interrupted. During the non-migration path I think it’s fine to have fsync disabled.

  71. in src/index/txindex.cpp:162 in 1f9c92d5f7 outdated
    157+    vPos.reserve(block.vtx.size());
    158+    for (const auto& tx : block.vtx) {
    159+        vPos.push_back(std::make_pair(tx->GetHash(), pos));
    160+        pos.nTxOffset += ::GetSerializeSize(*tx, SER_DISK, CLIENT_VERSION);
    161+    }
    162+    return m_db->WriteTxns(vPos) && m_db->WriteBestBlockHash(pindex->GetBlockHash());
    


    eklitzke commented at 7:34 am on March 11, 2018:
    These should both be done in a single WriteBatch() call to make them atomic.

    jimpo commented at 10:15 pm on March 11, 2018:
    Will do.
  72. eklitzke changes_requested
  73. eklitzke commented at 7:36 am on March 11, 2018: contributor
    This looks pretty good. There are some data consistency issues and a null dereference I spotted, but they should be easy to fix.
  74. jimpo force-pushed on Mar 11, 2018
  75. jimpo force-pushed on Mar 11, 2018
  76. eklitzke commented at 6:25 am on March 13, 2018: contributor
    I thought more about the ordering dependency between the block index and the txindex, and I realize now that it might be possible to make them independent of ordering. There’s an edge case (that may not be possible in practice) if a reorg happens during crash recovery, so I stand by my original recommendation and maybe we can fix the reorg edge case before 0.17 is released.
  77. jimpo force-pushed on Mar 13, 2018
  78. in src/txdb.h:137 in 3c37a4b862 outdated
    125@@ -126,4 +126,25 @@ class CBlockTreeDB : public CDBWrapper
    126     bool LoadBlockIndexGuts(const Consensus::Params& consensusParams, std::function<CBlockIndex*(const uint256&)> insertBlockIndex);
    127 };
    128 
    129+/** Access to the block database (blocks/index/) */
    130+class TxIndexDB : public CDBWrapper
    


    ryanofsky commented at 3:03 pm on March 14, 2018:

    In commit “[db] Create separate database for txindex.”

    Commit title could be changed to something like “Add (unused) TxIndexDB class” to be more specific. Current title is a little misleading about what the actual change is.


    jimpo commented at 1:20 am on March 19, 2018:
    Done.
  79. in src/txdb.h:135 in 3c37a4b862 outdated
    130+class TxIndexDB : public CDBWrapper
    131+{
    132+public:
    133+    explicit TxIndexDB(size_t n_cache_size, bool f_memory = false, bool f_wipe = false);
    134+
    135+    TxIndexDB(const TxIndexDB&) = delete;
    


    ryanofsky commented at 3:11 pm on March 14, 2018:

    In commit “[db] Create separate database for txindex.”

    I think it’d be better to disable copying in CDBWrapper instead of here, since the state that’s actually unsafe to copy is in that class, not this one.


    jimpo commented at 1:20 am on March 19, 2018:
    Done.
  80. in src/txdb.cpp:489 in 3c37a4b862 outdated
    468+    std::pair<unsigned char, uint256> begin_key{DB_TXINDEX, uint256()};
    469+    std::pair<unsigned char, uint256> prev_key_newdb = begin_key;
    470+    std::pair<unsigned char, uint256> prev_key_olddb = begin_key;
    471+
    472+    std::unique_ptr<CDBIterator> pcursor(block_tree_db.NewIterator());
    473+    for (pcursor->Seek(begin_key); pcursor->Valid(); pcursor->Next()) {
    


    ryanofsky commented at 3:24 pm on March 14, 2018:

    In commit “[db] Create separate database for txindex.”

    Could say cursor instead of pcursor to avoid more hungarian names.


    jimpo commented at 1:20 am on March 19, 2018:
    Done.
  81. in src/index/txindex.h:17 in 478e5db2d7 outdated
    11+
    12+class CBlockIndex;
    13+
    14+/**
    15+ * TxIndex is used to look up transactions included in the blockchain by hash.
    16+ * The index is written to a keyspace in the block index database and records
    


    ryanofsky commented at 4:45 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    Not really the “block index database” now, but a separate database (unless I’m misinterpreting)?


    jimpo commented at 0:04 am on March 19, 2018:
    Yeah, outdated comment, will fix. An earlier version of this refactor didn’t split the DB out, just the logic.
  82. in src/index/txindex.h:30 in 478e5db2d7 outdated
    22+    const std::unique_ptr<TxIndexDB> m_db;
    23+
    24+    /// Whether the index is in sync with the main chain. The flag is flipped
    25+    /// from false to true once, after which point this starts processing
    26+    /// ValidationInterface notifications to stay in sync.
    27+    std::atomic<bool> m_synced;
    


    ryanofsky commented at 4:47 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    Maybe set to this false here to simplify initialization in the constructor. Similarly for atomic member below.

  83. in src/index/txindex.h:59 in 478e5db2d7 outdated
    38+protected:
    39+    void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* pindex,
    40+                        const std::vector<CTransactionRef>& txn_conflicted) override;
    41+
    42+public:
    43+    explicit TxIndex(std::unique_ptr<TxIndexDB> db);
    


    ryanofsky commented at 4:49 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    Maybe add comment about initialization sequence given the three initialization methods in this class (constructor, Init, and Start).


    jimpo commented at 1:28 am on March 19, 2018:
    Done.
  84. in src/index/txindex.cpp:72 in 478e5db2d7 outdated
    56+
    57+    if (best_block_hash.IsNull()) {
    58+        return true;
    59+    }
    60+
    61+    const CBlockIndex* pindex = LookupBlockIndex(best_block_hash);
    


    ryanofsky commented at 4:59 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    It seems hungarian notation can never go away even in new code, given how sticky the pindex convention seems to be, but maybe consider calling this block or best_block_index instead.

  85. jimpo force-pushed on Mar 14, 2018
  86. in src/index/txindex.cpp:134 in 478e5db2d7 outdated
    73+}
    74+
    75+bool TxIndex::WriteBlock(const CBlock& block, const CBlockIndex* pindex)
    76+{
    77+    CDiskTxPos pos(pindex->GetBlockPos(), GetSizeOfCompactSize(block.vtx.size()));
    78+    std::vector<std::pair<uint256, CDiskTxPos>> vPos;
    


    ryanofsky commented at 5:09 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    It seems needlessly inefficient and complicated to be creating a vector just to be able to pass a sequence of txids to the TxIndexDB::WriteTxs method, which is only called from this one place. It seems like a simpler design might inline the WriteTx method, or pass a block to it, or just not have separate TxIndexDB and TxIndex classes.


    jimpo commented at 1:33 am on March 19, 2018:
    Yes, it could be more efficient, but I feel that this produces the cleanest separation of responsibilities. Another way to do it might be to add a TxIndexDB::AddTxToBatch(CDBBatch& batch, const uint256& txid, const CDiskTxPos& tx_pos) method. Would you prefer that?

    ryanofsky commented at 10:50 pm on March 19, 2018:

    #11857 (review)

    Yes, it could be more efficient, but I feel that this produces the cleanest separation of responsibilities.

    I’m not sure what the separation of responsibilities is actually. If it’s important to avoid a more direct write, can you say somewhere in a comment what the separation is supposed to be, and what benefits it provides? This just seems a little more complicated than I would expect it to be.

  87. in src/index/txindex.cpp:94 in 478e5db2d7 outdated
    89+{
    90+    if (!m_synced) {
    91+        return;
    92+    }
    93+
    94+    // Ensure block connects to an ancestor of the current best block.
    


    ryanofsky commented at 7:05 pm on March 14, 2018:

    In commit “[index] Create new TxIndex class.”

    Would expand this comment to mention what could actually trigger these errors. Maybe add “If the errors below are triggered, it means a BlockConnected call has been received for a new block which has ancestor blocks that might never have been added to the transaction index. This could happen if a bug causes BlockConnected notifications to be sent in the wrong order, or if there’s an unfortunately timed reorg or invalidateblock call that causes the first BlockConnection notification after shutdown of the initial sync thread to not be an ancestor or direct descendant of chainActive at the time the sync completed.”


    jimpo commented at 1:39 am on March 19, 2018:
    I’ll elaborate on the comment, but I don’t understand the case you are describing. These checks should always pass as long as the ValidationInterface BlockConnected notifications are sent in order. If there is a race you can think of in the presence of reorgs or shutdowns, we should fix that.

    ryanofsky commented at 11:16 pm on March 19, 2018:

    #11857 (review)

    I’ll elaborate on the comment, but I don’t understand the case you are describing

    The BlockConnected handler will drop any notifications as long as m_synced is false, but when m_synced becomes true, there is no code to clear out the queue, which could be backlogged. So if m_synced is set to true after a reorg but before BlockConnected calls from the reorg arrive, some initial BlockConnected notifications about blocks that aren’t descendants of the synced best block could fail.


    jimpo commented at 10:04 pm on April 4, 2018:
    Ah, got it. Will update the comment and make this a warning log instead of a fatal error.
  88. in src/index/txindex.cpp:14 in b381ec3672 outdated
    10@@ -10,6 +11,8 @@
    11 #include <validation.h>
    12 #include <warnings.h>
    13 
    14+constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds
    


    ryanofsky commented at 8:10 pm on March 14, 2018:

    In commit “[index] TxIndex initial sync thread.”

    Could declare static to avoid creating a linker symbol.


    jimpo commented at 1:52 am on March 19, 2018:

    Correct me if I’m wrong, but I assume this could be inlined or something by the compiler if it were not declared static. I tried looking it up briefly and it said something along those lines, but then I got bored because I don’t care all that much (something, something ODR-rule?). Will change if you still want.

    Also, see #11857 (review).


    ryanofsky commented at 11:29 pm on March 19, 2018:

    #11857 (review)

    Correct me if I’m wrong, but I assume this could be inlined or something by the compiler if it were not declared static. I tried looking it up briefly and it said something along those lines, but then I got bored because I don’t care all that much (something, something ODR-rule?). Will change if you still want.

    Hmm, I guess you and @eklitzke are making me question my sanity, but why would adding static here prevent inlining, or “reserve .bss space” or do anything along these lines?

    If this isn’t declared static, the compiler doesn’t know that there isn’t some code in another object file that will access the SYNC_LOG_INTERVAL variable or take its address. So there will necessarily be a SYNC_LOG_INTERVAL symbol in the object file. If it’s static, it can just be internal to the object.

  89. in src/init.cpp:1616 in 0ffbc19451 outdated
    1609@@ -1598,15 +1610,22 @@ bool AppInitMain()
    1610         ::feeEstimator.Read(est_filein);
    1611     fFeeEstimatesInitialized = true;
    1612 
    1613-    // ********************************************************* Step 8: load wallet
    1614+    // ********************************************************* Step 8: start indexers
    1615+    if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
    1616+        auto txindex_db = MakeUnique<TxIndexDB>(nTxIndexCache, false, fReindex);
    1617+        g_txindex.reset(new TxIndex(std::move(txindex_db)));
    


    ryanofsky commented at 8:17 pm on March 14, 2018:

    In commit “[init] Initialize and start TxIndex in init code.”

    Might be more consistent to use MakeUnique here too.


    jimpo commented at 1:58 am on March 19, 2018:
    Done.
  90. in src/init.cpp:1418 in 0ffbc19451 outdated
    1415-    nBlockTreeDBCache = std::min(nBlockTreeDBCache, (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20);
    1416+    int64_t nBlockTreeDBCache = nTotalCache / 16;
    1417+    nBlockTreeDBCache = std::min(nBlockTreeDBCache, nMaxBlockDBCache << 20);
    1418     nTotalCache -= nBlockTreeDBCache;
    1419+    int64_t nTxIndexCache = nTotalCache / 8;
    1420+    nTxIndexCache = std::min(nTxIndexCache, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxTxIndexCache << 20 : 0);
    


    ryanofsky commented at 8:23 pm on March 14, 2018:

    In commit “[init] Initialize and start TxIndex in init code.”

    Could combine lines and shorten by just passing nTotalCache / 8 directly to min. Same for nBlockTreeDBCache above.

  91. in src/init.cpp:1414 in 0ffbc19451 outdated
    1410@@ -1404,16 +1411,22 @@ bool AppInitMain()
    1411     int64_t nTotalCache = (gArgs.GetArg("-dbcache", nDefaultDbCache) << 20);
    1412     nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache
    1413     nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greater than nMaxDbcache
    1414-    int64_t nBlockTreeDBCache = nTotalCache / 8;
    1415-    nBlockTreeDBCache = std::min(nBlockTreeDBCache, (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? nMaxBlockDBAndTxIndexCache : nMaxBlockDBCache) << 20);
    1416+    int64_t nBlockTreeDBCache = nTotalCache / 16;
    


    ryanofsky commented at 8:24 pm on March 14, 2018:

    In commit “[init] Initialize and start TxIndex in init code.”

    What’s the reason for halving this even if txindex is disabled?


    jimpo commented at 2:01 am on March 19, 2018:
    You’re right, will revert. I was just kind of making up values here.
  92. in src/index/txindex.h:68 in e4523cf480 outdated
    55@@ -56,6 +56,10 @@ class TxIndex final : public CValidationInterface
    56     /// Destructor interrupts sync thread if running and blocks until it exits.
    57     ~TxIndex();
    58 
    59+    /// Blocks the current thread until the transaction index is caught up to
    60+    /// the current state of the block chain.
    61+    bool BlockUntilSyncedToCurrentChain();
    


    ryanofsky commented at 8:37 pm on March 14, 2018:

    [index] TxIndex method to wait until caught up.

    Description above and function name seem a little misleading since this will only wait to be caught up with validation notifications, and will actually return false immediately during startup. This is probably worth mentioning in the comment above, and maybe in an additional comment where this is called. Could also give this this a less reassuring name like FinishProcessingCurrentBlock.


    jimpo commented at 1:57 am on March 19, 2018:
    Good point, updated comment.
  93. in src/test/txindex_tests.cpp:31 in 1f89575ec9 outdated
    26+        MilliSleep(100);
    27+    }
    28+
    29+    for (const auto& txn : coinbaseTxns) {
    30+        CDiskTxPos postx;
    31+        BOOST_CHECK(txindex.FindTx(txn.GetHash(), postx));
    


    ryanofsky commented at 8:45 pm on March 14, 2018:

    In commit “[test] Simple unit test for TxIndex.”

    Would be good to validate postx variable contents here and below.


    jimpo commented at 6:58 pm on March 20, 2018:
    Done.
  94. in src/test/txindex_tests.cpp:43 in 1f89575ec9 outdated
    38+        const CTransaction& txn = *block.vtx[0];
    39+
    40+        txindex.BlockUntilSyncedToCurrentChain();
    41+
    42+        CDiskTxPos actual_postx;
    43+        BOOST_CHECK(txindex.FindTx(txn.GetHash(), actual_postx));
    


    ryanofsky commented at 8:52 pm on March 14, 2018:

    In commit “[test] Simple unit test for TxIndex.”

    Would suggest adding a few more simple checks:

    • Test that sync thread actually works. I.e. that if new transactions are added before calling Start(), they don’t show up, and that they do show up after calling Start().
    • Check that transactions are persistent and reloaded correctly if txindex is closed and reopened.
    • Check BlockUntilSyncedToCurrentChain return value. Should be false before started, true after.

    jimpo commented at 7:02 pm on March 20, 2018:

    I added checks for 1 & 3. With regards to persistence, the DB in the test environment currently uses the in-memory LevelDB environment rather than actually writing to disk, which makes cleanup nicer and probably makes the test marginally faster.

    How important do you think it is to test the persistence at this layer? My feeling is that it’s probably not adding much since it’s just using the common CDBWrapper abstraction.

  95. ryanofsky commented at 9:14 pm on March 14, 2018: member

    utACK 21f8a3bae27ffbbfe9e5d8a6d3c6b4e8c09045e1. This is really nicely implemented. I left some suggestions below, but they are mostly requests to add comments.

    • On the open question in the PR description: I don’t understand the downside to migrating in a background thread. Isn’t getrawtransaction unusable during migration whether it’s happening in the foreground or background?

    • Other things could be cleaned up in the PR description: “its” spelled “it’s,” repetition of “The primary motivation is to lay the groundwork for other indexers” and “The real goal is to lay the groundwork for other indexers”

    • @eklitzke, can you clarify what the broken “reorg edge case” is from #11857 (comment), so we can at least document it, if not fix it here.

  96. ryanofsky commented at 5:30 pm on March 15, 2018: member

    Two functional tests rpc_rawtransaction.py and wallet_abandonconflict.py seem to be failing, perhaps due to a bad interaction with #11041. AssertLockHeld is failing with stack trace:

     0[#0](/bitcoin-bitcoin/0/)  0x00007ffff5411428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
     1[#1](/bitcoin-bitcoin/1/)  0x00007ffff541302a in __GI_abort () at abort.c:89
     2[#2](/bitcoin-bitcoin/2/)  0x0000555555b617d0 in AssertLockHeldInternal (pszName=0x555555bfe1ff "cs_main", pszFile=0x555555bfe1f0 "./validation.h", nLine=432, cs=0x55555615d240 <cs_main>) at sync.cpp:157
     3[#3](/bitcoin-bitcoin/3/)  0x00005555555beae4 in LookupBlockIndex (hash=...) at ./validation.h:432
     4[#4](/bitcoin-bitcoin/4/)  0x00005555557a487f in TxToJSON (tx=..., hashBlock=..., entry=...) at rpc/rawtransaction.cpp:52
     5[#5](/bitcoin-bitcoin/5/)  0x00005555557a5835 in getrawtransaction (request=...) at rpc/rawtransaction.cpp:200
     6[#6](/bitcoin-bitcoin/6/)  0x00005555557d07d6 in CRPCTable::execute (this=0x555556166d80 <tableRPC>, request=...) at rpc/server.cpp:493
     7[#7](/bitcoin-bitcoin/7/)  0x000055555596ffae in HTTPReq_JSONRPC (req=0x7fffcc0017b0) at httprpc.cpp:188
     8[#8](/bitcoin-bitcoin/8/)  0x00005555557542ae in std::_Function_handler<bool (HTTPRequest*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&), bool (*)(HTTPRequest*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)>::_M_invoke(std::_Any_data const&, HTTPRequest*&&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (__functor=..., __args#0=<unknown type in /home/russ/src/bitcoin/src/bitcoind, CU 0x509db3, DIE 0x562965>, __args#1="")
     9    at /usr/include/c++/5/functional:1857
    10[#9](/bitcoin-bitcoin/9/)  0x000055555597d64a in std::function<bool (HTTPRequest*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)>::operator()(HTTPRequest*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const (
    11    this=0x7fffcc001c10, __args#0=0x7fffcc0017b0, __args#1="") at /usr/include/c++/5/functional:2267
    12[#10](/bitcoin-bitcoin/10/) 0x000055555597b87b in HTTPWorkItem::operator() (this=0x7fffcc001be0) at httpserver.cpp:53
    13[#11](/bitcoin-bitcoin/11/) 0x000055555597e7c4 in WorkQueue<HTTPClosure>::Run (this=0x5555562e3ea0) at httpserver.cpp:112
    14[#12](/bitcoin-bitcoin/12/) 0x00005555559768d7 in HTTPWorkQueueRun (queue=0x5555562e3ea0) at httpserver.cpp:333
    15[#13](/bitcoin-bitcoin/13/) 0x000055555598ed42 in std::_Bind_simple<void (*(WorkQueue<HTTPClosure>*))(WorkQueue<HTTPClosure>*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x5555562e4368) at /usr/include/c++/5/functional:1531
    16[#14](/bitcoin-bitcoin/14/) 0x000055555598e6f8 in std::_Bind_simple<void (*(WorkQueue<HTTPClosure>*))(WorkQueue<HTTPClosure>*)>::operator()() (this=0x5555562e4368) at /usr/include/c++/5/functional:1520
    17[#15](/bitcoin-bitcoin/15/) 0x000055555598de99 in std::thread::_Impl<std::_Bind_simple<void (*(WorkQueue<HTTPClosure>*))(WorkQueue<HTTPClosure>*)> >::_M_run() (this=0x5555562e4350) at /usr/include/c++/5/thread:115
    18[#16](/bitcoin-bitcoin/16/) 0x00007ffff5d82510 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
    19[#17](/bitcoin-bitcoin/17/) 0x00007ffff6d556ba in start_thread (arg=0x7fffc7fff700) at pthread_create.c:333
    20[#18](/bitcoin-bitcoin/18/) 0x00007ffff54e341d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
    

    The error is showing up on travis (https://travis-ci.org/bitcoin/bitcoin/jobs/353440060), but I can also reproduce the problem locally building with building with --enable-debug.

  97. jimpo force-pushed on Mar 19, 2018
  98. ryanofsky commented at 11:34 pm on March 19, 2018: member
    I’ll take another look at this tomorrow to re-ack the new changes. Hopefully I responded to wherever there were questions above.
  99. jimpo force-pushed on Mar 20, 2018
  100. jnewbery commented at 9:43 pm on March 20, 2018: member

    @Sjors

    @jnewbery: any thoughts on how to tackle functional tests?

    My first thought is that this isn’t suitable for a functional test, since it’s an implementation change rather than new functionality. Unit tests seem more appropriate.

    Will try to review this week.

  101. sipa commented at 1:18 am on March 22, 2018: member
    This seems to introduce a cyclic dependency between index/txindex and validation. As these usually are a sign of imperfect abstraction boundaries, I would very much like to avoid them. If the current code is only expected to be temporary, I don’t want to hold things up just for this, but I don’t think it’s very hard to avoid; you can move GetTransaction from validation to rpc/rawtransaction or another utility file. It doesn’t quite belong in validation anyway.
  102. jimpo commented at 1:29 am on March 22, 2018: contributor
    @sipa Totally agree GetTransaction should be moved. I’d prefer to do it in a follow-up PR as this one is already rather large though.
  103. laanwj added this to the "Blockers" column in a project

  104. in src/txdb.cpp:478 in aee37f2b62 outdated
    473+    olddb.WriteBatch(batch_olddb);
    474+
    475+    batch_newdb.Clear();
    476+    batch_olddb.Clear();
    477+
    478+    newdb.CompactRange(begin_key, end_key);
    


    TheBlueMatt commented at 4:13 pm on March 28, 2018:
    Why compact the newdb?

    jimpo commented at 9:38 pm on March 29, 2018:
    Good point, that’s not necessary.
  105. in src/txdb.cpp:524 in aee37f2b62 outdated
    497+    uiInterface.ShowProgress(_("Upgrading txindex database"), 0, true);
    498+    int report_done = 0;
    499+    const size_t batch_size = 1 << 24; // 16 MiB
    500+
    501+    CDBBatch batch_newdb(*this);
    502+    CDBBatch batch_olddb(block_tree_db);
    


    TheBlueMatt commented at 4:13 pm on March 28, 2018:
    Should probably do something to the old DB so that any old versions refuse to start when the txindex data is partially-migrated (not sure how to do it, or if its really possible, but it’d be nice to sidestep the inevitable “I started the new version, and it took forever to start, so I killed it, downgraded again, and now my getrawtransaction-based scripts are all failing” issues).

    jimpo commented at 5:10 pm on March 30, 2018:
  106. in src/index/txindex.cpp:74 in ae578cbee7 outdated
    58+        return true;
    59+    }
    60+
    61+    const CBlockIndex* pindex = LookupBlockIndex(best_block_hash);
    62+    if (!pindex) {
    63+        FatalError("%s: Last block synced by txindex is unknown", __func__);
    


    TheBlueMatt commented at 4:36 pm on March 28, 2018:

    This check is too strict for the BlockConnected validationinterface callback - you really want some variant of the SetBestChain validationinterface callback (which is called when new blocks/header tree is flushed to disik). Practically, I’m not sure what the best way to sovle this is:

    • You could move txindex stuff to a cache ala pcoinsTip and then flush it on some SetBestChain variant (which would need to be tweaked to not have its own timer, which I think we should do anyway, and use the fPeriodicWrite bool or so), but then you’d need to plumb the txindex cache usage back into the FlushStateToDisk flush condition. I think this is probably the easiest approach, but people will probably not be too happy with the circular dependency here. You could get around that with some higher-level “total memory usage tracking” interface, but scope creep….
    • You could move to storing a locator in the txindex DB like the wallet does and then reconnect from that on startup from whatever the locator can find. This has a super weird edge-case where you may have dangling entries pointing to blocks which were stale, which were connected, (ie “written” to disk) but then lost as they were not flushed to disk, but that should be rare enough the overhead of DB entries isnt a big deal, as long as you handle such failed reads appropriately.

    jimpo commented at 9:20 pm on March 29, 2018:
    I don’t understand. This line is in the Init method, not the BlockConnected callback. As long as we never prune entries from the block index, I don’t see when this check would fail.

    TheBlueMatt commented at 4:34 pm on March 30, 2018:

    A block may be connected, but the block index may not be written to disk until much later. Thus, we can run far ahead here. Specifically, we flush in the following order:

    • Blocks are written to disk before connecting them (but not fsync’d, so not guarantees here!). At the same time entries are added to mapBlockIndex/CBlockIndex*s are created, but not flushed to disk)
    • Blocks are validated/connected, generating BlockConnected callbacks.
    • At some point in the future, flushing happens, where we first fsync the blocks written to disk, then write out the mapBlockIndex changes, then write out pcoinsTip/UTXO DB.

    jimpo commented at 3:12 am on April 3, 2018:
    See last two commits (c837c6c & 297f89c) for fix.
  107. in src/index/txindex.cpp:16 in 8ac5ed122e outdated
    12@@ -13,6 +13,8 @@
    13 
    14 constexpr int64_t SYNC_LOG_INTERVAL = 30; // seconds
    15 
    16+std::unique_ptr<TxIndex> g_txindex;
    


    TheBlueMatt commented at 5:27 pm on March 28, 2018:
    It’d be nice to keep these kinds of globals in the same place. At least g_connman is in init.cpp and exposed as extern in net.h, so you could duplicate that (which is “correct” in that init “owns” the object).

    jimpo commented at 7:05 pm on March 28, 2018:

    That’s what I did originally, but that caused problems when building tests. The test binary is not linked against init.cpp, it’s linked against test_bitcoin_main.cpp, which is why g_connman is also defined there. That took me an annoying amount of time to figure out, and by the time I did I was so pissed off I decided to move it to txindex.cpp.

    This whole thing where stuff in init has to get redefined in test_bitcoin_main.cpp if it’s used in the tests seems pretty gross. Do you really thing that’s the right way to do it?


    TheBlueMatt commented at 7:15 pm on March 28, 2018:
    Hmm, yea, thats a bit gross. Still, if thats what we have now, maybe add a comment noting that in both places and at least be consistent?

    jimpo commented at 9:31 pm on March 29, 2018:
    I’d prefer to just move the definition of g_connman to net.cpp or define them in the respective header files. Any reason they need to be extern? @theuni
  108. in src/rpc/rawtransaction.cpp:267 in 013716e1f5 outdated
    277         }
    278     }
    279 
    280+    // Allow txindex to catch up if we need to query it and before we acquire cs_main.
    281+    if (g_txindex && !pblockindex) {
    282+        g_txindex->BlockUntilSyncedToCurrentChain();
    


    TheBlueMatt commented at 5:49 pm on March 28, 2018:
    Also here - should report a different error of some kind if we’re not in sync yet.

    jimpo commented at 5:17 pm on March 30, 2018:
    The error here already is not that useful and does not even indicate if the txindex is disabled. I’d like to fix this in a follow-up PR.
  109. TheBlueMatt commented at 5:50 pm on March 28, 2018: member
    Didnt review tests, also note that doc/files.md should be updated with new datadir paths.
  110. sipa commented at 1:40 am on March 30, 2018: member

    I’d very much like to get rid of the UTXO based block lookup. It’s unreliable (depends on having at least one unspent output left for the transaction), is slow (needs to load and scan the entire block) and probably unused. There is an issue for it somewhere, but I think we’ll want a deprecation for it first.

    One way to deal with it is introducing a new RPC getchaintransaction or so, which strictly only works when txindex is enabled. Then getrawtransaction can be deprecated with a warning saying you can use getmempoolentry for unconfirmed transactions, and getchaintransaction for confirmed ones (if txindex is enabled).

  111. jimpo force-pushed on Mar 30, 2018
  112. jimpo commented at 5:19 pm on March 30, 2018: contributor
    @TheBlueMatt @sipa I’d prefer to handle some of your suggestions about getrawtransaction (better error reporting, no UTXO lookup, moving GetTransaction out of validation, etc.) in a follow-up PR because this one is already 700+ lines. Commit https://github.com/jimpo/bitcoin/commit/2b8ee538bf804bcd2d30942d1254d147e65f2ee3 is a starting point.
  113. sipa commented at 6:15 pm on March 30, 2018: member

    @jimpo Sorry if I was unclear; my message above was just to give some background thoughts and not a request to further change this PR itself.

    I’ll review the code soon.

  114. in src/txdb.cpp:500 in c3b34b1b62 outdated
    505+    // again, the migration will pick up where it left off and sync to the block
    506+    // with hash DB_TXINDEX_BLOCK.
    507+    bool f_legacy_flag = false;
    508+    block_tree_db.ReadFlag("txindex", f_legacy_flag);
    509+    if (f_legacy_flag) {
    510+        if (!block_tree_db.Write(DB_TXINDEX_BLOCK, tip_hash)) {
    


    ryanofsky commented at 5:13 pm on April 3, 2018:

    In commit “!fixup Migration to handle downgrades gracefully.”

    I really like this DB_TXINDEX_BLOCK solution to the problem of recovering from an interrupted upgrade. It’s surprisingly simple.

    Not sure if it’s worth the additional work, but it seems it’d be possible to write some simple unit tests that exercise this code by running it on databases with known states (with “txindex”, with both “txindex” and BLOCK, with BLOCK but no txindex, etc) and checking the results.

  115. in src/index/txindex.cpp:214 in 297f89cad4 outdated
    182@@ -195,6 +183,16 @@ void TxIndex::BlockConnected(const std::shared_ptr<const CBlock>& block, const C
    183     }
    184 }
    185 
    186+void TxIndex::SetBestChain(const CBlockLocator& locator)
    187+{
    188+    if (!m_synced) {
    189+        return;
    190+    }
    191+    if (!m_db->WriteBestBlock(locator)) {
    


    ryanofsky commented at 5:45 pm on April 3, 2018:

    In commit “!fixup Change TxIndexDB to read/write locators instead of hashes.”

    Would it make sense to assert locator.front() == m_best_block_index.GetBlockHash() as a sanity check on event ordering?


    jimpo commented at 10:02 pm on April 4, 2018:
    Yeah, that’s a good idea.
  116. in src/index/txindex.cpp:151 in 297f89cad4 outdated
    155+    if (!m_synced) {
    156+        return;
    157+    }
    158+
    159+    // Ensure block connects to an ancestor of the current best block. This should always be the
    160+    // case assuming BlockConnected is called on the ValidationInterfaces in the proper order.
    


    ryanofsky commented at 6:08 pm on April 3, 2018:

    In commit “[index] Create new TxIndex class.”

    It still seems to me like it would be possible (though unlikely) for these errors to trigger spuriously, even when the events arrive in the right order, but the notification queue gets backlogged: #11857 (review)

    If this is true, the current comment seems misleading, and could mention the edge case.


    jimpo commented at 11:14 pm on April 4, 2018:
    Fixed the comment and error handling.
  117. jimpo force-pushed on Apr 3, 2018
  118. ryanofsky commented at 6:15 pm on April 3, 2018: member
    utACK 297f89cad4808b3499d290bdadb5c4863a94fdd7 with squashed commits. Lots of updates since my previous review, too many to mention, but all in response to comments above.
  119. Sjors commented at 3:34 pm on April 4, 2018: member

    Tested 806b2f1, including interrupting the migration from legacy to the new db.

    If someone wants to do a partial backport, the migration code that removes txindex without requiring a reindex would be useful for folks who regret having set txindex=1 on a node with slow hardware.

  120. ryanofsky commented at 6:50 pm on April 4, 2018: member
    utACK 806b2f1764b6e5a9c7abec887bfa89cd386648d8. Only change since last review is rebase and removing “Last block synced by txindex is unknown” error.
  121. jimpo force-pushed on Apr 4, 2018
  122. jtimon commented at 8:16 pm on April 5, 2018: contributor
    Concept ACK
  123. jtimon commented at 8:20 pm on April 5, 2018: contributor

    warning saying you can use getmempoolentry for unconfirmed transactions @sipa But getmempoolentry doesn’t give you the same data as getrawtransaction (I guess it could be extended to do so optionally).

  124. in src/txdb.cpp:549 in ea8be45ace outdated
    544+            break;
    545+        }
    546+
    547+        // Log progress every 10%.
    548+        if (++count % 256 == 0) {
    549+            uint32_t high = 0x100 * *key.second.begin() + *(key.second.begin() + 1);
    


    jamesob commented at 5:51 pm on April 11, 2018:
    If you end up having to change this PR for other reasons, might be worth leaving a comment to note that this works because leveldb is sorted by key and that you’re making use of the first two bytes of the txid here. It took me a little head-scratching to see what was going on here (thanks to @ryanofsky for the help).
  125. in src/index/txindex.cpp:214 in ea8be45ace outdated
    209+                  __func__, locator_tip_hash.ToString(),
    210+                  best_block_index->GetBlockHash().ToString());
    211+        return;
    212+    }
    213+
    214+    if (!m_db->WriteBestBlock(locator)) {
    


    jamesob commented at 8:16 pm on April 11, 2018:
    If we made this call in TxIndex::BlockConnected it seems like we could avoid defining this entire function. Is the reason you’re not doing it there for efficiency’s sake (e.g. maybe you’re worried about the expense of constructing a locator)?

    jimpo commented at 6:32 pm on April 12, 2018:
    Yeah, there’s a few reasons. 1) It’s how CWallet works, which I looked at as a model. 2) I’d rather not LOCK(cs_main) on every BlockConnected if avoidable. We wouldn’t even need the lock in this method if SetBestChain was called with the tip block index as an argument. 3) I think this gives better efficiency of block locator hits. So if the tip of the block locator is always the best block index entry that has been flushed to disk, when the TxIndex restarts, it always picks up from the best possible point. Whereas if the locator is written on every BlockConnected call, because there are exponentially increasing jumps in the locator, you might only find a block that is further back than that one. (Hopefully that makes sense).
  126. in src/txdb.h:153 in ea8be45ace outdated
    147+
    148+    /// Read block locator of the chain that the txindex is in sync with.
    149+    bool ReadBestBlock(CBlockLocator& hash) const;
    150+
    151+    /// Write block locator of the chain that the txindex is in sync with.
    152+    bool WriteBestBlock(const CBlockLocator& locator);
    


    jamesob commented at 8:22 pm on April 11, 2018:
    Since you’re only ever using the tip of this locator to populate m_best_block_index in TxIndex::Init(), have you considered storing a single blockhash instead of a locator here? I guess there may be some beneficial future-proofing to having a locator on hand, but it’s a bit more expensive to maintain (c.f. my comment on possibly eliminating TxIndex::SetBestChain).

    jimpo commented at 6:16 pm on April 12, 2018:
    So TxIndex::Init() uses FindForkInGlobalIndex to populate m_best_block_index, which may not necessarily be the tip of the locator. One case where it might not be is when the active chain locator is written at the end of ThreadSync, which may contain locator entries for blocks that have not been flushed to disk.
  127. in src/index/txindex.cpp:233 in ea8be45ace outdated
    228+        // Skip the queue-draining stuff if we know we're caught up with
    229+        // chainActive.Tip().
    230+        LOCK(cs_main);
    231+        const CBlockIndex* chain_tip = chainActive.Tip();
    232+        const CBlockIndex* best_block_index = m_best_block_index.load();
    233+        if (best_block_index->GetAncestor(chain_tip->nHeight) == chain_tip) {
    


    jamesob commented at 8:30 pm on April 11, 2018:
    Just curious: under what circumstances would a chainActive best_block_index have ever surpassed chainActive.Tip()?

    jimpo commented at 6:23 pm on April 12, 2018:
    One scenario is if ActivateBestChainStep attempts to reorg to an invalid chain. So it would disconnect a few blocks, then realize that one of the blocks it tries to connect is invalid, leaving the tip at a lower height than it started. I suppose InvalidateBlock could cause this too.
  128. in src/index/txindex.cpp:289 in ea8be45ace outdated
    284+                                std::bind(&TxIndex::ThreadSync, this));
    285+}
    286+
    287+void TxIndex::Stop()
    288+{
    289+    UnregisterValidationInterface(this);
    


    ryanofsky commented at 9:02 pm on April 11, 2018:
    @jamesob got me looking into this code, but since Stop() is called from the destructor, is it ok to call this here, or is there a problem like #12647 (review)?
  129. jamesob commented at 9:06 pm on April 11, 2018: member
    Partially through a review: I’ve finished looking at TxIndex and some related parts of TxIndexDB. Looks great so far, very excited for this change.
  130. in src/txdb.cpp:457 in ea8be45ace outdated
    452+
    453+    // Read might have failed either because key does not exist or due to an error.
    454+    // If the former, return value should still be true.
    455+    if (!Exists(DB_BEST_BLOCK)) {
    456+        locator.SetNull();
    457+        return true;
    


    jamesob commented at 3:48 pm on April 12, 2018:
    nit: I think this return value is a bit confusing; if we didn’t actually read the best block, why would we return true (regardless of the reason)?

    jimpo commented at 5:25 am on April 16, 2018:
    Because we successfully read from the database – there was just no locator written, which in this case means no blocks have been written to the index yet.
  131. jamesob approved
  132. jamesob commented at 9:01 pm on April 12, 2018: member

    Tested ACK https://github.com/bitcoin/bitcoin/pull/11857/commits/ea8be45ace75b649584c163deca3051c4f33aa16

    This change is really nicely implemented. My comments are non-blocking - the code as-is reads well and represents a big improvement. txindex usage while testing this branch has been pleasant relative to its normally fickle behavior; I like that the index doesn’t get blown away when toggling txindex=.

    Here’s the manual test plan I followed:

    • Start migration of existing (legacy) blocks/index database, interrupt it (on testnet)
      • Continue interrupted migration of legacy blocks/index database (on testnet)
    • Build indexes/txindex from scratch (on testnet)
    • Retrieve a testnet transaction:
      0$ which rpct
      1
      2rpct () {
      3  ./src/bitcoin-cli -rpcuser=foo -rpcpassword=xxx -rpcport=18332 "$@"
      4}
      5
      6$ rpct getrawtransaction 9c86ecad5ecc447ef1540e13911f6b917b771d1780693613889f14efa7d6fc01
      7
      8020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff23030eb91304ac8bcf5a2f44504f4f4c2e544f502f484e2f0202f6d36693010000000000ffffffff028a910405000000001976a9140ac82b91aa7ff1b07c9673f28c8ced6c7045c41388ac0000000000000000266a24aa21a9eda00067c4917832fb72c8db625395da31e0e9e66d32d8385bf3d5bc1b036880e00120000000000000000000000000000000000000000000000000000000000000000000000000
      
    • After building indexes/txindex, retrieve a transaction from a recent testnet block:
      0 $ rpct getrawtransaction $(rpct getblock $(rpct getbestblockhash) | jq -r ".tx[0]")
      1
      2 020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff230310b913042791cf5a2f44504f4f4c2e544f502f484e2f0202f6d3dea9010000000000ffffffff029d61af04000000001976a9140ac82b91aa7ff1b07c9673f28c8ced6c7045c41388ac0000000000000000266a24aa21a9edbdf213b3f829be29346dece981a7b90bad893358a0d7d95163887b6960760d150120000000000000000000000000000000000000000000000000000000000000000000000000
      
    • After building indexes/txindex, retrieve a transaction from an older testnet block:
      0 $ rpct getrawtransaction $(rpct getblock $(rpct getblockhash 2) | jq -r ".tx[0]")
      1
      2 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0432e7494d010e062f503253482fffffffff0100f2052a010000002321038a7f6ef1c8ca0c588aa53fa860128077c9e6c11e6830f4d7ee4e763a56b7718fac00000000
      
    • Flip txindex from 1 to 0 and back to 1, ensure full reindexing doesn’t happen (on mainnet)
    • Do migration of existing legacy blocks/index database (on mainnet)
    • After building indexes/txindex, set txindex=0, restart, and run (on mainnet)
      0$ rpc getrawtransaction 1242739fa76fe5f84fdcf2a7557cd059bc89969a0e9b43ded8f01a97ecd8f2db
      1
      2020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4b03c7e607048413cf5a622f4254432e434f4d2ffabe6d6d37bccddf8230a0579980821ee6ad826521097fa2f3041c7492c1988eab282fc7010000000000000038014b294f56000000000000ffffffff02502ddd4a000000001976a91478ce48f88c94df3762da89dc8498205373a8ce6f88ac0000000000000000266a24aa21a9ed5a272ba104178bac1a18f9af13f1f13a8f727d2726f58f582d08d4b7076b8b7f0120000000000000000000000000000000000000000000000000000000000000000000000000
      3
      4$ rpc getrawtransaction 1242739fa76fe5f84fdcf2a7557cd059bc89969a0e9b43ded8f01a97ecd8f2db | sha256sum
      5
      67ef9d4596788ca7bbc51bc0e82a0588c78d0115af91d3f73632085716af10cbf  -
      
    • Set txindex back to 1, restart, ensure no full reindexing happens, and run (on mainnet)
      0$ rpc getrawtransaction 1242739fa76fe5f84fdcf2a7557cd059bc89969a0e9b43ded8f01a97ecd8f2db | sha256sum
      1
      27ef9d4596788ca7bbc51bc0e82a0588c78d0115af91d3f73632085716af10cbf  -
      
    • Run -reindex after having built indexes/txindex (on testnet)
  133. jimpo commented at 6:09 pm on April 16, 2018: contributor
    @TheBlueMatt Can you please take a look at the last few commits?
  134. TheBlueMatt commented at 5:11 pm on April 17, 2018: member

    The fixup commits look fine, I think, except that you should probably add a commit to FlushStateToDisk to call SetBestChain in sync with fDoFullFlush always instead of it having its own timer. This shouldn’t effect the wallet as in !fDoFullFlush cases we’ll be replaying the blocks on restart anyway, but for this stuff would otherwise mean you still have a race.

    Will do a full re-review when things are squashed/rebased.

  135. jonasschnelli commented at 7:53 am on April 18, 2018: contributor
    Needs rebase
  136. [db] Create separate database for txindex.
    The new TxIndexDB class will be used by a future commit in this
    change set.
    236b72019a
  137. jimpo force-pushed on Apr 18, 2018
  138. jimpo commented at 7:19 pm on April 18, 2018: contributor

    @TheBlueMatt While that sounds reasonable, I don’t think there’s a race here because we are writing a locator and the TxIndex will sync from the locator fork point to the chain tip on restart. Am I missing something?

    I’d like to not expand the scope of the this PR further to modifying flushing logic.

  139. TheBlueMatt commented at 7:23 pm on April 18, 2018: member
    Indeed, that shouldn’t be a real race, but could cuase needless additional sync on startup. Feel free to put it in a second PR, but lets not forget about it.
  140. jimpo force-pushed on Apr 19, 2018
  141. [db] Migration for txindex data to new, separate database. fcc074eedd
  142. [index] Create new TxIndex class.
    The TxIndex will be responsible for building the transaction index
    concurrently with the main validation thread by implementing
    ValidationInterface. This does not process blocks concurrently yet.
    387a1d6be9
  143. [index] TxIndex initial sync thread.
    TxIndex starts up a background thread to get in sync with the block
    index before blocks are processed through the ValidationInterface.
    7056a07112
  144. [index] Allow TxIndex sync thread to be interrupted. 239d1af07e
  145. [index] TxIndex method to wait until caught up.
    In order to preserve getrawtransaction RPC behavior, there needs to be
    a way for a thread to ensure the transaction index is in sync with the
    current state of the blockchain.
    0ec7bbae9f
  146. [init] Initialize and start TxIndex in init code. c8dad0fa73
  147. [validation] Replace tx index code in validation code with TxIndex. 3a203d4af5
  148. [index] Move disk IO logic from GetTransaction to TxIndex::FindTx. 1a8192b958
  149. [rpc] Public interfaces to GetTransaction block until synced.
    Now that the transaction index is updated asynchronously, in order to
    preserve the current behavior of public interfaces, the code blocks
    until the transaction index is caught up with the current state of the
    blockchain.
    ce38b6bd3f
  150. [test] Simple unit test for TxIndex. 15533d0684
  151. [doc] Include txindex changes in the release notes. 523dd763bf
  152. jimpo force-pushed on Apr 19, 2018
  153. in src/txdb.cpp:25 in fcc074eedd outdated
    21@@ -22,6 +22,7 @@ static const char DB_COIN = 'C';
    22 static const char DB_COINS = 'c';
    23 static const char DB_BLOCK_FILES = 'f';
    24 static const char DB_TXINDEX = 't';
    25+static const char DB_TXINDEX_BLOCK = 'T';
    


    TheBlueMatt commented at 3:09 pm on April 19, 2018:
    Can we get a better name here?

    jimpo commented at 9:50 pm on April 19, 2018:
    Suggest one.
  154. in src/txdb.cpp:571 in fcc074eedd outdated
    566+        }
    567+        batch_newdb.Write(key, value);
    568+        batch_olddb.Erase(key);
    569+
    570+        if (batch_newdb.SizeEstimate() > batch_size || batch_olddb.SizeEstimate() > batch_size) {
    571+            WriteTxIndexMigrationBatches(*this, block_tree_db,
    


    TheBlueMatt commented at 3:41 pm on April 19, 2018:
    Writing a batch deleting the thing the iterator is pointing to before calling Next() just feels broken to me. It should be fine cause iterators appear to always take a snapshot, but could you at least add a comment noting that we rely on this being safe (and also do in CCoinsViewDB::Upgrade).

    jimpo commented at 10:24 pm on April 19, 2018:
    Seems like overkill to me to leave a comment for documented LevelDB behavior, but sure.
  155. in src/txdb.cpp:454 in 236b72019a outdated
    449+        return true;
    450+    }
    451+
    452+    // Read might have failed either because key does not exist or due to an error.
    453+    // If the former, return value should still be true.
    454+    if (!Exists(DB_BEST_BLOCK)) {
    


    TheBlueMatt commented at 4:11 pm on April 19, 2018:
    I dont think this is right - if Read fails due to a DB error it will throw a dbwrapper_error, so we’ll never get here anyway. It also makes the return value super confusing. Can we just make it have return semantics the same as every other DB function?

    jimpo commented at 9:57 pm on April 19, 2018:
    I’ll change it, but I think the return value of CDBWrapper::Read() is super confusing on it’s own. It means either that the key was not found or that the value at that key could not be deserialized into the provided struct (and that the return value is now in an unspecified state).
  156. in src/index/txindex.cpp:57 in 523dd763bf
    52+    if (!m_db->ReadBestBlock(locator)) {
    53+        FatalError("%s: Failed to read from tx index database", __func__);
    54+        return false;
    55+    }
    56+
    57+    m_best_block_index = FindForkInGlobalIndex(chainActive, locator);
    


    TheBlueMatt commented at 5:13 pm on April 19, 2018:
    It’d be nice to check that the genesis block matches (if locator was non-null) as the locator will always have it. Not a big deal since users shouldnt be moving the txinded db to anothter datadir, but…users love to do crazy things and break things.

    jimpo commented at 10:22 pm on April 19, 2018:
    Eh, what’s the right behavior in that case? Seems to me like it would be to sync the txindex from genesis, which is exactly what this does.
  157. in src/index/txindex.cpp:85 in 523dd763bf
    80+    const CBlockIndex* pindex = m_best_block_index.load();
    81+    if (!m_synced) {
    82+        auto& consensus_params = Params().GetConsensus();
    83+
    84+        int64_t last_log_time = 0;
    85+        while (true) {
    


    TheBlueMatt commented at 5:22 pm on April 19, 2018:
    It’d be really nice to write the locator out at various points during this loop, as otherwise if you start a txindex sync and then kill your bitcoind while it takes its hour building the txindex you’ll have to start against from scratch on next startup.

    jimpo commented at 10:21 pm on April 19, 2018:
    Good point, done.
  158. TheBlueMatt commented at 6:06 pm on April 19, 2018: member
    utACK 523dd763bfe39150e369332e415215d33f2a3ef5 despite the comments. I think its correct as-is now, but there’s a few things that I think are rough API edges that would be nice to get cleanup on.
  159. jimpo commented at 0:12 am on April 20, 2018: contributor
    Closing and re-opening in #13033 because this has stopped loading for people.
  160. jimpo closed this on Apr 20, 2018

  161. fanquake removed this from the "Blockers" column in a project

  162. sipa referenced this in commit a07e8caa5d on Apr 26, 2018
  163. laanwj referenced this in commit 598db389c3 on May 2, 2018
  164. PastaPastaPasta referenced this in commit d1193cf4c4 on Jun 10, 2020
  165. PastaPastaPasta referenced this in commit 49089b7748 on Jun 12, 2020
  166. PastaPastaPasta referenced this in commit 3a7480f7c5 on Jun 13, 2020
  167. PastaPastaPasta referenced this in commit f25b9a7447 on Jun 14, 2020
  168. PastaPastaPasta referenced this in commit 9b825c8bb7 on Jun 14, 2020
  169. UdjinM6 referenced this in commit 2924424924 on May 21, 2021
  170. UdjinM6 referenced this in commit 6f4ea3388e on May 21, 2021
  171. UdjinM6 referenced this in commit f3ce0156a1 on May 25, 2021
  172. UdjinM6 referenced this in commit 7ff6515c88 on May 25, 2021
  173. UdjinM6 referenced this in commit 0f9f133f78 on Jun 5, 2021
  174. UdjinM6 referenced this in commit 531bc38e59 on Jun 5, 2021
  175. UdjinM6 referenced this in commit 56bd5aea1b on Jun 5, 2021
  176. gades referenced this in commit 23fa5cdc7b on Jun 24, 2021
  177. MarcoFalke locked this on Sep 8, 2021

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-07-05 22:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me