diff --git a/src/catchup/ApplyCheckpointWork.cpp b/src/catchup/ApplyCheckpointWork.cpp index 39180e9cdb..5afb9478f0 100644 --- a/src/catchup/ApplyCheckpointWork.cpp +++ b/src/catchup/ApplyCheckpointWork.cpp @@ -96,6 +96,16 @@ ApplyCheckpointWork::openInputFiles() mTxIn.open(ti.localPath_nogz()); mTxHistoryEntry = TransactionHistoryEntry(); mHeaderHistoryEntry = LedgerHeaderHistoryEntry(); + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS) + { + mTxResultIn.close(); + FileTransferInfo tri(mDownloadDir, FileType::HISTORY_FILE_TYPE_RESULTS, + mCheckpoint); + CLOG_DEBUG(History, "Replaying transaction results from {}", + tri.localPath_nogz()); + mTxResultIn.open(tri.localPath_nogz()); + mTxHistoryResultEntry = TransactionHistoryResultEntry{}; + } mFilesOpen = true; } @@ -141,6 +151,39 @@ ApplyCheckpointWork::getCurrentTxSet() return TxSetXDRFrame::makeEmpty(lm.getLastClosedLedgerHeader()); } +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). + do + { + 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); + } + } while (mTxResultIn && mTxResultIn.readOne(mTxHistoryResultEntry)); + CLOG_DEBUG(History, "No txresultset for ledger {}", seq); + return std::nullopt; +} + std::shared_ptr ApplyCheckpointWork::getNextLedgerCloseData() { @@ -219,6 +262,12 @@ ApplyCheckpointWork::getNextLedgerCloseData() CLOG_DEBUG(History, "Ledger {} has {} transactions", header.ledgerSeq, txset->sizeTxTotal()); + std::optional txres = std::nullopt; + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS) + { + txres = getCurrentTxResultSet(); + } + // 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 @@ -249,7 +298,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..4e15055215 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. @@ -48,7 +52,9 @@ class ApplyCheckpointWork : public BasicWork XDRInputFileStream mHdrIn; XDRInputFileStream mTxIn; + XDRInputFileStream mTxResultIn; TransactionHistoryEntry mTxHistoryEntry; + TransactionHistoryResultEntry mTxHistoryResultEntry; LedgerHeaderHistoryEntry mHeaderHistoryEntry; OnFailureCallback mOnFailure; @@ -57,6 +63,7 @@ class ApplyCheckpointWork : public BasicWork std::shared_ptr mConditionalWork; TxSetXDRFrameConstPtr getCurrentTxSet(); + std::optional getCurrentTxResultSet(); void openInputFiles(); std::shared_ptr getNextLedgerCloseData(); diff --git a/src/catchup/CatchupConfiguration.h b/src/catchup/CatchupConfiguration.h index 38465b96f9..3008d64000 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. If 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 ed36c75f5c..7607091ec2 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 90538ce3fa..37b0b5e8db 100644 --- a/src/catchup/DownloadApplyTxsWork.cpp +++ b/src/catchup/DownloadApplyTxsWork.cpp @@ -43,44 +43,40 @@ DownloadApplyTxsWork::yieldMoreWork() { throw std::runtime_error("Work has no more children to iterate over!"); } + std::vector fileTypesToDownload{ + FileType::HISTORY_FILE_TYPE_TRANSACTIONS}; + std::vector> downloadSeq; + std::vector filesToTransfer; + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS) + { + fileTypesToDownload.emplace_back(FileType::HISTORY_FILE_TYPE_RESULTS); + } + for (auto const& fileType : fileTypesToDownload) + { + CLOG_INFO(History, + "Downloading, unzipping and applying {} for checkpoint {}", + typeString(fileType), mCheckpointToQueue); + FileTransferInfo ft(mDownloadDir, fileType, mCheckpointToQueue); + filesToTransfer.emplace_back(ft); + downloadSeq.emplace_back( + std::make_shared(mApp, ft, mArchive)); + } - CLOG_INFO(History, - "Downloading, unzipping and applying {} for checkpoint {}", - typeString(FileType::HISTORY_FILE_TYPE_TRANSACTIONS), - mCheckpointToQueue); - FileTransferInfo ft(mDownloadDir, FileType::HISTORY_FILE_TYPE_TRANSACTIONS, - mCheckpointToQueue); - auto getAndUnzip = - std::make_shared(mApp, ft, mArchive); + OnFailureCallback cb = [archive = mArchive, filesToTransfer]() { + for (auto const& ft : filesToTransfer) + { + CLOG_ERROR(History, "Archive {} maybe contains corrupt file {}", + archive->getName(), ft.remoteName()); + } + }; auto const& hm = mApp.getHistoryManager(); auto low = hm.firstLedgerInCheckpointContaining(mCheckpointToQueue); auto high = std::min(mCheckpointToQueue, mRange.last()); - TmpDir const& dir = mDownloadDir; - uint32_t checkpoint = mCheckpointToQueue; - auto getFileWeak = std::weak_ptr(getAndUnzip); - - OnFailureCallback cb = [getFileWeak, checkpoint, &dir]() { - auto getFile = getFileWeak.lock(); - if (getFile) - { - auto archive = getFile->getArchive(); - if (archive) - { - FileTransferInfo ti( - dir, FileType::HISTORY_FILE_TYPE_TRANSACTIONS, checkpoint); - CLOG_ERROR(History, "Archive {} maybe contains corrupt file {}", - archive->getName(), ti.remoteName()); - } - } - }; - auto apply = std::make_shared( mApp, mDownloadDir, LedgerRange::inclusive(low, high), cb); - std::vector> seq{getAndUnzip}; - auto maybeWaitForMerges = [](Application& app) { if (app.getConfig().CATCHUP_WAIT_MERGES_TX_APPLY_FOR_TESTING) { @@ -98,8 +94,10 @@ DownloadApplyTxsWork::yieldMoreWork() { auto prev = mLastYieldedWork; bool pqFellBehind = false; + auto applyName = apply->getName(); auto predicate = [prev, pqFellBehind, waitForPublish = mWaitForPublish, - maybeWaitForMerges](Application& app) mutable { + maybeWaitForMerges, + applyName](Application& app) mutable { if (!prev) { throw std::runtime_error("Download and apply txs: related Work " @@ -130,37 +128,42 @@ DownloadApplyTxsWork::yieldMoreWork() } return res && maybeWaitForMerges(app); }; - seq.push_back(std::make_shared( + downloadSeq.push_back(std::make_shared( mApp, "conditional-" + apply->getName(), predicate, apply)); } else { - seq.push_back(std::make_shared( + downloadSeq.push_back(std::make_shared( mApp, "wait-merges" + apply->getName(), maybeWaitForMerges, apply)); } - seq.push_back(std::make_shared( + downloadSeq.push_back(std::make_shared( mApp, "delete-transactions-" + std::to_string(mCheckpointToQueue), - [ft](Application& app) { - try + [filesToTransfer](Application& app) { + for (auto const& ft : filesToTransfer) { - std::filesystem::remove( - std::filesystem::path(ft.localPath_nogz())); - CLOG_DEBUG(History, "Deleted transactions {}", + CLOG_DEBUG(History, "Deleting transactions {}", ft.localPath_nogz()); - return true; - } - catch (std::filesystem::filesystem_error const& e) - { - CLOG_ERROR(History, "Could not delete transactions {}: {}", - ft.localPath_nogz(), e.what()); - return false; + try + { + std::filesystem::remove( + std::filesystem::path(ft.localPath_nogz())); + CLOG_DEBUG(History, "Deleted transactions {}", + ft.localPath_nogz()); + } + catch (std::filesystem::filesystem_error const& e) + { + CLOG_ERROR(History, "Could not delete transactions {}: {}", + ft.localPath_nogz(), e.what()); + return false; + } } + return true; })); auto nextWork = std::make_shared( - mApp, "download-apply-" + std::to_string(mCheckpointToQueue), seq, - BasicWork::RETRY_NEVER); + mApp, "download-apply-" + std::to_string(mCheckpointToQueue), + downloadSeq, BasicWork::RETRY_NEVER); mCheckpointToQueue += mApp.getHistoryManager().getCheckpointFrequency(); mLastYieldedWork = nextWork; return nextWork; diff --git a/src/herder/LedgerCloseData.cpp b/src/herder/LedgerCloseData.cpp index a4e60700e5..a00f1c757e 100644 --- a/src/herder/LedgerCloseData.cpp +++ b/src/herder/LedgerCloseData.cpp @@ -14,14 +14,15 @@ using namespace std; namespace stellar { -LedgerCloseData::LedgerCloseData(uint32_t ledgerSeq, - TxSetXDRFrameConstPtr txSet, - StellarValue const& v, - std::optional const& expectedLedgerHash) +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); } diff --git a/src/herder/LedgerCloseData.h b/src/herder/LedgerCloseData.h index 31f9192277..a8a4039426 100644 --- a/src/herder/LedgerCloseData.h +++ b/src/herder/LedgerCloseData.h @@ -26,7 +26,9 @@ class LedgerCloseData public: LedgerCloseData( uint32_t ledgerSeq, TxSetXDRFrameConstPtr txSet, StellarValue const& v, - std::optional const& expectedLedgerHash = std::nullopt); + std::optional const& expectedLedgerHash = std::nullopt, + std::optional const& expectedResults = + std::nullopt); uint32_t getLedgerSeq() const @@ -48,6 +50,11 @@ class LedgerCloseData { return mExpectedLedgerHash; } + std::optional const& + getExpectedResults() const + { + return mExpectedResults; + } StoredDebugTransactionSet toXDR() const @@ -82,6 +89,7 @@ class LedgerCloseData TxSetXDRFrameConstPtr mTxSet; StellarValue mValue; std::optional mExpectedLedgerHash; + std::optional mExpectedResults; }; std::string stellarValueToString(Config const& c, StellarValue const& sv); diff --git a/src/ledger/LedgerManagerImpl.cpp b/src/ledger/LedgerManagerImpl.cpp index 8e7c5841b2..3d08008e5b 100644 --- a/src/ledger/LedgerManagerImpl.cpp +++ b/src/ledger/LedgerManagerImpl.cpp @@ -59,6 +59,7 @@ #include #include +#include #include #include #include @@ -920,7 +921,7 @@ LedgerManagerImpl::closeLedger(LedgerCloseData const& ledgerData) txResultSet.results.reserve(txs.size()); // Subtle: after this call, `header` is invalidated, and is not safe to use applyTransactions(*applicableTxSet, txs, mutableTxResults, ltx, txResultSet, - ledgerCloseMeta); + ledgerData.getExpectedResults(), ledgerCloseMeta); if (mApp.getConfig().MODE_STORES_HISTORY_MISC) { auto ledgerSeq = ltx.loadHeader().current().ledgerSeq; @@ -1498,6 +1499,7 @@ LedgerManagerImpl::applyTransactions( std::vector const& txs, std::vector const& mutableTxResults, AbstractLedgerTxn& ltx, TransactionResultSet& txResultSet, + std::optional const& expectedResults, std::unique_ptr const& ledgerCloseMeta) { ZoneNamedN(txsZone, "applyTransactions", true); @@ -1520,6 +1522,17 @@ LedgerManagerImpl::applyTransactions( prefetchTransactionData(txs); + std::optional::const_iterator> + expectedResultsIter = std::nullopt; + if (mApp.getConfig().CATCHUP_SKIP_KNOWN_RESULTS && expectedResults) + { + auto const& resVec = expectedResults->results; + CLOG_DEBUG(Tx, "Will skip replaying known results: {} txs, {} results", + txs.size(), resVec.size()); + releaseAssertOrThrow(txs.size() == resVec.size()); + expectedResultsIter = std::make_optional(resVec.begin()); + } + Hash sorobanBasePrngSeed = txSet.getContentsHash(); uint64_t txNum{0}; uint64_t txSucceeded{0}; @@ -1550,10 +1563,43 @@ LedgerManagerImpl::applyTransactions( } ++txNum; - tx->apply(mApp.getAppConnector(), ltx, tm, mutableTxResult, subSeed); - tx->processPostApply(mApp.getAppConnector(), ltx, tm, mutableTxResult); TransactionResultPair results; results.transactionHash = tx->getContentsHash(); + + if (expectedResultsIter) + { + releaseAssert(*expectedResultsIter != + expectedResults->results.end()); + while ((*expectedResultsIter)->transactionHash != + results.transactionHash) + { + (*expectedResultsIter)++; + releaseAssert(*expectedResultsIter != + expectedResults->results.end()); + } + releaseAssert((*expectedResultsIter)->transactionHash == + results.transactionHash); + if ((*expectedResultsIter)->result.result.code() == + TransactionResultCode::txSUCCESS) + { + CLOG_DEBUG(Tx, + "Skipping signature verification for known " + "successful tx#{}", + index); + tx->setReplaySuccessfulTransactionResult( + (*expectedResultsIter)->result); + } + else + { + CLOG_DEBUG(Tx, "Skipping replay of known failed tx#{}", index); + tx->setReplayFailingTransactionResult( + (*expectedResultsIter)->result); + } + } + + 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 1d16f93f4e..d3b4eca48d 100644 --- a/src/ledger/LedgerManagerImpl.h +++ b/src/ledger/LedgerManagerImpl.h @@ -84,6 +84,7 @@ class LedgerManagerImpl : public LedgerManager std::vector const& txs, std::vector const& mutableTxResults, AbstractLedgerTxn& ltx, TransactionResultSet& txResultSet, + std::optional const& expectedResults, std::unique_ptr const& ledgerCloseMeta); // initialLedgerVers must be the ledger version at the start of the ledger. diff --git a/src/ledger/test/LedgerCloseMetaStreamTests.cpp b/src/ledger/test/LedgerCloseMetaStreamTests.cpp index efdff716d5..07aaa2dcd5 100644 --- a/src/ledger/test/LedgerCloseMetaStreamTests.cpp +++ b/src/ledger/test/LedgerCloseMetaStreamTests.cpp @@ -295,6 +295,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 = true; + } VirtualClock clock; auto app = createTestApplication(clock, cfg, /*newdb=*/false); diff --git a/src/main/Config.cpp b/src/main/Config.cpp index 927883ee8d..201708cd01 100644 --- a/src/main/Config.cpp +++ b/src/main/Config.cpp @@ -1150,6 +1150,8 @@ Config::processConfig(std::shared_ptr t) CATCHUP_RECENT = readInt(item, 0, UINT32_MAX - 1); }}, + {"CATCHUP_SKIP_KNOWN_RESULTS", + [&]() { CATCHUP_SKIP_KNOWN_RESULTS = readBool(item); }}, {"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 600a349259..ca7ffe659d 100644 --- a/src/main/Config.h +++ b/src/main/Config.h @@ -203,6 +203,11 @@ class Config : public std::enable_shared_from_this // If you want, say, a week of history, set this to 120000. uint32_t CATCHUP_RECENT; + // 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; + // Interval between automatic maintenance executions std::chrono::seconds AUTOMATIC_MAINTENANCE_PERIOD; diff --git a/src/protocol-next/xdr b/src/protocol-next/xdr index 9a174be684..8c88608d0a 160000 --- a/src/protocol-next/xdr +++ b/src/protocol-next/xdr @@ -1 +1 @@ -Subproject commit 9a174be684d999db0de3ba9cb29c4c3a7e1cb169 +Subproject commit 8c88608d0a20b01873056a5ec13be245e1f2aa27 diff --git a/src/test/FuzzerImpl.cpp b/src/test/FuzzerImpl.cpp index 23e4c400df..b60edf3cc3 100644 --- a/src/test/FuzzerImpl.cpp +++ b/src/test/FuzzerImpl.cpp @@ -921,9 +921,10 @@ class FuzzTransactionFrame : public TransactionFrame // attempt application of transaction without processing the fee or // committing the LedgerTxn - SignatureChecker signatureChecker{ - ltx.loadHeader().current().ledgerVersion, getContentsHash(), - mEnvelope.v1().signatures}; + auto sc = + SignatureCheckerImpl{ltx.loadHeader().current().ledgerVersion, + getContentsHash(), mEnvelope.v1().signatures}; + SignatureChecker& signatureChecker = sc; LedgerSnapshot ltxStmt(ltx); // if any ill-formed Operations, do not attempt transaction application auto isInvalidOperation = [&](auto const& op, auto& opResult) { diff --git a/src/transactions/FeeBumpTransactionFrame.cpp b/src/transactions/FeeBumpTransactionFrame.cpp index ebddd16896..eb24128b43 100644 --- a/src/transactions/FeeBumpTransactionFrame.cpp +++ b/src/transactions/FeeBumpTransactionFrame.cpp @@ -170,9 +170,10 @@ FeeBumpTransactionFrame::checkValid(AppConnector& app, LedgerSnapshot const& ls, auto txResult = createSuccessResultWithFeeCharged( ls.getLedgerHeader().current(), minBaseFee, false); - SignatureChecker signatureChecker{ - ls.getLedgerHeader().current().ledgerVersion, getContentsHash(), - mEnvelope.feeBump().signatures}; + auto sc = + SignatureCheckerImpl{ls.getLedgerHeader().current().ledgerVersion, + getContentsHash(), mEnvelope.feeBump().signatures}; + SignatureChecker& signatureChecker = sc; if (commonValid(signatureChecker, ls, false, *txResult) != ValidationType::kFullyValid) { @@ -575,4 +576,16 @@ FeeBumpTransactionFrame::toStellarMessage() const msg->transaction() = mEnvelope; return msg; } + +void +FeeBumpTransactionFrame::setReplayFailingTransactionResult( + TransactionResult const&) const +{ /* NO OP*/ +} +void +FeeBumpTransactionFrame::setReplaySuccessfulTransactionResult( + TransactionResult const&) const +{ /* NO OP*/ +} + } diff --git a/src/transactions/FeeBumpTransactionFrame.h b/src/transactions/FeeBumpTransactionFrame.h index 6435c84eab..d31f21dae9 100644 --- a/src/transactions/FeeBumpTransactionFrame.h +++ b/src/transactions/FeeBumpTransactionFrame.h @@ -139,5 +139,10 @@ class FeeBumpTransactionFrame : public TransactionFrameBase SorobanResources const& sorobanResources() const override; virtual int64 declaredSorobanResourceFee() const override; virtual bool XDRProvidesValidFee() const override; + + void setReplayFailingTransactionResult( + TransactionResult const& failing) const override; + void setReplaySuccessfulTransactionResult( + TransactionResult const& successful) const override; }; } diff --git a/src/transactions/SignatureChecker.cpp b/src/transactions/SignatureChecker.cpp index 6a8636f6ba..327c49bdad 100644 --- a/src/transactions/SignatureChecker.cpp +++ b/src/transactions/SignatureChecker.cpp @@ -17,7 +17,7 @@ namespace stellar { -SignatureChecker::SignatureChecker( +SignatureCheckerImpl::SignatureCheckerImpl( uint32_t protocolVersion, Hash const& contentsHash, xdr::xvector const& signatures) : mProtocolVersion{protocolVersion} @@ -28,8 +28,8 @@ SignatureChecker::SignatureChecker( } bool -SignatureChecker::checkSignature(std::vector const& signersV, - int neededWeight) +SignatureCheckerImpl::checkSignature(std::vector const& signersV, + int neededWeight) { #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return true; @@ -136,7 +136,7 @@ SignatureChecker::checkSignature(std::vector const& signersV, } bool -SignatureChecker::checkAllSignaturesUsed() const +SignatureCheckerImpl::checkAllSignaturesUsed() const { #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return true; diff --git a/src/transactions/SignatureChecker.h b/src/transactions/SignatureChecker.h index 15a8c33c35..69e05261f9 100644 --- a/src/transactions/SignatureChecker.h +++ b/src/transactions/SignatureChecker.h @@ -19,13 +19,23 @@ namespace stellar class SignatureChecker { public: - explicit SignatureChecker( + virtual ~SignatureChecker() + { + } + + virtual bool checkSignature(std::vector const&, int32_t) = 0; + virtual bool checkAllSignaturesUsed() const = 0; +}; +class SignatureCheckerImpl : public SignatureChecker +{ + public: + explicit SignatureCheckerImpl( uint32_t protocolVersion, Hash const& contentsHash, xdr::xvector const& signatures); bool checkSignature(std::vector const& signersV, - int32_t neededWeight); - bool checkAllSignaturesUsed() const; + int32_t neededWeight) override; + bool checkAllSignaturesUsed() const override; private: uint32_t mProtocolVersion; @@ -34,4 +44,25 @@ class SignatureChecker std::vector mUsedSignatures; }; + +class AlwaysValidSignatureChecker : public SignatureChecker +{ + public: + AlwaysValidSignatureChecker(uint32_t, Hash const&, + xdr::xvector const&) + : SignatureChecker() + { + } + + bool + checkSignature(std::vector const&, int32_t) + { + return true; + } + bool + checkAllSignaturesUsed() const + { + return true; + } }; +} diff --git a/src/transactions/TransactionFrame.cpp b/src/transactions/TransactionFrame.cpp index 1eb017d768..107b62ef83 100644 --- a/src/transactions/TransactionFrame.cpp +++ b/src/transactions/TransactionFrame.cpp @@ -1152,6 +1152,22 @@ TransactionFrame::processSignatures( return maybeValid; } +// These functions are const so that we can call them from a const context but +// they modify mutable members. +void +TransactionFrame::setReplaySuccessfulTransactionResult( + TransactionResult const& successful) const +{ + mReplaySuccessfulTransactionResult = std::make_optional(successful); +} + +void +TransactionFrame::setReplayFailingTransactionResult( + TransactionResult const& failing) const +{ + mReplayFailingTransactionResult = std::make_optional(failing); +} + bool TransactionFrame::isBadSeq(LedgerHeaderWrapper const& header, int64_t seqNum) const @@ -1434,10 +1450,9 @@ TransactionFrame::checkValidWithOptionallyChargedFee( auto txResult = createSuccessResultWithFeeCharged( ls.getLedgerHeader().current(), minBaseFee, false); releaseAssert(txResult); - - SignatureChecker signatureChecker{ - ls.getLedgerHeader().current().ledgerVersion, getContentsHash(), - getSignatures(mEnvelope)}; + auto sc = SignatureCheckerImpl{ls.getLedgerHeader().current().ledgerVersion, + getContentsHash(), getSignatures(mEnvelope)}; + SignatureChecker& signatureChecker = sc; std::optional sorobanResourceFee; if (protocolVersionStartsFrom(ls.getLedgerHeader().current().ledgerVersion, SOROBAN_PROTOCOL_VERSION) && @@ -1574,6 +1589,17 @@ TransactionFrame::applyOperations(SignatureChecker& signatureChecker, Hash const& sorobanBasePrngSeed) const { ZoneScoped; + if (mReplayFailingTransactionResult.has_value()) + { + // Sub-zone for skips + ZoneScopedN("skipped failed"); + + txResult.setResultCode(mReplayFailingTransactionResult->result.code()); + txResult.getResult().result.results() = + mReplayFailingTransactionResult->result.results(); + return false; + } + auto& internalErrorCounter = app.getMetrics().NewCounter( {"ledger", "transaction", "internal-error"}); bool reportInternalErrOnException = true; @@ -1691,6 +1717,11 @@ TransactionFrame::applyOperations(SignatureChecker& signatureChecker, SOROBAN_PROTOCOL_VERSION) && isSoroban()) { + // TODO this needs to execute in skip mode? + // We would have to execute the soroban ops to get the correct + // diagnostic info to publish to meta. This doesn't affect + // ledger state. + // If transaction fails, we don't charge for any // refundable resources. auto preApplyFee = computePreApplySorobanResourceFee( @@ -1785,8 +1816,19 @@ TransactionFrame::apply(AppConnector& app, AbstractLedgerTxn& ltx, { mCachedAccountPreProtocol8.reset(); uint32_t ledgerVersion = ltx.loadHeader().current().ledgerVersion; - SignatureChecker signatureChecker{ledgerVersion, getContentsHash(), - getSignatures(mEnvelope)}; + auto skipMode = mReplayFailingTransactionResult.has_value() || + mReplaySuccessfulTransactionResult.has_value(); + std::unique_ptr signatureChecker; + if (skipMode) + { + signatureChecker = std::make_unique( + ledgerVersion, getContentsHash(), getSignatures(mEnvelope)); + } + else + { + signatureChecker = std::make_unique( + ledgerVersion, getContentsHash(), getSignatures(mEnvelope)); + } // when applying, a failure during tx validation means that // we'll skip trying to apply operations but we'll still @@ -1809,7 +1851,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 +1859,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 +1877,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; diff --git a/src/transactions/TransactionFrame.h b/src/transactions/TransactionFrame.h index ad46e3b650..1ed945120c 100644 --- a/src/transactions/TransactionFrame.h +++ b/src/transactions/TransactionFrame.h @@ -70,6 +70,10 @@ class TransactionFrame : public TransactionFrameBase mutable Hash mFullHash; // the hash of the contents and the sig. std::vector> mOperations; + mutable std::optional mReplaySuccessfulTransactionResult{ + std::nullopt}; + mutable std::optional mReplayFailingTransactionResult{ + std::nullopt}; LedgerTxnEntry loadSourceAccount(AbstractLedgerTxn& ltx, LedgerTxnHeader const& header) const; @@ -288,6 +292,10 @@ class TransactionFrame : public TransactionFrameBase virtual int64 declaredSorobanResourceFee() const override; virtual bool XDRProvidesValidFee() const override; + void setReplayFailingTransactionResult( + TransactionResult const& failing) const override; + void setReplaySuccessfulTransactionResult( + TransactionResult const& successful) const override; #ifdef BUILD_TESTS friend class TransactionTestFrame; #endif diff --git a/src/transactions/TransactionFrameBase.h b/src/transactions/TransactionFrameBase.h index 707fd10cf9..8d66a3da4b 100644 --- a/src/transactions/TransactionFrameBase.h +++ b/src/transactions/TransactionFrameBase.h @@ -114,5 +114,10 @@ class TransactionFrameBase virtual SorobanResources const& sorobanResources() const = 0; virtual int64 declaredSorobanResourceFee() const = 0; virtual bool XDRProvidesValidFee() const = 0; + + virtual void + setReplaySuccessfulTransactionResult(TransactionResult const&) const = 0; + virtual void + setReplayFailingTransactionResult(TransactionResult const&) const = 0; }; } diff --git a/src/transactions/test/TransactionTestFrame.cpp b/src/transactions/test/TransactionTestFrame.cpp index 8f4673ffcb..9cf0fd4c69 100644 --- a/src/transactions/test/TransactionTestFrame.cpp +++ b/src/transactions/test/TransactionTestFrame.cpp @@ -349,4 +349,15 @@ TransactionTestFrame::XDRProvidesValidFee() const { return mTransactionFrame->XDRProvidesValidFee(); } + +void +TransactionTestFrame::setReplayFailingTransactionResult( + TransactionResult const&) const +{ /* NO OP*/ +} +void +TransactionTestFrame::setReplaySuccessfulTransactionResult( + TransactionResult const&) const +{ /* NO OP*/ +} } \ No newline at end of file diff --git a/src/transactions/test/TransactionTestFrame.h b/src/transactions/test/TransactionTestFrame.h index 0efca11fdf..82991da88c 100644 --- a/src/transactions/test/TransactionTestFrame.h +++ b/src/transactions/test/TransactionTestFrame.h @@ -135,7 +135,10 @@ class TransactionTestFrame : public TransactionFrameBase SorobanResources const& sorobanResources() const override; int64 declaredSorobanResourceFee() const override; bool XDRProvidesValidFee() const override; - + void setReplayFailingTransactionResult( + TransactionResult const& failing) const override; + void setReplaySuccessfulTransactionResult( + TransactionResult const& successful) const override; bool isTestTx() const override {