diff --git a/src/policy/packages.cpp b/src/policy/packages.cpp index 693adcdfd0b25f..38f26a9c4b4096 100644 --- a/src/policy/packages.cpp +++ b/src/policy/packages.cpp @@ -118,8 +118,9 @@ bool IsWellFormedPackage(const Package& txns, PackageValidationState& state, boo bool IsChildWithParents(const Package& package) { + if (package.empty()) return false; assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); - if (package.size() < 2) return false; + if (package.size() < 2) return true; // The package is expected to be sorted, so the last transaction is the child. const auto& child = package.back(); diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 27a00c5d9170ae..84b37660a326c9 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -971,9 +971,9 @@ static RPCHelpMan submitpackage() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const UniValue raw_transactions = request.params[0].get_array(); - if (raw_transactions.size() < 2 || raw_transactions.size() > MAX_PACKAGE_COUNT) { + if (raw_transactions.empty() || raw_transactions.size() > MAX_PACKAGE_COUNT) { throw JSONRPCError(RPC_INVALID_PARAMETER, - "Array must contain between 2 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); + "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); } // Fee check needs to be run with chainstate and package context diff --git a/src/test/txpackage_tests.cpp b/src/test/txpackage_tests.cpp index ea211aedf3ac34..4cd785a687f592 100644 --- a/src/test/txpackage_tests.cpp +++ b/src/test/txpackage_tests.cpp @@ -283,6 +283,14 @@ BOOST_AUTO_TEST_CASE(noncontextual_package_tests) BOOST_CHECK(GetPackageHash({tx_parent}) != GetPackageHash({tx_child})); BOOST_CHECK(GetPackageHash({tx_child, tx_child}) != GetPackageHash({tx_child})); BOOST_CHECK(GetPackageHash({tx_child, tx_parent}) != GetPackageHash({tx_child, tx_child})); + + // IsChildWithParents* can also be a singleton + BOOST_CHECK(IsChildWithParents({tx_parent})); + BOOST_CHECK(IsChildWithParentsTree({tx_parent})); + + // But must not be empty + BOOST_CHECK(!IsChildWithParents({})); + BOOST_CHECK(!IsChildWithParentsTree({})); } // 24 Parents and 1 Child diff --git a/src/validation.cpp b/src/validation.cpp index 1953a42df1db4e..e57bc34153fc94 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1695,8 +1695,9 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package, return PackageMempoolAcceptResult(package_state_quit_early, {}); } - // IsChildWithParents() guarantees the package is > 1 transactions. - assert(package.size() > 1); + // IsChildWithParents() guarantees the package is not empty. + assert(!package.empty()); + // The package must be 1 child with all of its unconfirmed parents. The package is expected to // be sorted, so the last transaction is the child. const auto& child = package.back(); diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py index ef2f66b3a01850..d4da0f9b06640d 100755 --- a/test/functional/rpc_packages.py +++ b/test/functional/rpc_packages.py @@ -381,13 +381,15 @@ def test_submitpackage(self): assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex) assert_equal(legacy_pool, node.getrawmempool()) - assert_raises_rpc_error(-8, f"Array must contain between 2 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, []) - assert_raises_rpc_error(-8, f"Array must contain between 2 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, [chain_hex[0]] * 1) + assert_raises_rpc_error(-8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, []) assert_raises_rpc_error( - -8, f"Array must contain between 2 and {MAX_PACKAGE_COUNT} transactions.", + -8, f"Array must contain between 1 and {MAX_PACKAGE_COUNT} transactions.", node.submitpackage, [chain_hex[0]] * (MAX_PACKAGE_COUNT + 1) ) + # Singleton is ok + node.submitpackage([chain_hex[0]] * 1) + # Create a transaction chain such as only the parent gets accepted (by making the child's # version non-standard). Make sure the parent does get broadcast. self.log.info("If a package is partially submitted, transactions included in mempool get broadcast")