From 47caaff32d2337c9ff84c716fbb86688036de1b5 Mon Sep 17 00:00:00 2001 From: Thomas Brady Date: Wed, 16 Oct 2024 16:49:07 -0700 Subject: [PATCH] Adds CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING config option to skip application of failed transactions and signature verification for all transactions during catchup. --- src/catchup/ApplyCheckpointWork.cpp | 83 ++++++++++++++++- src/catchup/ApplyCheckpointWork.h | 31 ++++--- src/catchup/CatchupConfiguration.h | 15 ++-- src/catchup/CatchupWork.h | 14 +-- src/catchup/DownloadApplyTxsWork.cpp | 90 +++++++++++++++---- src/herder/LedgerCloseData.cpp | 15 ++++ src/herder/LedgerCloseData.h | 19 +++- src/historywork/GetAndUnzipRemoteFileWork.cpp | 89 ++++++++++++++---- src/historywork/GetAndUnzipRemoteFileWork.h | 4 +- src/historywork/GetRemoteFileWork.cpp | 1 + src/ledger/LedgerManagerImpl.cpp | 41 +++++++-- src/ledger/LedgerManagerImpl.h | 3 +- .../test/LedgerCloseMetaStreamTests.cpp | 5 ++ src/main/Config.cpp | 7 ++ src/main/Config.h | 7 ++ src/transactions/MutableTransactionResult.cpp | 30 +++++++ src/transactions/MutableTransactionResult.h | 21 ++++- src/transactions/SignatureChecker.h | 33 ++++++- src/transactions/TransactionFrame.cpp | 46 ++++++++-- 19 files changed, 476 insertions(+), 78 deletions(-) diff --git a/src/catchup/ApplyCheckpointWork.cpp b/src/catchup/ApplyCheckpointWork.cpp index 4823fb5bc0..96face9b7a 100644 --- a/src/catchup/ApplyCheckpointWork.cpp +++ b/src/catchup/ApplyCheckpointWork.cpp @@ -93,6 +93,42 @@ ApplyCheckpointWork::openInputFiles() mTxIn.open(ti.localPath_nogz()); mTxHistoryEntry = TransactionHistoryEntry(); mHeaderHistoryEntry = LedgerHeaderHistoryEntry(); +#ifdef BUILD_TESTS + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING) + { + mTxResultIn = std::make_optional(); + FileTransferInfo tri(mDownloadDir, FileType::HISTORY_FILE_TYPE_RESULTS, + mCheckpoint); + if (!tri.localPath_nogz().empty() && + std::filesystem::exists(tri.localPath_nogz())) + { + CLOG_DEBUG(History, "Replaying transaction results from {}", + tri.localPath_nogz()); + + try + { + mTxResultIn->open(tri.localPath_nogz()); + } + catch (std::exception const& e) + { + CLOG_DEBUG(History, + "Failed to open transaction results file: {}. All " + "transactions will be applied.", + e.what()); + } + mTxHistoryResultEntry = + std::make_optional(); + } + else + { + CLOG_DEBUG(History, + "Results file {} not found for checkpoint {} . All " + "transactions will be applied for this checkpoint.", + tri.localPath_nogz(), mCheckpoint); + mTxHistoryResultEntry = std::nullopt; + } + } +#endif mFilesOpen = true; } @@ -138,6 +174,43 @@ ApplyCheckpointWork::getCurrentTxSet() return TxSetXDRFrame::makeEmpty(lm.getLastClosedLedgerHeader()); } +#ifdef BUILD_TESTS +std::optional +ApplyCheckpointWork::getCurrentTxResultSet() +{ + ZoneScoped; + auto& lm = mApp.getLedgerManager(); + auto seq = lm.getLastClosedLedgerNum() + 1; + // Check mTxResultSet prior to loading next result set. + // This order is important because it accounts for ledger "gaps" + // in the history archives (which are caused by ledgers with empty tx + // sets, as those are not uploaded). + while (mTxResultIn && mTxResultIn->readOne(*mTxHistoryResultEntry)) + { + if (mTxHistoryResultEntry) + { + if (mTxHistoryResultEntry->ledgerSeq < seq) + { + CLOG_DEBUG(History, "Advancing past txresultset for ledger {}", + mTxHistoryResultEntry->ledgerSeq); + } + else if (mTxHistoryResultEntry->ledgerSeq > seq) + { + break; + } + else + { + releaseAssert(mTxHistoryResultEntry->ledgerSeq == seq); + CLOG_DEBUG(History, "Loaded txresultset for ledger {}", seq); + return std::make_optional(mTxHistoryResultEntry->txResultSet); + } + } + } + CLOG_DEBUG(History, "No txresultset for ledger {}", seq); + return std::nullopt; +} +#endif // BUILD_TESTS + std::shared_ptr ApplyCheckpointWork::getNextLedgerCloseData() { @@ -216,6 +289,14 @@ ApplyCheckpointWork::getNextLedgerCloseData() CLOG_DEBUG(History, "Ledger {} has {} transactions", header.ledgerSeq, txset->sizeTxTotal()); + std::optional txres = std::nullopt; +#ifdef BUILD_TESTS + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING) + { + txres = getCurrentTxResultSet(); + } +#endif + // We've verified the ledgerHeader (in the "trusted part of history" // sense) in CATCHUP_VERIFY phase; we now need to check that the // txhash we're about to apply is the one denoted by that ledger @@ -246,7 +327,7 @@ ApplyCheckpointWork::getNextLedgerCloseData() return std::make_shared( header.ledgerSeq, txset, header.scpValue, - std::make_optional(mHeaderHistoryEntry.hash)); + std::make_optional(mHeaderHistoryEntry.hash), txres); } BasicWork::State diff --git a/src/catchup/ApplyCheckpointWork.h b/src/catchup/ApplyCheckpointWork.h index 93c4168b21..f85f8c52bd 100644 --- a/src/catchup/ApplyCheckpointWork.h +++ b/src/catchup/ApplyCheckpointWork.h @@ -21,20 +21,24 @@ class TmpDir; struct LedgerHeaderHistoryEntry; /** - * This class is responsible for applying transactions stored in files on - * temporary directory (downloadDir) to local ledger. It requires two sets of - * files - ledgers and transactions - int .xdr format. Transaction files are - * used to read transactions that will be used and ledger files are used to + * This class is responsible for applying transactions stored in files in the + * temporary directory (downloadDir) to local the ledger. It requires two sets + * of files - ledgers and transactions - in .xdr format. Transaction files are + * used to read transactions that will be applied and ledger files are used to * check if ledger hashes are matching. * + * It may also require a third set of files - transaction results - to use in + * accelerated replay, where failed transactions are not applied and successful + * transactions are applied without verifying their signatures. + * * In each run it skips or applies transactions from one ledger. Skipping occurs - * when ledger to be applied is older than LCL from local ledger. At LCL - * boundary checks are made to confirm that ledgers from files knit up with - * LCL. If everything is OK, an apply ledger operation is performed. Then - * another check is made - if new local ledger matches corresponding ledger from - * file. + * when the ledger to be applied is older than the LCL of the local ledger. At + * LCL, boundary checks are made to confirm that the ledgers from the files knit + * up with LCL. If everything is OK, an apply ledger operation is performed. + * Then another check is made - if the new local ledger matches corresponding + * the ledger from file. * - * Constructor of this class takes some important parameters: + * The constructor of this class takes some important parameters: * * downloadDir - directory containing ledger and transaction files * * range - LedgerRange to apply, must be checkpoint-aligned, * and cover at most one checkpoint. @@ -49,6 +53,10 @@ class ApplyCheckpointWork : public BasicWork XDRInputFileStream mHdrIn; XDRInputFileStream mTxIn; TransactionHistoryEntry mTxHistoryEntry; +#ifdef BUILD_TESTS + std::optional mTxResultIn; + std::optional mTxHistoryResultEntry; +#endif // BUILD_TESTS LedgerHeaderHistoryEntry mHeaderHistoryEntry; OnFailureCallback mOnFailure; @@ -57,6 +65,9 @@ class ApplyCheckpointWork : public BasicWork std::shared_ptr mConditionalWork; TxSetXDRFrameConstPtr getCurrentTxSet(); +#ifdef BUILD_TESTS + std::optional getCurrentTxResultSet(); +#endif // BUILD_TESTS void openInputFiles(); std::shared_ptr getNextLedgerCloseData(); diff --git a/src/catchup/CatchupConfiguration.h b/src/catchup/CatchupConfiguration.h index 38465b96f9..b48ad93669 100644 --- a/src/catchup/CatchupConfiguration.h +++ b/src/catchup/CatchupConfiguration.h @@ -13,7 +13,7 @@ namespace stellar { -// Each catchup can be configured by two parameters destination ledger +// Each catchup can be configured by two parameters: destination ledger // (and its hash, if known) and count of ledgers to apply. // Value of count can be adjusted in different ways during catchup. If applying // count ledgers would mean going before the last closed ledger - it is @@ -31,12 +31,13 @@ namespace stellar // and catchup to that instead of destination ledger. This is useful when // doing offline commandline catchups with stellar-core catchup command. // -// Catchup can be done in two modes - ONLINE nad OFFLINE. In ONLINE mode node -// is connected to the network. If receives ledgers during catchup and applies -// them after history is applied. Also additional closing ledger is required -// to mark catchup as complete and node as synced. In OFFLINE mode node is not -// connected to network, so new ledgers are not being externalized. Only -// buckets and transactions from history archives are applied. +// Catchup can be done in two modes - ONLINE and OFFLINE. In ONLINE mode, the +// node is connected to the network. It receives ledgers during catchup and +// applies them after history is applied. Also, an additional closing ledger is +// required to mark catchup as complete and the node as synced. In OFFLINE mode, +// the node is not connected to network, so new ledgers are not being +// externalized. Only buckets and transactions from history archives are +// applied. class CatchupConfiguration { public: diff --git a/src/catchup/CatchupWork.h b/src/catchup/CatchupWork.h index d650bbc910..45c68a62ca 100644 --- a/src/catchup/CatchupWork.h +++ b/src/catchup/CatchupWork.h @@ -24,22 +24,22 @@ using WorkSeqPtr = std::shared_ptr; // CatchupWork does all the necessary work to perform any type of catchup. // It accepts CatchupConfiguration structure to know from which ledger to which -// one do the catchup and if it involves only applying ledgers or ledgers and +// one to do the catchup and if it involves only applying ledgers or ledgers and // buckets. // -// First thing it does is to get a history state which allows to calculate -// proper destination ledger (in case CatchupConfiguration::CURRENT) was used -// and to get list of buckets that should be in database on that ledger. +// First, it gets a history state, which allows it to calculate a +// proper destination ledger (in case CatchupConfiguration::CURRENT) +// and get a list of buckets that should be in the database on that ledger. // -// Next step is downloading and verifying ledgers (if verifyMode is set to -// VERIFY_BUFFERED_LEDGERS it can also verify against ledgers currently +// Next, it downloads and verifies ledgers (if verifyMode is set to +// VERIFY_BUFFERED_LEDGERS, it can also verify against ledgers currently // buffered in LedgerManager). // // Then, depending on configuration, it can download, verify and apply buckets // (as in MINIMAL and RECENT catchups), and then download and apply // transactions (as in COMPLETE and RECENT catchups). // -// After that, catchup is done and node can replay buffered ledgers and take +// After that, catchup is done and the node can replay buffered ledgers and take // part in consensus protocol. class CatchupWork : public Work diff --git a/src/catchup/DownloadApplyTxsWork.cpp b/src/catchup/DownloadApplyTxsWork.cpp index a9dadb2528..6ebde3ab43 100644 --- a/src/catchup/DownloadApplyTxsWork.cpp +++ b/src/catchup/DownloadApplyTxsWork.cpp @@ -43,7 +43,6 @@ DownloadApplyTxsWork::yieldMoreWork() { throw std::runtime_error("Work has no more children to iterate over!"); } - CLOG_INFO(History, "Downloading, unzipping and applying {} for checkpoint {}", typeString(FileType::HISTORY_FILE_TYPE_TRANSACTIONS), @@ -80,6 +79,53 @@ DownloadApplyTxsWork::yieldMoreWork() mApp, mDownloadDir, LedgerRange::inclusive(low, high), cb); std::vector> seq{getAndUnzip}; + std::vector filesToTransfer{ft}; + std::vector> optionalDownloads; +#ifdef BUILD_TESTS + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING) + { + CLOG_INFO(History, + "Downloading, unzipping and applying {} for checkpoint {}", + typeString(FileType::HISTORY_FILE_TYPE_RESULTS), + mCheckpointToQueue); + + FileTransferInfo resultsFile(mDownloadDir, + FileType::HISTORY_FILE_TYPE_RESULTS, + mCheckpointToQueue); + auto getResultsWork = std::make_shared( + mApp, resultsFile, mArchive, /*logErrorOnFailure=*/false); + std::weak_ptr getResultsWorkWeak = + getResultsWork; + seq.emplace_back(getResultsWork); + seq.emplace_back(std::make_shared( + mApp, "get-results-" + std::to_string(mCheckpointToQueue), + [apply, getResultsWorkWeak, checkpoint, &dir](Application& app) { + auto getResults = getResultsWorkWeak.lock(); + if (getResults && getResults->getState() != State::WORK_SUCCESS) + { + auto archive = getResults->getArchive(); + if (archive) + { + FileTransferInfo ti(dir, + FileType::HISTORY_FILE_TYPE_RESULTS, + checkpoint); + CLOG_WARNING( + History, + "Archive {} maybe contains corrupt results file " + "{}. " + "This is not fatal as long as the archive contains " + "valid transaction history. Catchup will proceed " + "but" + "the node will not be able to skip known results.", + archive->getName(), ti.remoteName()); + } + } + return true; + })); + + filesToTransfer.push_back(resultsFile); + } +#endif // BUILD_TESTS auto maybeWaitForMerges = [](Application& app) { if (app.getConfig().CATCHUP_WAIT_MERGES_TX_APPLY_FOR_TESTING) @@ -139,28 +185,34 @@ DownloadApplyTxsWork::yieldMoreWork() mApp, "wait-merges" + apply->getName(), maybeWaitForMerges, apply)); } - seq.push_back(std::make_shared( - mApp, "delete-transactions-" + std::to_string(mCheckpointToQueue), - [ft](Application& app) { - try - { - std::filesystem::remove( - std::filesystem::path(ft.localPath_nogz())); - CLOG_DEBUG(History, "Deleted transactions {}", + for (auto const& ft : filesToTransfer) + { + auto deleteWorkName = "delete-" + ft.getTypeString() + "-" + + std::to_string(mCheckpointToQueue); + seq.push_back(std::make_shared( + mApp, deleteWorkName, [ft](Application& app) { + CLOG_DEBUG(History, "Deleting {} {}", ft.getTypeString(), ft.localPath_nogz()); + try + { + std::filesystem::remove( + std::filesystem::path(ft.localPath_nogz())); + CLOG_DEBUG(History, "Deleted {} {}", ft.getTypeString(), + ft.localPath_nogz()); + } + catch (std::filesystem::filesystem_error const& e) + { + CLOG_ERROR(History, "Could not delete {} {}: {}", + ft.getTypeString(), ft.localPath_nogz(), + e.what()); + return false; + } return true; - } - catch (std::filesystem::filesystem_error const& e) - { - CLOG_ERROR(History, "Could not delete transactions {}: {}", - ft.localPath_nogz(), e.what()); - return false; - } - })); - + })); + } auto nextWork = std::make_shared( mApp, "download-apply-" + std::to_string(mCheckpointToQueue), seq, - BasicWork::RETRY_NEVER); + BasicWork::RETRY_NEVER, true /*stop at first failure*/); mCheckpointToQueue += mApp.getHistoryManager().getCheckpointFrequency(); mLastYieldedWork = nextWork; return nextWork; diff --git a/src/herder/LedgerCloseData.cpp b/src/herder/LedgerCloseData.cpp index a4e60700e5..962e52643b 100644 --- a/src/herder/LedgerCloseData.cpp +++ b/src/herder/LedgerCloseData.cpp @@ -26,6 +26,21 @@ LedgerCloseData::LedgerCloseData(uint32_t ledgerSeq, releaseAssert(txSet->getContentsHash() == mValue.txSetHash); } +#ifdef BUILD_TESTS +LedgerCloseData::LedgerCloseData( + uint32_t ledgerSeq, TxSetXDRFrameConstPtr txSet, StellarValue const& v, + std::optional const& expectedLedgerHash, + std::optional const& expectedResults) + : mLedgerSeq(ledgerSeq) + , mTxSet(txSet) + , mValue(v) + , mExpectedLedgerHash(expectedLedgerHash) + , mExpectedResults(expectedResults) +{ + releaseAssert(txSet->getContentsHash() == mValue.txSetHash); +} +#endif // BUILD_TESTS + std::string stellarValueToString(Config const& c, StellarValue const& sv) { diff --git a/src/herder/LedgerCloseData.h b/src/herder/LedgerCloseData.h index 31f9192277..06d9af5ff7 100644 --- a/src/herder/LedgerCloseData.h +++ b/src/herder/LedgerCloseData.h @@ -28,6 +28,13 @@ class LedgerCloseData uint32_t ledgerSeq, TxSetXDRFrameConstPtr txSet, StellarValue const& v, std::optional const& expectedLedgerHash = std::nullopt); +#ifdef BUILD_TESTS + LedgerCloseData(uint32_t ledgerSeq, TxSetXDRFrameConstPtr txSet, + StellarValue const& v, + std::optional const& expectedLedgerHash, + std::optional const& expectedResults); +#endif // BUILD_TESTS + uint32_t getLedgerSeq() const { @@ -48,6 +55,13 @@ class LedgerCloseData { return mExpectedLedgerHash; } +#ifdef BUILD_TESTS + std::optional const& + getExpectedResults() const + { + return mExpectedResults; + } +#endif // BUILD_TESTS StoredDebugTransactionSet toXDR() const @@ -81,7 +95,10 @@ class LedgerCloseData uint32_t mLedgerSeq; TxSetXDRFrameConstPtr mTxSet; StellarValue mValue; - std::optional mExpectedLedgerHash; + std::optional mExpectedLedgerHash = std::nullopt; +#ifdef BUILD_TESTS + std::optional mExpectedResults = std::nullopt; +#endif // BUILD_TESTS }; std::string stellarValueToString(Config const& c, StellarValue const& sv); diff --git a/src/historywork/GetAndUnzipRemoteFileWork.cpp b/src/historywork/GetAndUnzipRemoteFileWork.cpp index fef3bc0a49..d25232b669 100644 --- a/src/historywork/GetAndUnzipRemoteFileWork.cpp +++ b/src/historywork/GetAndUnzipRemoteFileWork.cpp @@ -17,11 +17,13 @@ namespace stellar GetAndUnzipRemoteFileWork::GetAndUnzipRemoteFileWork( Application& app, FileTransferInfo ft, - std::shared_ptr archive, size_t retry) + std::shared_ptr archive, size_t retry, + bool logErrorOnFailure) : Work(app, std::string("get-and-unzip-remote-file ") + ft.remoteName(), retry) , mFt(std::move(ft)) , mArchive(archive) + , mLogErrorOnFailure(logErrorOnFailure) { } @@ -56,8 +58,16 @@ GetAndUnzipRemoteFileWork::onFailureRaise() std::shared_ptr ar = getArchive(); if (ar) { - CLOG_ERROR(History, "Archive {}: file {} is maybe corrupt", - ar->getName(), mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR(History, "Archive {}: file {} is maybe corrupt", + ar->getName(), mFt.remoteName()); + } + else + { + CLOG_WARNING(History, "Archive {}: file {} is maybe corrupt", + ar->getName(), mFt.remoteName()); + } } Work::onFailureRaise(); } @@ -81,8 +91,18 @@ GetAndUnzipRemoteFileWork::doWork() auto state = mGunzipFileWork->getState(); if (state == State::WORK_SUCCESS && !fs::exists(mFt.localPath_nogz())) { - CLOG_ERROR(History, "Downloading and unzipping {}: .xdr not found", - mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR(History, + "Downloading and unzipping {}: .nogz not found", + mFt.remoteName()); + } + else + { + CLOG_WARNING(History, + "Downloading and unzipping {}: .nogz not found", + mFt.remoteName()); + } return State::WORK_FAILURE; } return state; @@ -119,8 +139,18 @@ GetAndUnzipRemoteFileWork::validateFile() ZoneScoped; if (!fs::exists(mFt.localPath_gz_tmp())) { - CLOG_ERROR(History, "Downloading and unzipping {}: .tmp file not found", - mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR(History, + "Downloading and unzipping {}: .tmp file not found", + mFt.remoteName()); + } + else + { + CLOG_WARNING(History, + "Downloading and unzipping {}: .tmp file not found", + mFt.remoteName()); + } return false; } @@ -129,18 +159,37 @@ GetAndUnzipRemoteFileWork::validateFile() if (fs::exists(mFt.localPath_gz()) && std::remove(mFt.localPath_gz().c_str())) { - CLOG_ERROR(History, - "Downloading and unzipping {}: failed to remove .gz", - mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR(History, + "Downloading and unzipping {}: failed to remove .gz", + mFt.remoteName()); + } + else + { + CLOG_WARNING(History, + "Downloading and unzipping {}: failed to remove .gz", + mFt.remoteName()); + } return false; } if (std::rename(mFt.localPath_gz_tmp().c_str(), mFt.localPath_gz().c_str())) { - CLOG_ERROR( - History, - "Downloading and unzipping {}: failed to rename .gz.tmp to .gz", - mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR( + History, + "Downloading and unzipping {}: failed to rename .gz.tmp to .gz", + mFt.remoteName()); + } + else + { + CLOG_WARNING( + History, + "Downloading and unzipping {}: failed to rename .gz.tmp to .gz", + mFt.remoteName()); + } return false; } @@ -149,8 +198,16 @@ GetAndUnzipRemoteFileWork::validateFile() if (!fs::exists(mFt.localPath_gz())) { - CLOG_ERROR(History, "Downloading and unzipping {}: .gz not found", - mFt.remoteName()); + if (mLogErrorOnFailure) + { + CLOG_ERROR(History, "Downloading and unzipping {}: .gz not found", + mFt.remoteName()); + } + else + { + CLOG_WARNING(History, "Downloading and unzipping {}: .gz not found", + mFt.remoteName()); + } return false; } diff --git a/src/historywork/GetAndUnzipRemoteFileWork.h b/src/historywork/GetAndUnzipRemoteFileWork.h index 55c9b4e779..b6350f2b91 100644 --- a/src/historywork/GetAndUnzipRemoteFileWork.h +++ b/src/historywork/GetAndUnzipRemoteFileWork.h @@ -20,6 +20,7 @@ class GetAndUnzipRemoteFileWork : public Work FileTransferInfo mFt; std::shared_ptr const mArchive; + bool mLogErrorOnFailure; bool validateFile(); @@ -29,7 +30,8 @@ class GetAndUnzipRemoteFileWork : public Work // retries. GetAndUnzipRemoteFileWork(Application& app, FileTransferInfo ft, std::shared_ptr archive = nullptr, - size_t retry = BasicWork::RETRY_A_LOT); + size_t retry = BasicWork::RETRY_A_LOT, + bool logErrorOnFailure = true); ~GetAndUnzipRemoteFileWork() = default; std::string getStatus() const override; std::shared_ptr getArchive() const; diff --git a/src/historywork/GetRemoteFileWork.cpp b/src/historywork/GetRemoteFileWork.cpp index 044ea6f725..23a0d95405 100644 --- a/src/historywork/GetRemoteFileWork.cpp +++ b/src/historywork/GetRemoteFileWork.cpp @@ -42,6 +42,7 @@ GetRemoteFileWork::getCommand() releaseAssert(mCurrentArchive); releaseAssert(mCurrentArchive->hasGetCmd()); auto cmdLine = mCurrentArchive->getFileCmd(mRemote, mLocal); + CLOG_DEBUG(History, "Downloading file: cmd: {}", cmdLine); return CommandInfo{cmdLine, std::string()}; } diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index 6499885f20..b562fe8c6f 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -55,6 +55,7 @@ #include #include +#include #include #include #include @@ -909,8 +910,9 @@ LedgerManagerImpl::closeLedger(LedgerCloseData const& ledgerData) // first, prefetch source accounts for txset, then charge fees prefetchTxSourceIds(txs); - auto const mutableTxResults = - processFeesSeqNums(txs, ltx, *applicableTxSet, ledgerCloseMeta); + + auto const mutableTxResults = processFeesSeqNums( + txs, ltx, *applicableTxSet, ledgerCloseMeta, ledgerData); TransactionResultSet txResultSet; txResultSet.results.reserve(txs.size()); @@ -1347,7 +1349,8 @@ std::vector LedgerManagerImpl::processFeesSeqNums( std::vector const& txs, AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, - std::unique_ptr const& ledgerCloseMeta) + std::unique_ptr const& ledgerCloseMeta, + LedgerCloseData const& ledgerData) { ZoneScoped; std::vector txResults; @@ -1360,6 +1363,20 @@ LedgerManagerImpl::processFeesSeqNums( auto header = ltx.loadHeader().current(); std::map accToMaxSeq; +#ifdef BUILD_TESTS + // If we have expected results, we assign them to the mutable tx results + // here. + std::optional::const_iterator> + expectedResultsIter = std::nullopt; + auto expectedResults = ledgerData.getExpectedResults(); + if (expectedResults) + { + releaseAssert(mApp.getCatchupManager().isCatchupInitialized()); + expectedResultsIter = + std::make_optional(expectedResults->results.begin()); + } +#endif + bool mergeSeen = false; for (auto tx : txs) { @@ -1367,6 +1384,18 @@ LedgerManagerImpl::processFeesSeqNums( txResults.push_back( tx->processFeeSeqNum(ltxTx, txSet.getTxBaseFee(tx, header))); +#ifdef BUILD_TESTS + if (expectedResultsIter) + { + releaseAssert(*expectedResultsIter != + expectedResults->results.end()); + releaseAssert((*expectedResultsIter)->transactionHash == + tx->getContentsHash()); + txResults.back()->setReplayTransactionResult( + (*expectedResultsIter)->result); + ++(*expectedResultsIter); + } +#endif // BUILD_TESTS if (protocolVersionStartsFrom( ltxTx.loadHeader().current().ledgerVersion, @@ -1550,10 +1579,12 @@ LedgerManagerImpl::applyTransactions( } ++txNum; - tx->apply(mApp.getAppConnector(), ltx, tm, mutableTxResult, subSeed); - tx->processPostApply(mApp.getAppConnector(), ltx, tm, mutableTxResult); TransactionResultPair results; results.transactionHash = tx->getContentsHash(); + + tx->apply(mApp.getAppConnector(), ltx, tm, mutableTxResult, subSeed); + tx->processPostApply(mApp.getAppConnector(), ltx, tm, mutableTxResult); + results.result = mutableTxResult->getResult(); if (results.result.result.code() == TransactionResultCode::txSUCCESS) { diff --git a/src/ledger/LedgerManagerImpl.h b/src/ledger/LedgerManagerImpl.h index 675dfe90c5..61caaf5490 100644 --- a/src/ledger/LedgerManagerImpl.h +++ b/src/ledger/LedgerManagerImpl.h @@ -77,7 +77,8 @@ class LedgerManagerImpl : public LedgerManager std::vector processFeesSeqNums( std::vector const& txs, AbstractLedgerTxn& ltxOuter, ApplicableTxSetFrame const& txSet, - std::unique_ptr const& ledgerCloseMeta); + std::unique_ptr const& ledgerCloseMeta, + LedgerCloseData const& ledgerData); void applyTransactions( ApplicableTxSetFrame const& txSet, diff --git a/src/ledger/test/LedgerCloseMetaStreamTests.cpp b/src/ledger/test/LedgerCloseMetaStreamTests.cpp index ac75139260..078e7d2d35 100644 --- a/src/ledger/test/LedgerCloseMetaStreamTests.cpp +++ b/src/ledger/test/LedgerCloseMetaStreamTests.cpp @@ -294,6 +294,11 @@ TEST_CASE("LedgerCloseMetaStream file descriptor - REPLAY_IN_MEMORY", cfg.RUN_STANDALONE = true; cfg.setInMemoryMode(); cfg.EXPERIMENTAL_PRECAUTION_DELAY_META = delayMeta; + SECTION("skip mode") + { + cfg.MODE_STORES_HISTORY_MISC = true; + cfg.CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING = true; + } VirtualClock clock; auto app = createTestApplication(clock, cfg, /*newdb=*/false); diff --git a/src/main/Config.cpp b/src/main/Config.cpp index 6520ae72ea..a932d9217a 100644 --- a/src/main/Config.cpp +++ b/src/main/Config.cpp @@ -306,6 +306,7 @@ Config::Config() : NODE_SEED(SecretKey::random()) #ifdef BUILD_TESTS TEST_CASES_ENABLED = false; + CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING = false; #endif #ifdef BEST_OFFER_DEBUGGING @@ -1151,6 +1152,12 @@ Config::processConfig(std::shared_ptr t) CATCHUP_RECENT = readInt(item, 0, UINT32_MAX - 1); }}, +#ifdef BUILD_TESTS + {"CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING", + [&]() { + CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING = readBool(item); + }}, +#endif // BUILD_TESTS {"ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING", [&]() { ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING = readBool(item); diff --git a/src/main/Config.h b/src/main/Config.h index 736bea2864..df88ed4184 100644 --- a/src/main/Config.h +++ b/src/main/Config.h @@ -203,6 +203,13 @@ class Config : public std::enable_shared_from_this // If you want, say, a week of history, set this to 120000. uint32_t CATCHUP_RECENT; +#ifdef BUILD_TESTS + // Mode for "accelerated" catchup. If set to true, the node will skip + // application of failed transactions and will not verify signatures of + // successful transactions. + bool CATCHUP_SKIP_KNOWN_RESULTS_FOR_TESTING; +#endif // BUILD_TESTS + // Interval between automatic maintenance executions std::chrono::seconds AUTOMATIC_MAINTENANCE_PERIOD; diff --git a/src/transactions/MutableTransactionResult.cpp b/src/transactions/MutableTransactionResult.cpp index 4dccb4da68..2de2bdcd19 100644 --- a/src/transactions/MutableTransactionResult.cpp +++ b/src/transactions/MutableTransactionResult.cpp @@ -314,6 +314,21 @@ MutableTransactionResult::isSuccess() const return getResult().result.code() == txSUCCESS; } +#ifdef BUILD_TESTS +void +MutableTransactionResult::setReplayTransactionResult( + TransactionResult const& replayResult) +{ + mReplayTransactionResult = std::make_optional(replayResult); +} + +std::optional const& +MutableTransactionResult::getReplayTransactionResult() const +{ + return mReplayTransactionResult; +} +#endif // BUILD_TESTS + FeeBumpMutableTransactionResult::FeeBumpMutableTransactionResult( MutableTxResultPtr innerTxResult) : MutableTransactionResultBase(), mInnerTxResult(innerTxResult) @@ -447,4 +462,19 @@ FeeBumpMutableTransactionResult::isSuccess() const { return mTxResult->result.code() == txFEE_BUMP_INNER_SUCCESS; } + +#ifdef BUILD_TESTS +void +FeeBumpMutableTransactionResult::setReplayTransactionResult( + TransactionResult const& replayResult) +{ + /* NO-OP */ +} + +std::optional const& +FeeBumpMutableTransactionResult::getReplayTransactionResult() const +{ + return mReplayTransactionResult; +} +#endif // BUILD_TESTS } \ No newline at end of file diff --git a/src/transactions/MutableTransactionResult.h b/src/transactions/MutableTransactionResult.h index 3527f20274..bfac8f7a90 100644 --- a/src/transactions/MutableTransactionResult.h +++ b/src/transactions/MutableTransactionResult.h @@ -71,6 +71,7 @@ class MutableTransactionResultBase : public NonMovableOrCopyable { protected: std::unique_ptr mTxResult; + std::optional mReplayTransactionResult{std::nullopt}; MutableTransactionResultBase(); MutableTransactionResultBase(MutableTransactionResultBase&& rhs); @@ -97,8 +98,13 @@ class MutableTransactionResultBase : public NonMovableOrCopyable virtual void refundSorobanFee(int64_t feeRefund, uint32_t ledgerVersion) = 0; - virtual bool isSuccess() const = 0; +#ifdef BUILD_TESTS + virtual std::optional const& + getReplayTransactionResult() const = 0; + virtual void + setReplayTransactionResult(TransactionResult const& replayResult) = 0; +#endif }; class MutableTransactionResult : public MutableTransactionResultBase @@ -124,6 +130,12 @@ class MutableTransactionResult : public MutableTransactionResultBase TransactionResult const& getResult() const override; TransactionResultCode getResultCode() const override; void setResultCode(TransactionResultCode code) override; +#ifdef BUILD_TESTS + void + setReplayTransactionResult(TransactionResult const& replayResult) override; + std::optional const& + getReplayTransactionResult() const override; +#endif // BUILD_TESTS OperationResult& getOpResultAt(size_t index) override; std::shared_ptr getSorobanData() override; @@ -177,5 +189,12 @@ class FeeBumpMutableTransactionResult : public MutableTransactionResultBase void refundSorobanFee(int64_t feeRefund, uint32_t ledgerVersion) override; bool isSuccess() const override; + +#ifdef BUILD_TESTS + void + setReplayTransactionResult(TransactionResult const& replayResult) override; + std::optional const& + getReplayTransactionResult() const override; +#endif // BUILD_TESTS }; } \ No newline at end of file diff --git a/src/transactions/SignatureChecker.h b/src/transactions/SignatureChecker.h index 15a8c33c35..fb0cdbbce9 100644 --- a/src/transactions/SignatureChecker.h +++ b/src/transactions/SignatureChecker.h @@ -15,18 +15,21 @@ namespace stellar { - class SignatureChecker { public: explicit SignatureChecker( uint32_t protocolVersion, Hash const& contentsHash, xdr::xvector const& signatures); - +#ifdef BUILD_TESTS + virtual bool checkSignature(std::vector const& signersV, + int32_t neededWeight); + virtual bool checkAllSignaturesUsed() const; +#else bool checkSignature(std::vector const& signersV, int32_t neededWeight); bool checkAllSignaturesUsed() const; - +#endif // BUILD_TESTS private: uint32_t mProtocolVersion; Hash const& mContentsHash; @@ -34,4 +37,28 @@ class SignatureChecker std::vector mUsedSignatures; }; + +#ifdef BUILD_TESTS +class AlwaysValidSignatureChecker : public SignatureChecker +{ + public: + AlwaysValidSignatureChecker( + uint32_t protocolVersion, Hash const& contentsHash, + xdr::xvector const& signatures) + : SignatureChecker(protocolVersion, contentsHash, signatures) + { + } + + bool + checkSignature(std::vector const&, int32_t) override + { + return true; + } + bool + checkAllSignaturesUsed() const override + { + return true; + } }; +#endif // BUILD_TESTS +} diff --git a/src/transactions/TransactionFrame.cpp b/src/transactions/TransactionFrame.cpp index 1eb017d768..d2645e2152 100644 --- a/src/transactions/TransactionFrame.cpp +++ b/src/transactions/TransactionFrame.cpp @@ -1434,10 +1434,10 @@ TransactionFrame::checkValidWithOptionallyChargedFee( auto txResult = createSuccessResultWithFeeCharged( ls.getLedgerHeader().current(), minBaseFee, false); releaseAssert(txResult); - SignatureChecker signatureChecker{ ls.getLedgerHeader().current().ledgerVersion, getContentsHash(), getSignatures(mEnvelope)}; + std::optional sorobanResourceFee; if (protocolVersionStartsFrom(ls.getLedgerHeader().current().ledgerVersion, SOROBAN_PROTOCOL_VERSION) && @@ -1574,6 +1574,24 @@ TransactionFrame::applyOperations(SignatureChecker& signatureChecker, Hash const& sorobanBasePrngSeed) const { ZoneScoped; +#ifdef BUILD_TESTS + auto const& result = txResult.getReplayTransactionResult(); + if (result && result->result.code() != txSUCCESS) + { + // Sub-zone for skips + ZoneScopedN("skipped failed"); + CLOG_DEBUG(Tx, "Skipping replay of failed transaction: tx {}", + binToHex(getContentsHash())); + txResult.setResultCode(result->result.code()); + // results field is only active if code is txFAILED or txSUCCESS + if (result->result.code() == txFAILED) + { + txResult.getResult().result.results() = result->result.results(); + } + return false; + } +#endif + auto& internalErrorCounter = app.getMetrics().NewCounter( {"ledger", "transaction", "internal-error"}); bool reportInternalErrOnException = true; @@ -1785,8 +1803,24 @@ TransactionFrame::apply(AppConnector& app, AbstractLedgerTxn& ltx, { mCachedAccountPreProtocol8.reset(); uint32_t ledgerVersion = ltx.loadHeader().current().ledgerVersion; - SignatureChecker signatureChecker{ledgerVersion, getContentsHash(), - getSignatures(mEnvelope)}; + std::unique_ptr signatureChecker; +#ifdef BUILD_TESTS + // If the txResult has a replay result (catchup in skip mode is + // enabled), + // we do not perform signature verification. + if (txResult->getReplayTransactionResult()) + { + signatureChecker = std::make_unique( + ledgerVersion, getContentsHash(), getSignatures(mEnvelope)); + } + else + { +#endif // BUILD_TESTS + signatureChecker = std::make_unique( + ledgerVersion, getContentsHash(), getSignatures(mEnvelope)); +#ifdef BUILD_TESTS + } +#endif // BUILD_TESTS // when applying, a failure during tx validation means that // we'll skip trying to apply operations but we'll still @@ -1809,7 +1843,7 @@ TransactionFrame::apply(AppConnector& app, AbstractLedgerTxn& ltx, } LedgerTxn ltxTx(ltx); LedgerSnapshot ltxStmt(ltxTx); - auto cv = commonValid(app, signatureChecker, ltxStmt, 0, true, + auto cv = commonValid(app, *signatureChecker, ltxStmt, 0, true, chargeFee, 0, 0, sorobanResourceFee, txResult); if (cv >= ValidationType::kInvalidUpdateSeqNum) { @@ -1817,7 +1851,7 @@ TransactionFrame::apply(AppConnector& app, AbstractLedgerTxn& ltx, } bool signaturesValid = - processSignatures(cv, signatureChecker, ltxTx, *txResult); + processSignatures(cv, *signatureChecker, ltxTx, *txResult); meta.pushTxChangesBefore(ltxTx.getChanges()); ltxTx.commit(); @@ -1835,7 +1869,7 @@ TransactionFrame::apply(AppConnector& app, AbstractLedgerTxn& ltx, updateSorobanMetrics(app); } - ok = applyOperations(signatureChecker, app, ltx, meta, + ok = applyOperations(*signatureChecker, app, ltx, meta, *txResult, sorobanBasePrngSeed); } return ok;