Skip to content

Commit 54bdb6e

Browse files
committed
Merge bitcoin#27609: rpc: allow submitpackage to be called outside of regtest
5b878be [doc] add release note for submitpackage (glozow) 7a9bb2a [rpc] allow submitpackage to be called outside of regtest (glozow) 5b9087a [rpc] require package to be a tree in submitpackage (glozow) e32ba15 [txpackages] IsChildWithParentsTree() (glozow) b4f28cc [doc] parent pay for child in aggregate CheckFeeRate (glozow) Pull request description: Permit (restricted topology) submitpackage RPC outside of regtest. Suggested in bitcoin#26933 (comment) This RPC should be safe but still experimental - interface may change, not all features (e.g. package RBF) are implemented, etc. If a miner wants to expose this to people, they can effectively use "package relay" before the p2p changes are implemented. However, please note **this is not package relay**; transactions submitted this way will not relay to other nodes if the feerates are below their mempool min fee. Users should put this behind some kind of rate limit or permissions. ACKs for top commit: instagibbs: ACK 5b878be achow101: ACK 5b878be dergoegge: Code review ACK 5b878be ajtowns: ACK 5b878be ariard: Code Review ACK 5b878be. Though didn’t manually test the PR. Tree-SHA512: 610365c0b2ffcccd55dedd1151879c82de1027e3319712bcb11d54f2467afaae4d05dca5f4b25f03354c80845fef538d3938b958174dda8b14c10670537a6524
2 parents cf553e3 + 5b878be commit 54bdb6e

File tree

7 files changed

+50
-8
lines changed

7 files changed

+50
-8
lines changed

doc/release-notes-27609.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
- A new RPC, `submitpackage`, has been added. It can be used to submit a list of raw hex
2+
transactions to the mempool to be evaluated as a package using consensus and mempool policy rules.
3+
These policies include package CPFP, allowing a child with high fees to bump a parent below the
4+
mempool minimum feerate (but not minimum relay feerate).
5+
6+
- Warning: successful submission does not mean the transactions will propagate throughout the
7+
network, as package relay is not supported.
8+
9+
- Not all features are available. The package is limited to a child with all of its
10+
unconfirmed parents, and no parent may spend the output of another parent. Also, package
11+
RBF is not supported. Refer to doc/policy/packages.md for more details on package policies
12+
and limitations.
13+
14+
- This RPC is experimental. Its interface may change.

src/policy/packages.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,18 @@ bool IsChildWithParents(const Package& package)
8888
return std::all_of(package.cbegin(), package.cend() - 1,
8989
[&input_txids](const auto& ptx) { return input_txids.count(ptx->GetHash()) > 0; });
9090
}
91+
92+
bool IsChildWithParentsTree(const Package& package)
93+
{
94+
if (!IsChildWithParents(package)) return false;
95+
std::unordered_set<uint256, SaltedTxidHasher> parent_txids;
96+
std::transform(package.cbegin(), package.cend() - 1, std::inserter(parent_txids, parent_txids.end()),
97+
[](const auto& ptx) { return ptx->GetHash(); });
98+
// Each parent must not have an input who is one of the other parents.
99+
return std::all_of(package.cbegin(), package.cend() - 1, [&](const auto& ptx) {
100+
for (const auto& input : ptx->vin) {
101+
if (parent_txids.count(input.prevout.hash) > 0) return false;
102+
}
103+
return true;
104+
});
105+
}

src/policy/packages.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,8 @@ bool CheckPackage(const Package& txns, PackageValidationState& state);
6363
*/
6464
bool IsChildWithParents(const Package& package);
6565

66+
/** Context-free check that a package IsChildWithParents() and none of the parents depend on each
67+
* other (the package is a "tree").
68+
*/
69+
bool IsChildWithParentsTree(const Package& package);
6670
#endif // BITCOIN_POLICY_PACKAGES_H

src/rpc/mempool.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -819,11 +819,11 @@ static RPCHelpMan savemempool()
819819
static RPCHelpMan submitpackage()
820820
{
821821
return RPCHelpMan{"submitpackage",
822-
"Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n"
822+
"Submit a package of raw transactions (serialized, hex-encoded) to local node.\n"
823+
"The package must consist of a child with its parents, and none of the parents may depend on one another.\n"
823824
"The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n"
824825
"This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n"
825-
"Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n"
826-
"Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n"
826+
"Warning: successful submission does not mean the transactions will propagate throughout the network.\n"
827827
,
828828
{
829829
{"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.",
@@ -862,9 +862,6 @@ static RPCHelpMan submitpackage()
862862
},
863863
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
864864
{
865-
if (Params().GetChainType() != ChainType::REGTEST) {
866-
throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only");
867-
}
868865
const UniValue raw_transactions = request.params[0].get_array();
869866
if (raw_transactions.size() < 1 || raw_transactions.size() > MAX_PACKAGE_COUNT) {
870867
throw JSONRPCError(RPC_INVALID_PARAMETER,
@@ -881,6 +878,9 @@ static RPCHelpMan submitpackage()
881878
}
882879
txns.emplace_back(MakeTransactionRef(std::move(mtx)));
883880
}
881+
if (!IsChildWithParentsTree(txns)) {
882+
throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, "package topology disallowed. not child-with-parents or parents depend on each other.");
883+
}
884884

885885
NodeContext& node = EnsureAnyNodeContext(request.context);
886886
CTxMemPool& mempool = EnsureMemPool(node);
@@ -983,7 +983,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
983983
{"blockchain", &getrawmempool},
984984
{"blockchain", &importmempool},
985985
{"blockchain", &savemempool},
986-
{"hidden", &submitpackage},
986+
{"rawtransactions", &submitpackage},
987987
};
988988
for (const auto& c : commands) {
989989
t.appendCommand(c.name, &c);

src/test/txpackage_tests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup)
162162
BOOST_CHECK_EQUAL(state.GetResult(), PackageValidationResult::PCKG_POLICY);
163163
BOOST_CHECK_EQUAL(state.GetRejectReason(), "package-not-sorted");
164164
BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
165+
BOOST_CHECK(IsChildWithParentsTree({tx_parent, tx_child}));
165166
}
166167

167168
// 24 Parents and 1 Child
@@ -187,6 +188,7 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup)
187188
PackageValidationState state;
188189
BOOST_CHECK(CheckPackage(package, state));
189190
BOOST_CHECK(IsChildWithParents(package));
191+
BOOST_CHECK(IsChildWithParentsTree(package));
190192

191193
package.erase(package.begin());
192194
BOOST_CHECK(IsChildWithParents(package));
@@ -219,6 +221,7 @@ BOOST_FIXTURE_TEST_CASE(noncontextual_package_tests, TestChain100Setup)
219221
BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child}));
220222
BOOST_CHECK(IsChildWithParents({tx_parent, tx_child}));
221223
BOOST_CHECK(IsChildWithParents({tx_parent, tx_parent_also_child, tx_child}));
224+
BOOST_CHECK(!IsChildWithParentsTree({tx_parent, tx_parent_also_child, tx_child}));
222225
// IsChildWithParents does not detect unsorted parents.
223226
BOOST_CHECK(IsChildWithParents({tx_parent_also_child, tx_parent, tx_child}));
224227
BOOST_CHECK(CheckPackage({tx_parent, tx_parent_also_child, tx_child}, state));

src/validation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,12 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
12871287
// Transactions must meet two minimum feerates: the mempool minimum fee and min relay fee.
12881288
// For transactions consisting of exactly one child and its parents, it suffices to use the
12891289
// package feerate (total modified fees / total virtual size) to check this requirement.
1290+
// Note that this is an aggregate feerate; this function has not checked that there are transactions
1291+
// too low feerate to pay for themselves, or that the child transactions are higher feerate than
1292+
// their parents. Using aggregate feerate may allow "parents pay for child" behavior and permit
1293+
// a child that is below mempool minimum feerate. To avoid these behaviors, callers of
1294+
// AcceptMultipleTransactions need to restrict txns topology (e.g. to ancestor sets) and check
1295+
// the feerates of individuals and subsets.
12901296
const auto m_total_vsize = std::accumulate(workspaces.cbegin(), workspaces.cend(), int64_t{0},
12911297
[](int64_t sum, auto& ws) { return sum + ws.m_vsize; });
12921298
const auto m_total_modified_fees = std::accumulate(workspaces.cbegin(), workspaces.cend(), CAmount{0},

test/functional/rpc_packages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def test_submitpackage(self):
335335
self.log.info("Submitpackage only allows packages of 1 child with its parents")
336336
# Chain of 3 transactions has too many generations
337337
chain_hex = [t["hex"] for t in self.wallet.create_self_transfer_chain(chain_length=25)]
338-
assert_raises_rpc_error(-25, "not-child-with-parents", node.submitpackage, chain_hex)
338+
assert_raises_rpc_error(-25, "package topology disallowed", node.submitpackage, chain_hex)
339339

340340

341341
if __name__ == "__main__":

0 commit comments

Comments
 (0)