5
5
6
6
#include < rpc/blockchain.h>
7
7
8
+ #include < chainparams.h>
8
9
#include < core_io.h>
9
10
#include < fs.h>
10
11
#include < policy/rbf.h>
@@ -729,6 +730,150 @@ static RPCHelpMan savemempool()
729
730
};
730
731
}
731
732
733
+ static RPCHelpMan submitpackage ()
734
+ {
735
+ return RPCHelpMan{" submitpackage" ,
736
+ " Submit a package of raw transactions (serialized, hex-encoded) to local node (-regtest only).\n "
737
+ " The package will be validated according to consensus and mempool policy rules. If all transactions pass, they will be accepted to mempool.\n "
738
+ " This RPC is experimental and the interface may be unstable. Refer to doc/policy/packages.md for documentation on package policies.\n "
739
+ " Warning: until package relay is in use, successful submission does not mean the transaction will propagate to other nodes on the network.\n "
740
+ " Currently, each transaction is broadcasted individually after submission, which means they must meet other nodes' feerate requirements alone.\n "
741
+ ,
742
+ {
743
+ {" package" , RPCArg::Type::ARR, RPCArg::Optional::NO, " An array of raw transactions." ,
744
+ {
745
+ {" rawtx" , RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, " " },
746
+ },
747
+ },
748
+ },
749
+ RPCResult{
750
+ RPCResult::Type::OBJ, " " , " " ,
751
+ {
752
+ {RPCResult::Type::OBJ_DYN, " tx-results" , " transaction results keyed by wtxid" ,
753
+ {
754
+ {RPCResult::Type::OBJ, " wtxid" , " transaction wtxid" , {
755
+ {RPCResult::Type::STR_HEX, " txid" , " The transaction hash in hex" },
756
+ {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." },
757
+ {RPCResult::Type::NUM, " vsize" , " Virtual transaction size as defined in BIP 141." },
758
+ {RPCResult::Type::OBJ, " fees" , " Transaction fees" , {
759
+ {RPCResult::Type::STR_AMOUNT, " base" , " transaction fee in " + CURRENCY_UNIT},
760
+ }},
761
+ }}
762
+ }},
763
+ {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." },
764
+ {RPCResult::Type::ARR, " replaced-transactions" , /* optional=*/ true , " List of txids of replaced transactions" ,
765
+ {
766
+ {RPCResult::Type::STR_HEX, " " , " The transaction id" },
767
+ }},
768
+ },
769
+ },
770
+ RPCExamples{
771
+ HelpExampleCli (" testmempoolaccept" , " [rawtx1, rawtx2]" ) +
772
+ HelpExampleCli (" submitpackage" , " [rawtx1, rawtx2]" )
773
+ },
774
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
775
+ {
776
+ if (!Params ().IsMockableChain ()) {
777
+ throw std::runtime_error (" submitpackage is for regression testing (-regtest mode) only" );
778
+ }
779
+ RPCTypeCheck (request.params , {
780
+ UniValue::VARR,
781
+ });
782
+ const UniValue raw_transactions = request.params [0 ].get_array ();
783
+ if (raw_transactions.size () < 1 || raw_transactions.size () > MAX_PACKAGE_COUNT) {
784
+ throw JSONRPCError (RPC_INVALID_PARAMETER,
785
+ " Array must contain between 1 and " + ToString (MAX_PACKAGE_COUNT) + " transactions." );
786
+ }
787
+
788
+ std::vector<CTransactionRef> txns;
789
+ txns.reserve (raw_transactions.size ());
790
+ for (const auto & rawtx : raw_transactions.getValues ()) {
791
+ CMutableTransaction mtx;
792
+ if (!DecodeHexTx (mtx, rawtx.get_str ())) {
793
+ throw JSONRPCError (RPC_DESERIALIZATION_ERROR,
794
+ " TX decode failed: " + rawtx.get_str () + " Make sure the tx has at least one input." );
795
+ }
796
+ txns.emplace_back (MakeTransactionRef (std::move (mtx)));
797
+ }
798
+
799
+ NodeContext& node = EnsureAnyNodeContext (request.context );
800
+ CTxMemPool& mempool = EnsureMemPool (node);
801
+ CChainState& chainstate = EnsureChainman (node).ActiveChainstate ();
802
+ const auto package_result = WITH_LOCK (::cs_main, return ProcessNewPackage (chainstate, mempool, txns, /* test_accept=*/ false ));
803
+
804
+ // First catch any errors.
805
+ switch (package_result.m_state .GetResult ()) {
806
+ case PackageValidationResult::PCKG_RESULT_UNSET: break ;
807
+ case PackageValidationResult::PCKG_POLICY:
808
+ {
809
+ throw JSONRPCTransactionError (TransactionError::INVALID_PACKAGE,
810
+ package_result.m_state .GetRejectReason ());
811
+ }
812
+ case PackageValidationResult::PCKG_MEMPOOL_ERROR:
813
+ {
814
+ throw JSONRPCTransactionError (TransactionError::MEMPOOL_ERROR,
815
+ package_result.m_state .GetRejectReason ());
816
+ }
817
+ case PackageValidationResult::PCKG_TX:
818
+ {
819
+ for (const auto & tx : txns) {
820
+ auto it = package_result.m_tx_results .find (tx->GetWitnessHash ());
821
+ if (it != package_result.m_tx_results .end () && it->second .m_state .IsInvalid ()) {
822
+ throw JSONRPCTransactionError (TransactionError::MEMPOOL_REJECTED,
823
+ strprintf (" %s failed: %s" , tx->GetHash ().ToString (), it->second .m_state .GetRejectReason ()));
824
+ }
825
+ }
826
+ // If a PCKG_TX error was returned, there must have been an invalid transaction.
827
+ NONFATAL_UNREACHABLE ();
828
+ }
829
+ }
830
+ for (const auto & tx : txns) {
831
+ size_t num_submitted{0 };
832
+ std::string err_string;
833
+ const auto err = BroadcastTransaction (node, tx, err_string, 0 , true , true );
834
+ if (err != TransactionError::OK) {
835
+ throw JSONRPCTransactionError (err,
836
+ strprintf (" transaction broadcast failed: %s (all transactions were submitted, %d transactions were broadcast successfully)" ,
837
+ err_string, num_submitted));
838
+ }
839
+ }
840
+ UniValue rpc_result{UniValue::VOBJ};
841
+ UniValue tx_result_map{UniValue::VOBJ};
842
+ std::set<uint256> replaced_txids;
843
+ for (const auto & tx : txns) {
844
+ auto it = package_result.m_tx_results .find (tx->GetWitnessHash ());
845
+ CHECK_NONFATAL (it != package_result.m_tx_results .end ());
846
+ UniValue result_inner{UniValue::VOBJ};
847
+ result_inner.pushKV (" txid" , tx->GetHash ().GetHex ());
848
+ if (it->second .m_result_type == MempoolAcceptResult::ResultType::DIFFERENT_WITNESS) {
849
+ result_inner.pushKV (" other-wtxid" , it->second .m_other_wtxid .value ().GetHex ());
850
+ }
851
+ if (it->second .m_result_type == MempoolAcceptResult::ResultType::VALID ||
852
+ it->second .m_result_type == MempoolAcceptResult::ResultType::MEMPOOL_ENTRY) {
853
+ result_inner.pushKV (" vsize" , int64_t {it->second .m_vsize .value ()});
854
+ UniValue fees (UniValue::VOBJ);
855
+ fees.pushKV (" base" , ValueFromAmount (it->second .m_base_fees .value ()));
856
+ result_inner.pushKV (" fees" , fees);
857
+ if (it->second .m_replaced_transactions .has_value ()) {
858
+ for (const auto & ptx : it->second .m_replaced_transactions .value ()) {
859
+ replaced_txids.insert (ptx->GetHash ());
860
+ }
861
+ }
862
+ }
863
+ tx_result_map.pushKV (tx->GetWitnessHash ().GetHex (), result_inner);
864
+ }
865
+ rpc_result.pushKV (" tx-results" , tx_result_map);
866
+ if (package_result.m_package_feerate .has_value ()) {
867
+ rpc_result.pushKV (" package-feerate" , ValueFromAmount (package_result.m_package_feerate .value ().GetFeePerK ()));
868
+ }
869
+ UniValue replaced_list (UniValue::VARR);
870
+ for (const uint256& hash : replaced_txids) replaced_list.push_back (hash.ToString ());
871
+ rpc_result.pushKV (" replaced-transactions" , replaced_list);
872
+ return rpc_result;
873
+ },
874
+ };
875
+ }
876
+
732
877
void RegisterMempoolRPCCommands (CRPCTable& t)
733
878
{
734
879
static const CRPCCommand commands[]{
@@ -741,6 +886,7 @@ void RegisterMempoolRPCCommands(CRPCTable& t)
741
886
{" blockchain" , &getmempoolinfo},
742
887
{" blockchain" , &getrawmempool},
743
888
{" blockchain" , &savemempool},
889
+ {" hidden" , &submitpackage},
744
890
};
745
891
for (const auto & c : commands) {
746
892
t.appendCommand (c.name , &c);
0 commit comments