diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -27,11 +27,13 @@ #include #include #include +#include #ifdef ENABLE_WALLET #include #endif #include +#include #include @@ -1289,10 +1291,10 @@ "\"hex\" (string) The transaction hash in hex\n" "\nExamples:\n" "\nCreate a transaction\n" + - HelpExampleCli("createrawtransaction", - "\"[{\\\"txid\\\" : " - "\\\"mytxid\\\",\\\"vout\\\":0}]\" " - "\"{\\\"myaddress\\\":0.01}\"") + + HelpExampleCli( + "createrawtransaction", + "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" " + "\"{\\\"myaddress\\\":0.01}\"") + "Sign the transaction, and get back the hex\n" + HelpExampleCli("signrawtransaction", "\"myhex\"") + "\nSend the transaction (signed hex)\n" + @@ -1301,7 +1303,8 @@ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")); } - LOCK(cs_main); + std::promise promise; + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); // parse hex string from parameter @@ -1319,38 +1322,57 @@ nMaxRawTxFee = Amount::zero(); } - CCoinsViewCache &view = *pcoinsTip; - bool fHaveChain = false; - for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) { - const Coin &existingCoin = view.AccessCoin(COutPoint(txid, o)); - fHaveChain = !existingCoin.IsSpent(); - } + { // cs_main scope + LOCK(cs_main); + CCoinsViewCache &view = *pcoinsTip; + bool fHaveChain = false; + for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) { + const Coin &existingCoin = view.AccessCoin(COutPoint(txid, o)); + fHaveChain = !existingCoin.IsSpent(); + } + + bool fHaveMempool = g_mempool.exists(txid); + if (!fHaveMempool && !fHaveChain) { + // Push to local node and sync with wallets. + CValidationState state; + bool fMissingInputs; + if (!AcceptToMemoryPool(config, g_mempool, state, std::move(tx), + fLimitFree, &fMissingInputs, false, + nMaxRawTxFee)) { + if (state.IsInvalid()) { + throw JSONRPCError(RPC_TRANSACTION_REJECTED, + strprintf("%i: %s", + state.GetRejectCode(), + state.GetRejectReason())); + } - bool fHaveMempool = g_mempool.exists(txid); - if (!fHaveMempool && !fHaveChain) { - // Push to local node and sync with wallets. - CValidationState state; - bool fMissingInputs; - if (!AcceptToMemoryPool(config, g_mempool, state, std::move(tx), - fLimitFree, &fMissingInputs, false, - nMaxRawTxFee)) { - if (state.IsInvalid()) { - throw JSONRPCError(RPC_TRANSACTION_REJECTED, - strprintf("%i: %s", state.GetRejectCode(), - state.GetRejectReason())); - } else { if (fMissingInputs) { throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs"); } throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason()); + } else { + // If wallet is enabled, ensure that the wallet has been made + // aware of the new transaction prior to returning. This + // prevents a race where a user might call sendrawtransaction + // with a transaction to/from their wallet, immediately call + // some wallet RPC, and get a stale result because callbacks + // have not yet been processed. + CallFunctionInValidationInterfaceQueue( + [&promise] { promise.set_value(); }); } + } else if (fHaveChain) { + throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, + "transaction already in block chain"); + } else { + // Make sure we don't block forever if re-sending a transaction + // already in mempool. + promise.set_value(); } - } else if (fHaveChain) { - throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, - "transaction already in block chain"); - } + } // cs_main + + promise.get_future().wait(); if (!g_connman) { throw JSONRPCError( @@ -1360,6 +1382,7 @@ CInv inv(MSG_TX, txid); g_connman->ForEachNode([&inv](CNode *pnode) { pnode->PushInventory(inv); }); + return txid.GetHex(); }