diff options
author | glozow <gloriajzhao@gmail.com> | 2021-07-20 11:45:52 +0100 |
---|---|---|
committer | glozow <gloriajzhao@gmail.com> | 2022-06-23 14:35:04 +0100 |
commit | fa076515b07ac4b10b2134e323bf4f56be5996a8 (patch) | |
tree | 6f4851be8f004c8fc03cb207e70df14c8e7ffe54 /src | |
parent | b9ef5a10e2fa4609d048db57b99463305455ebe4 (diff) |
[rpc] add new submitpackage RPC
It could be unsafe/confusing to create an actual mainnet interface while
package relay doesn't exist. However, a regtest-only interface allows
wallet/application devs to test current package policies.
Diffstat (limited to 'src')
-rw-r--r-- | src/rpc/client.cpp | 1 | ||||
-rw-r--r-- | src/rpc/mempool.cpp | 146 | ||||
-rw-r--r-- | src/test/fuzz/rpc.cpp | 1 | ||||
-rw-r--r-- | src/util/error.cpp | 2 | ||||
-rw-r--r-- | src/util/error.h | 1 |
5 files changed, 151 insertions, 0 deletions
diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index ae0d0112ba..9be3ab7df0 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -110,6 +110,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "sendrawtransaction", 1, "maxfeerate" }, { "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 1, "maxfeerate" }, + { "submitpackage", 0, "package" }, { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, diff --git a/src/rpc/mempool.cpp b/src/rpc/mempool.cpp index 01c75bfda3..dbba00fd9d 100644 --- a/src/rpc/mempool.cpp +++ b/src/rpc/mempool.cpp @@ -5,6 +5,7 @@ #include <rpc/blockchain.h> +#include <chainparams.h> #include <core_io.h> #include <fs.h> #include <policy/rbf.h> @@ -729,6 +730,150 @@ static RPCHelpMan savemempool() }; } +static RPCHelpMan submitpackage() +{ + return RPCHelpMan{"submitpackage", + "Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\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" + , + { + {"package", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of raw transactions.", + { + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::OBJ_DYN, "tx-results", "transaction results keyed by wtxid", + { + {RPCResult::Type::OBJ, "wtxid", "transaction wtxid", { + {RPCResult::Type::STR_HEX, "txid", "The transaction hash in hex"}, + {RPCResult::Type::STR_HEX, "other-wtxid", /*optional=*/true, "The wtxid of a different transaction with the same txid but different witness found in the mempool. This means the submitted transaction was ignored."}, + {RPCResult::Type::NUM, "vsize", "Virtual transaction size as defined in BIP 141."}, + {RPCResult::Type::OBJ, "fees", "Transaction fees", { + {RPCResult::Type::STR_AMOUNT, "base", "transaction fee in " + CURRENCY_UNIT}, + }}, + }} + }}, + {RPCResult::Type::STR_AMOUNT, "package-feerate", /*optional=*/true, "package feerate used for feerate checks in " + CURRENCY_UNIT + " per KvB. Excludes transactions which were deduplicated or accepted individually."}, + {RPCResult::Type::ARR, "replaced-transactions", /*optional=*/true, "List of txids of replaced transactions", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + }, + }, + RPCExamples{ + HelpExampleCli("testmempoolaccept", "[rawtx1, rawtx2]") + + HelpExampleCli("submitpackage", "[rawtx1, rawtx2]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + if (!Params().IsMockableChain()) { + throw std::runtime_error("submitpackage is for regression testing (-regtest mode) only"); + } + RPCTypeCheck(request.params, { + UniValue::VARR, + }); + 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, + "Array must contain between 1 and " + ToString(MAX_PACKAGE_COUNT) + " transactions."); + } + + std::vector<CTransactionRef> txns; + txns.reserve(raw_transactions.size()); + for (const auto& rawtx : raw_transactions.getValues()) { + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, rawtx.get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, + "TX decode failed: " + rawtx.get_str() + " Make sure the tx has at least one input."); + } + txns.emplace_back(MakeTransactionRef(std::move(mtx))); + } + + NodeContext& node = EnsureAnyNodeContext(request.context); + CTxMemPool& mempool = EnsureMemPool(node); + CChainState& chainstate = EnsureChainman(node).ActiveChainstate(); + const auto package_result = WITH_LOCK(::cs_main, return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/ false)); + + // First catch any errors. + switch(package_result.m_state.GetResult()) { + case PackageValidationResult::PCKG_RESULT_UNSET: break; + case PackageValidationResult::PCKG_POLICY: + { + throw JSONRPCTransactionError(TransactionError::INVALID_PACKAGE, + package_result.m_state.GetRejectReason()); + } + case PackageValidationResult::PCKG_MEMPOOL_ERROR: + { + throw JSONRPCTransactionError(TransactionError::MEMPOOL_ERROR, + package_result.m_state.GetRejectReason()); + } + case PackageValidationResult::PCKG_TX: + { + for (const auto& tx : txns) { + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); + if (it != package_result.m_tx_results.end() && it->second.m_state.IsInvalid()) { + throw JSONRPCTransactionError(TransactionError::MEMPOOL_REJECTED, + strprintf("%s failed: %s", tx->GetHash().ToString(), it->second.m_state.GetRejectReason())); + } + } + // If a PCKG_TX error was returned, there must have been an invalid transaction. + NONFATAL_UNREACHABLE(); + } + } + for (const auto& tx : txns) { + size_t num_submitted{0}; + std::string err_string; + const auto err = BroadcastTransaction(node, tx, err_string, 0, true, true); + if (err != TransactionError::OK) { + throw JSONRPCTransactionError(err, + strprintf("transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)", + err_string, num_submitted)); + } + } + UniValue rpc_result{UniValue::VOBJ}; + UniValue tx_result_map{UniValue::VOBJ}; + std::set<uint256> replaced_txids; + for (const auto& tx : txns) { + auto it = package_result.m_tx_results.find(tx->GetWitnessHash()); + CHECK_NONFATAL(it != package_result.m_tx_results.end()); + UniValue result_inner{UniValue::VOBJ}; + result_inner.pushKV("txid", tx->GetHash().GetHex()); + if (it->second.m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) { + result_inner.pushKV("other-wtxid", it->second.m_other_wtxid.value().GetHex()); + } + if (it->second.m_result_type == MempoolAcceptResult::ResultType::VALID || + it->second.m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) { + result_inner.pushKV("vsize", int64_t{it->second.m_vsize.value()}); + UniValue fees(UniValue::VOBJ); + fees.pushKV("base", ValueFromAmount(it->second.m_base_fees.value())); + result_inner.pushKV("fees", fees); + if (it->second.m_replaced_transactions.has_value()) { + for (const auto& ptx : it->second.m_replaced_transactions.value()) { + replaced_txids.insert(ptx->GetHash()); + } + } + } + tx_result_map.pushKV(tx->GetWitnessHash().GetHex(), result_inner); + } + rpc_result.pushKV("tx-results", tx_result_map); + if (package_result.m_package_feerate.has_value()) { + rpc_result.pushKV("package-feerate", ValueFromAmount(package_result.m_package_feerate.value().GetFeePerK())); + } + UniValue replaced_list(UniValue::VARR); + for (const uint256& hash : replaced_txids) replaced_list.push_back(hash.ToString()); + rpc_result.pushKV("replaced-transactions", replaced_list); + return rpc_result; + }, + }; +} + void RegisterMempoolRPCCommands(CRPCTable& t) { static const CRPCCommand commands[]{ @@ -741,6 +886,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t) {"blockchain", &getmempoolinfo}, {"blockchain", &getrawmempool}, {"blockchain", &savemempool}, + {"hidden", &submitpackage}, }; for (const auto& c : commands) { t.appendCommand(c.name, &c); diff --git a/src/test/fuzz/rpc.cpp b/src/test/fuzz/rpc.cpp index e4e83c3f32..26913a41d2 100644 --- a/src/test/fuzz/rpc.cpp +++ b/src/test/fuzz/rpc.cpp @@ -159,6 +159,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{ "signrawtransactionwithkey", "submitblock", "submitheader", + "submitpackage", "syncwithvalidationinterfacequeue", "testmempoolaccept", "uptime", diff --git a/src/util/error.cpp b/src/util/error.cpp index af8cbd0353..22a5964279 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -35,6 +35,8 @@ bilingual_str TransactionErrorString(const TransactionError err) return Untranslated("External signer not found"); case TransactionError::EXTERNAL_SIGNER_FAILED: return Untranslated("External signer failed to sign"); + case TransactionError::INVALID_PACKAGE: + return Untranslated("Transaction rejected due to invalid package"); // no default case, so the compiler can warn about missing cases } assert(false); diff --git a/src/util/error.h b/src/util/error.h index 4cc35eb1fd..0429de651a 100644 --- a/src/util/error.h +++ b/src/util/error.h @@ -32,6 +32,7 @@ enum class TransactionError { MAX_FEE_EXCEEDED, EXTERNAL_SIGNER_NOT_FOUND, EXTERNAL_SIGNER_FAILED, + INVALID_PACKAGE, }; bilingual_str TransactionErrorString(const TransactionError error); |