/* * This file is part of the flowee project * Copyright (C) 2017-2024 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef BLOCKVALIDATION_P_H #define BLOCKVALIDATION_P_H /* * WARNING USAGE OF THIS HEADER IS RESTRICTED. * This Header file is part of the private API and is meant to be used solely by the validation component. * * Usage of this API will likely mean your code will break in interesting ways in the future, * or even stop to compile. * * YOU HAVE BEEN WARNED!! */ #include "Engine.h" #include "ValidationSettings_p.h" #include "ValidationException.h" #include #include #include #include #include #include #include #include #include #include // #define ENABLE_BENCHMARKS struct ValidationFlags { ValidationFlags(); bool strictPayToScriptHash; bool enforceBIP34; bool enableValidation; bool scriptVerifyDerSig; bool scriptVerifyLockTimeVerify; bool scriptVerifySequenceVerify; bool nLocktimeVerifySequence; bool hf201708Active; bool hf201805Active; bool hf201811Active; bool hf201905Active; bool hf201911Active; bool hf202005Active; bool hf202205Active; bool hf202305Active; bool hf202405Active; bool hf202505Active; bool hf202605Active; uint32_t scriptValidationFlags(bool requireStandard) const; /// based on the assumption that the index is after this Flags object, update it based on chain properties void updateForBlock(CBlockIndex *index); }; // implemented in TxValidation.cpp namespace ValidationPrivate { void validateTransactionInputs(const CTransaction &tx, const Tx &newTx, const std::vector &unspents, int blockHeight, ValidationFlags flags, int64_t &fees, uint32_t &txSigops, bool &spendsCoinbase, bool requireStandard); void checkContextualTransaction(const Tx &tx, ValidationFlags flags); } struct Output { Output(int index, int offsetInBlock) : index(index), offsetInBlock(offsetInBlock) {} uint256 txid; int index = -1; int offsetInBlock = 0; }; class BlockValidationState : public std::enable_shared_from_this { public: /// A bit field of validations that have succeeded so far. Or a simple BlockInvalid if one of them did not. enum BlockValidationStatus { BlockValidityUnknown = 0, //! Parsed just the header, checked basics. //! Set at successful completion of checks1NoContext() BlockValidHeader = 1, //! Block has a parent state or CBlockIndex, fully validatable leading back to genesis. //! This block has full data (not just a header) or is on the main-chain. Same with all its parents. //! When this is set it is allowed for the block to start checks2HaveParentHeaders() BlockValidTree = 2, //! Block has a valid header, parsable transactions and we did contextual checks. //! Implies BlockValidTree to be set. //! set at the successful completion of checks2HaveParentHeaders(); BlockValidChainHeaders = 4, //! Parent block is accepted on the chain, allowing this block to be offered as well. //! When this and BlockValidChainHeaders are set the block is allowed to start updateUtxoAndStartValidation() BlockValidParent = 8, //! At least one of the items didn't pass validation. BlockInvalid = 0x20 }; BlockValidationState(const std::weak_ptr &parent, const Block &block, std::uint32_t onResultFlags = 0, int originatingNodeId = -1); ~BlockValidationState(); void load(); void checks1NoContext(); void checks2HaveParentHeaders(); void checkSignaturesChunk(); void blockFailed(int punishment, const std::string &error, Validation::RejectCodes code, bool corruptionPossible = false); /** * When a block is accepted as the new chain-tip, check and schedule child-blocks that are next in line to be validated. */ void signalChildren() const; enum RecursiveOption { AddFlag, RemoveFlag }; void recursivelyMark(BlockValidationStatus value, RecursiveOption option = AddFlag); /// schedules a call to our BlockValidationPrivate processNewBlock() void finishUp(); /// When the previous block's transactions are added to the UTXO, we start our validation. void updateUtxoAndStartValidation(); /** * @brief calculateTxCheckChunks returns the amount of 'chunks' we split the transaction pool into for parallel validation. * @param[out] chunks the chunk-count. * @param[out] itemsPerChunk the number of transactions to be processed in a single chunk */ inline void calculateTxCheckChunks(int &chunks, int &itemsPerChunk) const { size_t txCount = m_block.transactions().size(); // we remove some cores from hardware_concurrency because that avoids the problem // that we'll end up waiting on other jobs finishing before we start validating our // block, slowing down the entire block's throughput. chunks = std::min((txCount+9) / 10, std::max(boost::thread::hardware_concurrency() - 4, 3)); itemsPerChunk = std::lrint(std::ceil(txCount / static_cast(chunks))); } Block m_block; CDiskBlockPos m_blockPos; CBlockIndex *m_blockIndex; uint256 blockId; const std::uint8_t m_onResultFlags; std::uint8_t punishment = 100; bool m_ownsIndex = false; bool m_checkingHeader = true; bool m_checkPow = true; bool m_checkMerkleRoot = true; bool m_checkValidityOnly = false; bool m_checkTransactionValidity = true; bool m_fetchFees = false; ValidationFlags flags; const std::int32_t m_originatingNodeId; std::string error; Validation::RejectCodes errorCode = Validation::NotRejected; bool isCorruptionPossible = false; //< true if failure could be result of a block-corruption in-transit mutable std::atomic m_txChunkLeftToStart; mutable std::atomic m_txChunkLeftToFinish; mutable std::atomic m_validationStatus; mutable std::atomic m_blockFees; mutable std::atomic m_sigChecksCounted; std::vector > > m_undoItems; std::vector > > m_perTxFees; std::weak_ptr m_parent; std::weak_ptr m_settings; // These children are waiting to be notified when I reach the conclusion // my block is likely on the main chain since that means they might be as well. std::vector > m_chainChildren; // ---------- only used when m_validateOnly is true. // for validateOnly style we omit changing the UTXO. As such we need to // allow some way to do in-block tx spending. This structure does that. // we map txid to a pair of ints. The first int is the output index. The second int is the tx's offset in block. typedef boost::unordered_map >, HashShortener> UnspentMap; UnspentMap m_txMap; // when a block is being checked for validity only (not appended) we store changes // in this map to detect double-spends. typedef boost::unordered_map, HashShortener> SpentMap; SpentMap m_spentMap; // ---------- only used when m_validateOnly is true. }; struct MapHashShortener { inline size_t operator()(const uint256& hash) const { return hash.GetCheapHash(); } }; class ValidationEnginePrivate { public: ValidationEnginePrivate(Validation::EngineType type); void setSpvValidationMode(bool spv); void blockHeaderValidated(std::shared_ptr state); void processNewBlock(std::shared_ptr state); void startOrphanWithParent(std::vector > &adoptees, const std::shared_ptr &state); void prepareChain(); void prepareChain_priv(); void createBlockIndexFor(const std::shared_ptr &state); /// called (from strand) to speed up shutdown void cleanup(); void handleFailedBlock(const std::shared_ptr &state); // check for data from node and potentially clean it. // ONLY CALL FROM STRAND! void nodeBanned(int nodeId); [[noreturn]] void fatal(const char *error); enum ProcessingType { CheckingHeader, CheckingBlock }; /// reduce blocks-in-flight counters void blockLanded(ProcessingType type); /// Find out if there are unscheduled blocks left to validate and schedule them. void findMoreJobs(); inline int blocksInFlightLimit() { return (int(boost::thread::hardware_concurrency())); } bool disconnectTip(CBlockIndex *index, bool *userClean = nullptr, bool *error = nullptr); boost::asio::io_context::strand strand; std::atomic shuttingDown; std::mutex lock; std::condition_variable waitVariable; /* We have some *InFlight limits here. * First of all, they are only loosly controlled, not very strict. * So, we share a threadPool with the entire application and as such we should not overwhelm it with jobs. * The fact that there are two checks here is because blocksInFlight is used for blocks that end up in the sequence of * * checks2HaveParentHeaders * * updateUtxoAndStartValidation * ** checkSignaturesChunk * * The step to go from check2 to the utxo method is serialized, meaning only one at a time is doing an utxo check. * This would hinder the total thoughput if we made this stop the headersInFlight additions, and as such there are * two counters. */ std::atomic headersInFlight; // indicates headers being checked, can grow upto, but not including blocksInFlightLimit() std::atomic blocksInFlight; // indicates blocks being checked, can grow upto, but not including blocksInFlightLimit() CChain *blockchain; std::atomic tip; ///< Since blockchain is only usable from the strand, copy this here for cross-thread usage. ValidationFlags tipFlags; ///< validation flags representative of the tip. CTxMemPool *mempool; uint256 hashPrevBestCoinBase; std::list > orphanBlocks; typedef boost::unordered_map, MapHashShortener> StatesMap; StatesMap blocksBeingValidated; std::mutex recentRejectsLock; CRollingBloomFilter recentTxRejects; std::weak_ptr me; const Validation::EngineType engineType; bool fetchFeeForMetaBlocks = false; bool spvValidationMode = false; private: int lastFullBlockScheduled; int previousPrintedHeaderHeight = 0; #ifdef ENABLE_BENCHMARKS public: std::atomic m_headerCheckTime; std::atomic m_basicValidityChecks; std::atomic m_contextCheckTime; std::atomic m_utxoTime; std::atomic m_validationTime; std::atomic m_loadingTime; std::atomic m_mempoolTime; std::atomic m_walletTime; #endif }; #endif