diff --git a/.github/workflows/test-src.yml b/.github/workflows/test-src.yml index d6d9b7efe58f..3b25c11a3ef8 100644 --- a/.github/workflows/test-src.yml +++ b/.github/workflows/test-src.yml @@ -41,7 +41,8 @@ jobs: image: ${{ inputs.container-path }} # zizmor: ignore[unpinned-images] built earlier in this run by build-container.yml # SYS_PTRACE is required by LeakSanitizer's stop-the-world # (https://github.com/google/sanitizers/issues/764) - options: --user root --cap-add SYS_PTRACE + # LINUX_IMMUTABLE lets feature_reindex_readonly.py run chattr +i + options: --user root --cap-add LINUX_IMMUTABLE --cap-add SYS_PTRACE steps: - name: Checkout code uses: actions/checkout@v6 diff --git a/contrib/containers/ci/ci-slim.Dockerfile b/contrib/containers/ci/ci-slim.Dockerfile index 2cce5c2c705c..50fb86688d8a 100644 --- a/contrib/containers/ci/ci-slim.Dockerfile +++ b/contrib/containers/ci/ci-slim.Dockerfile @@ -40,6 +40,7 @@ RUN set -ex; \ build-essential \ ca-certificates \ curl \ + e2fsprogs \ g++ \ git \ jq \ diff --git a/doc/release-notes-27609.md b/doc/release-notes-27609.md new file mode 100644 index 000000000000..a69bcaffaaa5 --- /dev/null +++ b/doc/release-notes-27609.md @@ -0,0 +1,13 @@ +- A new RPC, `submitpackage`, has been added. It can be used to submit a list of raw hex + transactions to the mempool to be evaluated as a package using consensus and mempool policy rules. +These policies include package CPFP, allowing a child with high fees to bump a parent below the +mempool minimum feerate (but not minimum relay feerate). + + - Warning: successful submission does not mean the transactions will propagate throughout the + network, as package relay is not supported. + + - Not all features are available. The package is limited to a child with all of its + unconfirmed parents, and no parent may spend the output of another parent. Refer to + doc/policy/packages.md for more details on package policies and limitations. + + - This RPC is experimental. Its interface may change. diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 73f39c029934..a6d42e7568f3 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -363,6 +363,7 @@ test_fuzz_fuzz_SOURCES = \ test/fuzz/netbase_dns_lookup.cpp \ test/fuzz/node_eviction.cpp \ test/fuzz/p2p_transport_serialization.cpp \ + test/fuzz/package_eval.cpp \ test/fuzz/parse_hd_keypath.cpp \ test/fuzz/parse_numbers.cpp \ test/fuzz/parse_script.cpp \ diff --git a/src/bench/load_external.cpp b/src/bench/load_external.cpp index 33667b02cc5e..f2bc8346c0a8 100644 --- a/src/bench/load_external.cpp +++ b/src/bench/load_external.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,7 @@ static void LoadExternalBlockFile(benchmark::Bench& bench) bench.run([&] { // "rb" is "binary, O_RDONLY", positioned to the start of the file. // The file will be closed by LoadExternalBlockFile(). - FILE* file{fsbridge::fopen(blkfile, "rb")}; + CAutoFile file{fsbridge::fopen(blkfile, "rb"), CLIENT_VERSION}; testing_setup->m_node.chainman->LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent); }); fs::remove(blkfile); diff --git a/src/bench/streams_findbyte.cpp b/src/bench/streams_findbyte.cpp index 22b8f1b356b9..4aaccb2af842 100644 --- a/src/bench/streams_findbyte.cpp +++ b/src/bench/streams_findbyte.cpp @@ -4,19 +4,23 @@ #include -#include #include +#include + +#include +#include +#include static void FindByte(benchmark::Bench& bench) { // Setup - FILE* file = fsbridge::fopen("streams_tmp", "w+b"); + CAutoFile file{fsbridge::fopen("streams_tmp", "w+b"), 0}; const size_t file_size = 200; uint8_t data[file_size] = {0}; data[file_size-1] = 1; - fwrite(&data, sizeof(uint8_t), file_size, file); - rewind(file); - BufferedFile bf{file, /*nBufSize=*/file_size + 1, /*nRewindIn=*/file_size, 0}; + file << data; + std::rewind(file.Get()); + BufferedFile bf{file, /*nBufSize=*/file_size + 1, /*nRewindIn=*/file_size}; bench.run([&] { bf.SetPos(0); @@ -24,7 +28,7 @@ static void FindByte(benchmark::Bench& bench) }); // Cleanup - bf.fclose(); + file.fclose(); fs::remove("streams_tmp"); } diff --git a/src/chain.h b/src/chain.h index a67a88641e9f..7b82ea1c8762 100644 --- a/src/chain.h +++ b/src/chain.h @@ -372,6 +372,14 @@ const CBlockIndex* LastCommonAncestor(const CBlockIndex* pa, const CBlockIndex* /** Used to marshal pointers into hashes for db storage. */ class CDiskBlockIndex : public CBlockIndex { + /** Historically CBlockLocator's version field has been written to disk + * streams as the client version, but the value has never been used. + * + * Hard-code to the highest client version ever written. + * SerParams can be used if the field requires any meaning in the future. + **/ + static constexpr int DUMMY_VERSION = 240100; + public: uint256 hash; uint256 hashPrev; @@ -391,8 +399,8 @@ class CDiskBlockIndex : public CBlockIndex SERIALIZE_METHODS(CDiskBlockIndex, obj) { LOCK(::cs_main); - int _nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) READWRITE(VARINT_MODE(_nVersion, VarIntMode::NONNEGATIVE_SIGNED)); + int _nVersion = DUMMY_VERSION; + READWRITE(VARINT_MODE(_nVersion, VarIntMode::NONNEGATIVE_SIGNED)); READWRITE(VARINT_MODE(obj.nHeight, VarIntMode::NONNEGATIVE_SIGNED)); READWRITE(VARINT(obj.nStatus)); diff --git a/src/dbwrapper.cpp b/src/dbwrapper.cpp index ecc406699b32..431161479487 100644 --- a/src/dbwrapper.cpp +++ b/src/dbwrapper.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -157,9 +156,9 @@ struct CDBBatch::WriteBatchImpl { leveldb::WriteBatch batch; }; -CDBBatch::CDBBatch(const CDBWrapper& _parent) : parent(_parent), - m_impl_batch{std::make_unique()}, - ssValue(SER_DISK, CLIENT_VERSION){}; +CDBBatch::CDBBatch(const CDBWrapper& _parent) + : parent{_parent}, + m_impl_batch{std::make_unique()} {}; CDBBatch::~CDBBatch() = default; @@ -169,7 +168,7 @@ void CDBBatch::Clear() size_estimate = 0; } -void CDBBatch::WriteImpl(Span key, CDataStream& ssValue) +void CDBBatch::WriteImpl(Span key, DataStream& ssValue) { leveldb::Slice slKey(CharCast(key.data()), key.size()); ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); diff --git a/src/dbwrapper.h b/src/dbwrapper.h index affdb583438a..4c09a1174339 100644 --- a/src/dbwrapper.h +++ b/src/dbwrapper.h @@ -91,11 +91,11 @@ class CDBBatch const std::unique_ptr m_impl_batch; DataStream ssKey{}; - CDataStream ssValue; + DataStream ssValue{}; size_t size_estimate{0}; - void WriteImpl(Span key, CDataStream& ssValue); + void WriteImpl(Span key, DataStream& ssValue); void EraseImpl(Span key); public: @@ -199,7 +199,7 @@ class CDBIterator template bool GetValue(V& value) { try { - CDataStream ssValue{GetValueImpl(), SER_DISK, CLIENT_VERSION}; + DataStream ssValue{GetValueImpl()}; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); ssValue >> value; } catch (const std::exception&) { @@ -252,7 +252,7 @@ class CDBWrapper CDBWrapper& operator=(const CDBWrapper&) = delete; template - bool ReadDataStream(const K& key, CDataStream& ssValue) const + bool ReadDataStream(const K& key, DataStream& ssValue) const { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); @@ -260,13 +260,13 @@ class CDBWrapper return ReadDataStream(ssKey, ssValue); } - bool ReadDataStream(const DataStream& ssKey, CDataStream& ssValue) const + bool ReadDataStream(const DataStream& ssKey, DataStream& ssValue) const { std::optional strValue{ReadImpl(ssKey)}; if (!strValue) { return false; } - CDataStream ssValueTmp{MakeByteSpan(*strValue), SER_DISK, CLIENT_VERSION}; + DataStream ssValueTmp{MakeByteSpan(*strValue)}; ssValueTmp.Xor(obfuscate_key); ssValue = std::move(ssValueTmp); return true; @@ -284,7 +284,7 @@ class CDBWrapper template bool Read(const DataStream& ssKey, V& value) const { - CDataStream ssValue(SER_DISK, CLIENT_VERSION); + DataStream ssValue{}; if (!ReadDataStream(ssKey, ssValue)) { return false; } diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 70288671d0a6..da21fb5175df 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1068,7 +1068,7 @@ bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_in } // Use legacy-aware deserialization for DB_LIST_DIFF_LEGACY entries - CDataStream s(SER_DISK, CLIENT_VERSION); + DataStream s{}; if (!m_evoDb.GetRawDB().ReadDataStream(key, s)) { break; } diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index ce4df7367fab..5131845a90d2 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -73,14 +73,7 @@ class CDeterministicMN READWRITE(obj.collateralOutpoint); READWRITE(obj.nOperatorReward); READWRITE(obj.pdmnState); - // We can't know if we are serialising for the Disk or for the Network here (s.GetType() is not accessible) - // Therefore if s.GetVersion() == CLIENT_VERSION -> Then we know we are serialising for the Disk - // Otherwise, we can safely check with protocol versioning logic so we won't break old clients - if (s.GetVersion() == CLIENT_VERSION || s.GetVersion() >= DMN_TYPE_PROTO_VERSION) { - READWRITE(obj.nType); - } else { - SER_READ(obj, obj.nType = MnType::Regular); - } + READWRITE(obj.nType); } [[nodiscard]] bool HasInternalId() const { return internalId != std::numeric_limits::max(); } diff --git a/src/index/blockfilterindex.cpp b/src/index/blockfilterindex.cpp index d2b03a992734..d41b8fe21799 100644 --- a/src/index/blockfilterindex.cpp +++ b/src/index/blockfilterindex.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include diff --git a/src/index/txindex.cpp b/src/index/txindex.cpp index 67caf35574d1..73cb113415c1 100644 --- a/src/index/txindex.cpp +++ b/src/index/txindex.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -83,7 +84,7 @@ bool TxIndex::FindTx(const uint256& tx_hash, uint256& block_hash, CTransactionRe return false; } - AutoFile file{m_chainstate->m_blockman.OpenBlockFile(RecordHeaderPos(postx), true)}; + CAutoFile file{m_chainstate->m_blockman.OpenBlockFile(RecordHeaderPos(postx), true)}; if (file.IsNull()) { LogError("%s: OpenBlockFile failed\n", __func__); return false; diff --git a/src/init.cpp b/src/init.cpp index 4c4892f7054b..e6c3d0c56dda 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/src/llmq/dkgsessionmgr.cpp b/src/llmq/dkgsessionmgr.cpp index 77f81eab13fa..b7225d162c4d 100644 --- a/src/llmq/dkgsessionmgr.cpp +++ b/src/llmq/dkgsessionmgr.cpp @@ -159,7 +159,7 @@ bool CDKGSessionManager::GetVerifiedContributions(Consensus::LLMQType llmqType, ContributionsCacheKey cacheKey = {llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash}; auto it = contributionsCache.find(cacheKey); if (it == contributionsCache.end()) { - CDataStream s(SER_DISK, CLIENT_VERSION); + DataStream s{}; if (!db->ReadDataStream(std::make_tuple(DB_VVEC, llmqType, pQuorumBaseBlockIndex->GetBlockHash(), proTxHash), s)) { LogPrint(BCLog::LLMQ, "%s -- this node does not have vvec for llmq=%d block=%s protx=%s\n", __func__, std23::to_underlying(llmqType), pQuorumBaseBlockIndex->GetBlockHash().ToString(), diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index 3a0d466fa699..86e4b703704a 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -212,7 +212,7 @@ void CQuorum::WriteContributions(CDBWrapper& db) const bool CQuorum::ReadContributions(const CDBWrapper& db) { uint256 dbKey = MakeQuorumKey(*this); - CDataStream s(SER_DISK, CLIENT_VERSION); + DataStream s{}; if (!db.ReadDataStream(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), s)) { return false; diff --git a/src/llmq/quorumsman.cpp b/src/llmq/quorumsman.cpp index 48a8fd5bbcdf..b91e04a41b3f 100644 --- a/src/llmq/quorumsman.cpp +++ b/src/llmq/quorumsman.cpp @@ -643,7 +643,7 @@ void CQuorumManager::MigrateOldQuorumDB(CEvoDB& evoDb) const int count{0}; while (pcursor->Valid()) { decltype(start) k; - CDataStream s(SER_DISK, CLIENT_VERSION); + DataStream s{}; CBLSSecretKey sk; if (!pcursor->GetKey(k) || std::get<0>(k) != prefix) { diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index f374d28963cf..d4587e61e206 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -96,7 +96,7 @@ bool CRecoveredSigsDb::ReadRecoveredSig(Consensus::LLMQType llmqType, const uint { auto k = std::make_tuple(std::string("rs_r"), llmqType, id); - CDataStream ds(SER_DISK, CLIENT_VERSION); + DataStream ds{}; if (!db->ReadDataStream(k, ds)) { return false; } @@ -239,7 +239,7 @@ struct RecoveredSigMarker { std::optional ReadRecoveredSigMarker(const CDBWrapper& db, const std::string& prefix, const uint256& hash) { - CDataStream value{SER_DISK, CLIENT_VERSION}; + DataStream value{}; if (!db.ReadDataStream(std::make_pair(prefix, hash), value)) return std::nullopt; RecoveredSigMarker marker; try { diff --git a/src/node/blockstorage.cpp b/src/node/blockstorage.cpp index 6c27552bcd89..df9a6077e0e2 100644 --- a/src/node/blockstorage.cpp +++ b/src/node/blockstorage.cpp @@ -390,7 +390,7 @@ bool BlockManager::LoadBlockIndexDB() } for (std::set::iterator it = setBlkDataFiles.begin(); it != setBlkDataFiles.end(); it++) { FlatFilePos pos(*it, 0); - if (AutoFile{OpenBlockFile(pos, true)}.IsNull()) { + if (OpenBlockFile(pos, true).IsNull()) { return false; } } @@ -509,7 +509,7 @@ CBlockFileInfo* BlockManager::GetBlockFileInfo(size_t n) bool BlockManager::UndoWriteToDisk(const CBlockUndo& blockundo, const std::optional>& compressed, uint32_t size_field, FlatFilePos& pos, const uint256& hashBlock, const CMessageHeader::MessageStartChars& messageStart) const { // Open history file to append - AutoFile fileout{OpenUndoFile(pos)}; + CAutoFile fileout{OpenUndoFile(pos)}; if (fileout.IsNull()) { LogError("%s: OpenUndoFile failed\n", __func__); return false; @@ -550,7 +550,7 @@ bool BlockManager::UndoReadFromDisk(CBlockUndo& blockundo, const CBlockIndex& in } // Open history file to read - AutoFile filein{OpenUndoFile(RecordHeaderPos(pos), true)}; + CAutoFile filein{OpenUndoFile(RecordHeaderPos(pos), true)}; if (filein.IsNull()) { LogError("%s: OpenUndoFile failed\n", __func__); return false; @@ -662,15 +662,15 @@ FlatFileSeq BlockManager::UndoFileSeq() const return FlatFileSeq(m_opts.blocks_dir, "rev", m_opts.tiny_blk ? 0x10000 /* 64kb */ : UNDOFILE_CHUNK_SIZE); } -FILE* BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const +CAutoFile BlockManager::OpenBlockFile(const FlatFilePos& pos, bool fReadOnly) const { - return BlockFileSeq().Open(pos, fReadOnly); + return CAutoFile{BlockFileSeq().Open(pos, fReadOnly), CLIENT_VERSION}; } /** Open an undo file (rev?????.dat) */ -FILE* BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const +CAutoFile BlockManager::OpenUndoFile(const FlatFilePos& pos, bool fReadOnly) const { - return UndoFileSeq().Open(pos, fReadOnly); + return CAutoFile{UndoFileSeq().Open(pos, fReadOnly), CLIENT_VERSION}; } fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const @@ -782,7 +782,7 @@ bool BlockManager::FindUndoPos(BlockValidationState& state, int nFile, FlatFileP bool BlockManager::WriteBlockToDisk(Span payload, uint32_t size_field, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart) const { // Open history file to append - AutoFile fileout{OpenBlockFile(pos)}; + CAutoFile fileout{OpenBlockFile(pos)}; if (fileout.IsNull()) { LogError("WriteBlockToDisk: OpenBlockFile failed\n"); return false; @@ -904,7 +904,7 @@ std::optional BlockManager::ReadBlockFromDisk(CBlock& block, const Flat block.SetNull(); // Open history file to read - AutoFile filein{OpenBlockFile(RecordHeaderPos(pos), true)}; + CAutoFile filein{OpenBlockFile(RecordHeaderPos(pos), true)}; if (filein.IsNull()) { LogError("ReadBlockFromDisk: OpenBlockFile failed for %s\n", pos.ToString()); return std::nullopt; @@ -1027,8 +1027,8 @@ void ThreadImport(ChainstateManager& chainman, std::vector vImportFile if (!fs::exists(chainman.m_blockman.GetBlockPosFilename(pos))) { break; // No block files left to reindex } - FILE* file = chainman.m_blockman.OpenBlockFile(pos, true); - if (!file) { + CAutoFile file = chainman.m_blockman.OpenBlockFile(pos, true); + if (file.IsNull()) { break; // This error is logged in OpenBlockFile } LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile); @@ -1048,8 +1048,8 @@ void ThreadImport(ChainstateManager& chainman, std::vector vImportFile // -loadblock= for (const fs::path& path : vImportFiles) { - FILE *file = fsbridge::fopen(path, "rb"); - if (file) { + CAutoFile file{fsbridge::fopen(path, "rb"), CLIENT_VERSION}; + if (!file.IsNull()) { LogPrintf("Importing blocks file %s...\n", fs::PathToString(path)); chainman.LoadExternalBlockFile(file); if (ShutdownRequested()) { diff --git a/src/node/blockstorage.h b/src/node/blockstorage.h index 8e70b2cdc156..4262aa969d18 100644 --- a/src/node/blockstorage.h +++ b/src/node/blockstorage.h @@ -22,6 +22,7 @@ class AutoFile; class BlockValidationState; +class CAutoFile; class CBlock; class CBlockUndo; class CChainParams; @@ -114,7 +115,7 @@ class BlockManager FlatFileSeq BlockFileSeq() const; FlatFileSeq UndoFileSeq() const; - FILE* OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const; + CAutoFile OpenUndoFile(const FlatFilePos& pos, bool fReadOnly = false) const; bool WriteBlockToDisk(Span payload, uint32_t size_field, FlatFilePos& pos, const CMessageHeader::MessageStartChars& messageStart) const; /** Size field of the record already stored at pos */ @@ -262,7 +263,7 @@ class BlockManager void UpdatePruneLock(const std::string& name, const PruneLockInfo& lock_info) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); /** Open a block file (blk?????.dat) */ - FILE* OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false) const; + CAutoFile OpenBlockFile(const FlatFilePos& pos, bool fReadOnly = false) const; /** Translation to a filesystem path */ fs::path GetBlockPosFilename(const FlatFilePos& pos) const; diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp index 6e70a94088a2..a656a4b5f06d 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -81,3 +81,18 @@ bool IsChildWithParents(const Package& package) return std::all_of(package.cbegin(), package.cend() - 1, [&input_txids](const auto& ptx) { return input_txids.count(ptx->GetHash()) > 0; }); } + +bool IsChildWithParentsTree(const Package& package) +{ + if (!IsChildWithParents(package)) return false; + std::unordered_set parent_txids; + std::transform(package.cbegin(), package.cend() - 1, std::inserter(parent_txids, parent_txids.end()), + [](const auto& ptx) { return ptx->GetHash(); }); + // Each parent must not have an input who is one of the other parents. + return std::all_of(package.cbegin(), package.cend() - 1, [&](const auto& ptx) { + for (const auto& input : ptx->vin) { + if (parent_txids.count(input.prevout.hash) > 0) return false; + } + return true; + }); +} diff --git a/src/policy/packages.h b/src/policy/packages.h index c6fe963042ab..e7160a14cf3b 100644 --- a/src/policy/packages.h +++ b/src/policy/packages.h @@ -18,14 +18,15 @@ static constexpr uint32_t MAX_PACKAGE_COUNT{25}; static constexpr uint32_t MAX_PACKAGE_SIZE{101}; static_assert(MAX_PACKAGE_SIZE * 1000 >= MAX_STANDARD_TX_SIZE); -// If a package is submitted, it must be within the mempool's ancestor/descendant limits. Since a -// submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor +// If a package is to be evaluated, it must be at least as large as the mempool's ancestor/descendant limits, +// otherwise transactions that would be individually accepted may be rejected in a package erroneously. +// Since a submitted package must be child-with-unconfirmed-parents (all of the transactions are an ancestor // of the child), package limits are ultimately bounded by mempool package limits. Ensure that the // defaults reflect this constraint. static_assert(DEFAULT_DESCENDANT_LIMIT >= MAX_PACKAGE_COUNT); static_assert(DEFAULT_ANCESTOR_LIMIT >= MAX_PACKAGE_COUNT); -static_assert(DEFAULT_ANCESTOR_SIZE_LIMIT_KVB >= MAX_PACKAGE_SIZE); -static_assert(DEFAULT_DESCENDANT_SIZE_LIMIT_KVB >= MAX_PACKAGE_SIZE); +static_assert(MAX_PACKAGE_SIZE >= DEFAULT_ANCESTOR_SIZE_LIMIT_KVB); +static_assert(MAX_PACKAGE_SIZE >= DEFAULT_DESCENDANT_SIZE_LIMIT_KVB); /** A "reason" why a package was invalid. It may be that one or more of the included * transactions is invalid or the package itself violates our rules. @@ -58,4 +59,8 @@ bool CheckPackage(const Package& txns, PackageValidationState& state); */ bool IsChildWithParents(const Package& package); +/** Context-free check that a package IsChildWithParents() and none of the parents depend on each + * other (the package is a "tree"). + */ +bool IsChildWithParentsTree(const Package& package); #endif // BITCOIN_POLICY_PACKAGES_H diff --git a/src/primitives/block.h b/src/primitives/block.h index 8789a21cbe37..486f855e6204 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -245,6 +245,15 @@ class CBlock : public CBlockHeader */ struct CBlockLocator { + /** Historically CBlockLocator's version field has been written to network + * streams as the negotiated protocol version and to disk streams as the + * client version, but the value has never been used. + * + * Hard-code to the highest protocol version ever written to a network stream. + * SerParams can be used if the field requires any meaning in the future, + **/ + static constexpr int DUMMY_VERSION = 70242; + std::vector vHave; CBlockLocator() {} @@ -253,9 +262,8 @@ struct CBlockLocator SERIALIZE_METHODS(CBlockLocator, obj) { - int nVersion = s.GetVersion(); - if (!(s.GetType() & SER_GETHASH)) - READWRITE(nVersion); + int nVersion = DUMMY_VERSION; + READWRITE(nVersion); READWRITE(obj.vHave); } diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index ecc44e9e796a..dfe293434a0e 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -830,11 +830,11 @@ static RPCHelpMan savemempool() static RPCHelpMan submitpackage() { return RPCHelpMan{"submitpackage", - "Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n" + "Submit a package of raw transactions (serialized, hex-encoded) to local node.\n" + "The package must consist of a child with its parents, and none of the parents may depend on one another.\n" "The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n" "This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n" - "Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n" - "Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n" + "Warning: successful submission does not mean the transactions will propagate throughout the network.\n" , { {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.", @@ -868,9 +868,6 @@ static RPCHelpMan submitpackage() }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { - if (Params().GetChainType() != ChainType::REGTEST) { - throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); - } const UniValue raw_transactions = request.params[0].get_array(); if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, @@ -887,6 +884,9 @@ static RPCHelpMan submitpackage() } txns.emplace_back(MakeTransactionRef(std::move(mtx))); } + if (!IsChildWithParentsTree(txns)) { + throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, "package topology disallowed. not child-with-parents or parents depend on each other."); + } NodeContext& node = EnsureAnyNodeContext(request.context); CTxMemPool& mempool = EnsureMemPool(node); @@ -977,7 +977,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &getrawmempool}, {"blockchain", &importmempool}, {"blockchain", &savemempool}, - {"hidden", &submitpackage}, + {"rawtransactions", &submitpackage}, }; for (const auto& c : commands) { t.appendCommand(c.name, &c); diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index d76f94cab6c1..9c925cecd986 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -1147,10 +1147,9 @@ std::string RPCArg::ToString(const bool oneline) const if (oneline && !m_opts.oneline_description.empty()) { if (m_opts.oneline_description[0] == '\"' && m_type != Type::STR_HEX && m_type != Type::STR && gArgs.GetBoolArg("-rpcdoccheck", DEFAULT_RPC_DOC_CHECK)) { throw std::runtime_error{ - strprintf("Internal bug detected: non-string RPC arg \"%s\" quotes oneline_description:\n%s\n%s %s\nPlease report this issue here: %s\n", - m_names, m_opts.oneline_description, - PACKAGE_NAME, FormatFullVersion(), - PACKAGE_BUGREPORT)}; + STR_INTERNAL_BUG(strprintf("non-string RPC arg \"%s\" quotes oneline_description:\n%s", + m_names, m_opts.oneline_description) + )}; } return m_opts.oneline_description; } diff --git a/src/streams.h b/src/streams.h index 4fd854b8ef8b..aabf4d506430 100644 --- a/src/streams.h +++ b/src/streams.h @@ -584,7 +584,7 @@ class CAutoFile : public AutoFile } }; -/** Non-refcounted RAII wrapper around a FILE* that implements a ring buffer to +/** Wrapper around a CAutoFile& that implements a ring buffer to * deserialize from. It guarantees the ability to rewind a given number of bytes. * * Will automatically close the file when it goes out of scope if not null. @@ -593,9 +593,7 @@ class CAutoFile : public AutoFile class BufferedFile { private: - const int nVersion; - - FILE *src; //!< source file + CAutoFile& m_src; uint64_t nSrcPos{0}; //!< how many bytes have been read from source uint64_t m_read_pos{0}; //!< how many bytes have been read from this uint64_t nReadLimit; //!< up to which position we're allowed to read @@ -611,9 +609,9 @@ class BufferedFile readNow = nAvail; if (readNow == 0) return false; - size_t nBytes = fread((void*)&vchBuf[pos], 1, readNow, src); + size_t nBytes{m_src.detail_fread(Span{vchBuf}.subspan(pos, readNow))}; if (nBytes == 0) { - throw std::ios_base::failure(feof(src) ? "BufferedFile::Fill: end of file" : "BufferedFile::Fill: fread failed"); + throw std::ios_base::failure{m_src.feof() ? "BufferedFile::Fill: end of file" : "BufferedFile::Fill: fread failed"}; } nSrcPos += nBytes; return true; @@ -642,36 +640,18 @@ class BufferedFile } public: - BufferedFile(FILE* fileIn, uint64_t nBufSize, uint64_t nRewindIn, int nVersionIn) - : nVersion{nVersionIn}, nReadLimit{std::numeric_limits::max()}, nRewind{nRewindIn}, vchBuf(nBufSize, std::byte{0}) + BufferedFile(CAutoFile& file, uint64_t nBufSize, uint64_t nRewindIn) + : m_src{file}, nReadLimit{std::numeric_limits::max()}, nRewind{nRewindIn}, vchBuf(nBufSize, std::byte{0}) { if (nRewindIn >= nBufSize) throw std::ios_base::failure("Rewind limit must be less than buffer size"); - src = fileIn; - } - - ~BufferedFile() - { - fclose(); } - // Disallow copies - BufferedFile(const BufferedFile&) = delete; - BufferedFile& operator=(const BufferedFile&) = delete; - - int GetVersion() const { return nVersion; } - - void fclose() - { - if (src) { - ::fclose(src); - src = nullptr; - } - } + int GetVersion() const { return m_src.GetVersion(); } //! check whether we're at the end of the source file bool eof() const { - return m_read_pos == nSrcPos && feof(src); + return m_read_pos == nSrcPos && m_src.feof(); } //! read a number of bytes diff --git a/src/test/blockmanager_tests.cpp b/src/test/blockmanager_tests.cpp index 4ce0bd7bd1d5..a9b8c6f2e2e7 100644 --- a/src/test/blockmanager_tests.cpp +++ b/src/test/blockmanager_tests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include