diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/db.cpp | 41 | ||||
| -rw-r--r-- | src/main.cpp | 75 | ||||
| -rw-r--r-- | src/main.h | 2 | ||||
| -rw-r--r-- | src/main_gui.cpp | 5 |
4 files changed, 123 insertions, 0 deletions
| @@ -435,6 +435,47 @@ bool CTxDB::LoadBlockIndex() | |||
| 435 | nBestHeight = pindexBest->nHeight; | 435 | nBestHeight = pindexBest->nHeight; |
| 436 | printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); | 436 | printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); |
| 437 | 437 | ||
| 438 | // Verify the tail of the best chain, and fall back to the last good block | ||
| 439 | // if any of it fails. | ||
| 440 | // | ||
| 441 | // A node that accepted blocks its current rules would reject -- because it | ||
| 442 | // was running an older build, or because the tip it landed on was never | ||
| 443 | // valid -- has no way to notice on its own. It keeps the branch, keeps | ||
| 444 | // building on it, and keeps mining coins that the rest of the network | ||
| 445 | // throws away. The only cure used to be deleting blk*.dat by hand, which | ||
| 446 | // requires knowing something is wrong in the first place. That is how a | ||
| 447 | // node here once mined a 323-block fork for five days. | ||
| 448 | // | ||
| 449 | // Checking every block on every start would mean a RandomX hash per block, | ||
| 450 | // so only the tail is checked by default. /checkblocks=0 does the lot. | ||
| 451 | int nCheckBlocks = nCheckBlocksOnLoad; | ||
| 452 | CBlockIndex* pindexFork = NULL; | ||
| 453 | int nChecked = 0; | ||
| 454 | for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) | ||
| 455 | { | ||
| 456 | if (nCheckBlocks > 0 && nChecked >= nCheckBlocks) | ||
| 457 | break; | ||
| 458 | nChecked++; | ||
| 459 | CBlock block; | ||
| 460 | if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) | ||
| 461 | return error("LoadBlockIndex() : ReadFromDisk failed at height %d", pindex->nHeight); | ||
| 462 | if (!block.CheckBlock()) | ||
| 463 | { | ||
| 464 | printf("LoadBlockIndex() : *** bad block at height %d, hash=%s\n", | ||
| 465 | pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,14).c_str()); | ||
| 466 | // Keep going: an older bad block further back wins, because | ||
| 467 | // everything after it has to come off too. | ||
| 468 | pindexFork = pindex->pprev; | ||
| 469 | } | ||
| 470 | } | ||
| 471 | printf("LoadBlockIndex(): verified %d block(s)\n", nChecked); | ||
| 472 | |||
| 473 | // The repair itself happens in the caller, after this handle is closed. | ||
| 474 | // The cursor above is still open and holds read locks, so a write | ||
| 475 | // transaction opened here waits on it forever -- measured: the node came | ||
| 476 | // up, printed the diagnosis, and hung with no error anywhere. | ||
| 477 | pindexBadChainFork = pindexFork; | ||
| 478 | |||
| 438 | return true; | 479 | return true; |
| 439 | } | 480 | } |
| 440 | 481 | ||
diff --git a/src/main.cpp b/src/main.cpp index 15805ca..dbb2c41 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -49,6 +49,15 @@ uint256 hashGenesisMerkleRoot("0x1a45b4482532abb29b10e234d3f13132230525a339ecea9 | |||
| 49 | CBlockIndex* pindexGenesisBlock = NULL; | 49 | CBlockIndex* pindexGenesisBlock = NULL; |
| 50 | int nBestHeight = -1; | 50 | int nBestHeight = -1; |
| 51 | uint256 hashBestChain = 0; | 51 | uint256 hashBestChain = 0; |
| 52 | // Blocks at the tip to re-verify on startup. 0 means the whole chain. 288 is | ||
| 53 | // about ten hours at a two-minute target -- far enough back to catch a bad tip, | ||
| 54 | // short enough that startup does not pay a RandomX hash per block for the whole | ||
| 55 | // history. /checkblocks=N overrides it. | ||
| 56 | int nCheckBlocksOnLoad = 288; | ||
| 57 | // Set by CTxDB::LoadBlockIndex when the tail of the chain fails verification. | ||
| 58 | // The repair runs in LoadBlockIndex() below, once that read-only handle and its | ||
| 59 | // cursor are closed. | ||
| 60 | CBlockIndex* pindexBadChainFork = NULL; | ||
| 52 | CBlockIndex* pindexBest = NULL; | 61 | CBlockIndex* pindexBest = NULL; |
| 53 | 62 | ||
| 54 | map<uint256, CBlock*> mapOrphanBlocks; | 63 | map<uint256, CBlock*> mapOrphanBlocks; |
| @@ -1577,6 +1586,72 @@ bool LoadBlockIndex(bool fAllowNew) | |||
| 1577 | return false; | 1586 | return false; |
| 1578 | txdb.Close(); | 1587 | txdb.Close(); |
| 1579 | 1588 | ||
| 1589 | // Repair a bad tip found during verification. | ||
| 1590 | // | ||
| 1591 | // This has to happen after the handle above is closed. CTxDB::LoadBlockIndex | ||
| 1592 | // walks the index with a cursor it never closes, so it is still holding read | ||
| 1593 | // locks -- a write transaction opened alongside it waits on them forever. | ||
| 1594 | // Measured, not guessed: the node came up, printed the diagnosis, and hung | ||
| 1595 | // there with nothing in any log. | ||
| 1596 | if (pindexBadChainFork) | ||
| 1597 | { | ||
| 1598 | CBlockIndex* pindexFork = pindexBadChainFork; | ||
| 1599 | pindexBadChainFork = NULL; | ||
| 1600 | |||
| 1601 | printf("LoadBlockIndex() : *** moving best chain back to height %d, discarding %d block(s)\n", | ||
| 1602 | pindexFork->nHeight, nBestHeight - pindexFork->nHeight); | ||
| 1603 | |||
| 1604 | vector<CBlockIndex*> vDisconnect; | ||
| 1605 | for (CBlockIndex* pindex = pindexBest; pindex && pindex != pindexFork; pindex = pindex->pprev) | ||
| 1606 | vDisconnect.push_back(pindex); | ||
| 1607 | |||
| 1608 | bool fRolledBack = false; | ||
| 1609 | try | ||
| 1610 | { | ||
| 1611 | CTxDB txdbWrite; | ||
| 1612 | txdbWrite.TxnBegin(); | ||
| 1613 | foreach(CBlockIndex* pindex, vDisconnect) | ||
| 1614 | { | ||
| 1615 | CBlock block; | ||
| 1616 | if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) | ||
| 1617 | throw runtime_error("ReadFromDisk for disconnect failed"); | ||
| 1618 | if (!block.DisconnectBlock(txdbWrite, pindex)) | ||
| 1619 | throw runtime_error("DisconnectBlock failed"); | ||
| 1620 | } | ||
| 1621 | if (!txdbWrite.WriteHashBestChain(pindexFork->GetBlockHash())) | ||
| 1622 | throw runtime_error("WriteHashBestChain failed"); | ||
| 1623 | txdbWrite.TxnCommit(); | ||
| 1624 | txdbWrite.Close(); | ||
| 1625 | fRolledBack = true; | ||
| 1626 | } | ||
| 1627 | catch (const std::exception& e) | ||
| 1628 | { | ||
| 1629 | printf("LoadBlockIndex() : rollback failed: %s\n", e.what()); | ||
| 1630 | } | ||
| 1631 | catch (...) | ||
| 1632 | { | ||
| 1633 | printf("LoadBlockIndex() : rollback failed\n"); | ||
| 1634 | } | ||
| 1635 | |||
| 1636 | if (fRolledBack) | ||
| 1637 | { | ||
| 1638 | foreach(CBlockIndex* pindex, vDisconnect) | ||
| 1639 | if (pindex->pprev) | ||
| 1640 | pindex->pprev->pnext = NULL; | ||
| 1641 | |||
| 1642 | pindexBest = pindexFork; | ||
| 1643 | hashBestChain = pindexBest->GetBlockHash(); | ||
| 1644 | nBestHeight = pindexBest->nHeight; | ||
| 1645 | printf("LoadBlockIndex() : now at height %d, will re-download from peers\n", nBestHeight); | ||
| 1646 | } | ||
| 1647 | else | ||
| 1648 | { | ||
| 1649 | // Start anyway. The operator needs a running node to read this from. | ||
| 1650 | printf("LoadBlockIndex() : *** still on the bad chain at height %d -- back up the data directory and report this\n", | ||
| 1651 | nBestHeight); | ||
| 1652 | } | ||
| 1653 | } | ||
| 1654 | |||
| 1580 | // | 1655 | // |
| 1581 | // Init with genesis block | 1656 | // Init with genesis block |
| 1582 | // | 1657 | // |
| @@ -52,6 +52,8 @@ extern unsigned int GENESIS_NONCE; | |||
| 52 | extern CBlockIndex* pindexGenesisBlock; | 52 | extern CBlockIndex* pindexGenesisBlock; |
| 53 | extern int nBestHeight; | 53 | extern int nBestHeight; |
| 54 | extern uint256 hashBestChain; | 54 | extern uint256 hashBestChain; |
| 55 | extern int nCheckBlocksOnLoad; | ||
| 56 | extern CBlockIndex* pindexBadChainFork; | ||
| 55 | extern CBlockIndex* pindexBest; | 57 | extern CBlockIndex* pindexBest; |
| 56 | extern unsigned int nTransactionsUpdated; | 58 | extern unsigned int nTransactionsUpdated; |
| 57 | extern string strSetDataDir; | 59 | extern string strSetDataDir; |
diff --git a/src/main_gui.cpp b/src/main_gui.cpp index 852ad4d..99bcb6f 100644 --- a/src/main_gui.cpp +++ b/src/main_gui.cpp | |||
| @@ -54,6 +54,7 @@ static void PrintUsage() | |||
| 54 | printf(" /participant=POOL_BTF_ADDRESS\n"); | 54 | printf(" /participant=POOL_BTF_ADDRESS\n"); |
| 55 | printf(" /solomine\n"); | 55 | printf(" /solomine\n"); |
| 56 | printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n"); | 56 | printf(" /genproclimit=N (mining threads; 0 or absent = every core but one)\n"); |
| 57 | printf(" /checkblocks=N (blocks re-verified at startup, default 288, 0 = all)\n"); | ||
| 57 | printf("\n"); | 58 | printf("\n"); |
| 58 | printf("Pool operator announcement:\n"); | 59 | printf("Pool operator announcement:\n"); |
| 59 | printf(" /poolname=NAME\n"); | 60 | printf(" /poolname=NAME\n"); |
| @@ -101,6 +102,10 @@ static void ParseStartupArguments(int argc, char* argv[]) | |||
| 101 | if (arg(argc,argv,"/solomine") || arg(argc,argv,"-solomine")) | 102 | if (arg(argc,argv,"/solomine") || arg(argc,argv,"-solomine")) |
| 102 | fSoloMineTest = true; | 103 | fSoloMineTest = true; |
| 103 | 104 | ||
| 105 | string strCheckBlocks = argval2(argc, argv, "/checkblocks", "-checkblocks"); | ||
| 106 | if (!strCheckBlocks.empty()) | ||
| 107 | nCheckBlocksOnLoad = atoi(strCheckBlocks.c_str()); | ||
| 108 | |||
| 104 | // /genproclimit=N -- threads to hash with. 0 or absent means automatic, | 109 | // /genproclimit=N -- threads to hash with. 0 or absent means automatic, |
| 105 | // which is every core but one. Named after Bitcoin's own option so it reads | 110 | // which is every core but one. Named after Bitcoin's own option so it reads |
| 106 | // familiarly to anyone who has run one of these before. | 111 | // familiarly to anyone who has run one of these before. |