From 81cd9588484cb4f4050ea4e239da0681111795db Mon Sep 17 00:00:00 2001 From: Glenn Willen Date: Tue, 8 Jan 2019 22:16:50 -0800 Subject: Factor BroadcastTransaction out of sendrawtransaction Factor out a new BroadcastTransaction function, performing the core work of the sendrawtransaction rpc, so that it can be used from the GUI code. Move it from src/rpc/ to src/node/. --- src/node/transaction.cpp | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/node/transaction.cpp (limited to 'src/node/transaction.cpp') diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp new file mode 100644 index 000000000..47c0323f1 --- /dev/null +++ b/src/node/transaction.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2018 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include + +#include + +uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) { + std::promise promise; + const uint256& hashTx = tx->GetHash(); + + CAmount nMaxRawTxFee = maxTxFee; + if (allowhighfees) + nMaxRawTxFee = 0; + + { // 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(hashTx, o)); + fHaveChain = !existingCoin.IsSpent(); + } + bool fHaveMempool = mempool.exists(hashTx); + if (!fHaveMempool && !fHaveChain) { + // push to local node and sync with wallets + CValidationState state; + bool fMissingInputs; + if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, + nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) { + if (state.IsInvalid()) { + throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state)); + } else { + if (fMissingInputs) { + throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs"); + } + throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state)); + } + } 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(); + } + + } // cs_main + + promise.get_future().wait(); + + if(!g_connman) + throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + + CInv inv(MSG_TX, hashTx); + g_connman->ForEachNode([&inv](CNode* pnode) + { + pnode->PushInventory(inv); + }); + + return hashTx; +} -- cgit v1.2.3 From bd0dbe8763fc3029cf96531c9ccaba280b939445 Mon Sep 17 00:00:00 2001 From: Glenn Willen Date: Sat, 9 Feb 2019 20:51:33 -0800 Subject: Switch away from exceptions in refactored tx code After refactoring general-purpose PSBT and transaction code out of RPC code, for use in the GUI, it's no longer appropriate to throw exceptions. Instead we now return bools for success, and take an output parameter for an error object. We still use JSONRPCError() for the error objects, since only RPC callers actually care about the error codes. --- src/node/transaction.cpp | 56 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) (limited to 'src/node/transaction.cpp') diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 47c0323f1..6c4efb3d2 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -13,9 +12,36 @@ #include -uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) { +const char* TransactionErrorString(const TransactionError err) +{ + switch (err) { + case TransactionError::OK: + return "No error"; + case TransactionError::MISSING_INPUTS: + return "Missing inputs"; + case TransactionError::ALREADY_IN_CHAIN: + return "Transaction already in block chain"; + case TransactionError::P2P_DISABLED: + return "Peer-to-peer functionality missing or disabled"; + case TransactionError::MEMPOOL_REJECTED: + return "Transaction rejected by AcceptToMemoryPool"; + case TransactionError::MEMPOOL_ERROR: + return "AcceptToMemoryPool failed"; + case TransactionError::INVALID_PSBT: + return "PSBT is not sane"; + case TransactionError::SIGHASH_MISMATCH: + return "Specified sighash value does not match existing value"; + + case TransactionError::UNKNOWN_ERROR: + default: break; + } + return "Unknown error"; +} + +bool BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, TransactionError& error, std::string& err_string, const bool allowhighfees) +{ std::promise promise; - const uint256& hashTx = tx->GetHash(); + hashTx = tx->GetHash(); CAmount nMaxRawTxFee = maxTxFee; if (allowhighfees) @@ -37,12 +63,17 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) { if (state.IsInvalid()) { - throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state)); + err_string = FormatStateMessage(state); + error = TransactionError::MEMPOOL_REJECTED; + return false; } else { if (fMissingInputs) { - throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs"); + error = TransactionError::MISSING_INPUTS; + return false; } - throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state)); + err_string = FormatStateMessage(state); + error = TransactionError::MEMPOOL_ERROR; + return false; } } else { // If wallet is enabled, ensure that the wallet has been made aware @@ -55,7 +86,8 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) }); } } else if (fHaveChain) { - throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); + error = TransactionError::ALREADY_IN_CHAIN; + return false; } else { // Make sure we don't block forever if re-sending // a transaction already in mempool. @@ -66,8 +98,10 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) promise.get_future().wait(); - if(!g_connman) - throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + if(!g_connman) { + error = TransactionError::P2P_DISABLED; + return false; + } CInv inv(MSG_TX, hashTx); g_connman->ForEachNode([&inv](CNode* pnode) @@ -75,5 +109,5 @@ uint256 BroadcastTransaction(const CTransactionRef tx, const bool allowhighfees) pnode->PushInventory(inv); }); - return hashTx; -} + return true; + } -- cgit v1.2.3 From 102faad81efa1cb12c29c466cfe81fc8c7351e1d Mon Sep 17 00:00:00 2001 From: Glenn Willen Date: Wed, 9 Jan 2019 03:08:32 -0800 Subject: Factor out combine / finalize / extract PSBT helpers Refactor the new CombinePSBT, FinalizePSBT, and FinalizeAndExtractPSBT general-purpose functions out of the combinepsbt and finalizepsbt RPCs, for use in the GUI code. --- src/node/transaction.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/node/transaction.cpp') diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 6c4efb3d2..c9cdd0d1c 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -29,6 +29,8 @@ const char* TransactionErrorString(const TransactionError err) return "AcceptToMemoryPool failed"; case TransactionError::INVALID_PSBT: return "PSBT is not sane"; + case TransactionError::PSBT_MISMATCH: + return "PSBTs not compatible (different transactions)"; case TransactionError::SIGHASH_MISMATCH: return "Specified sighash value does not match existing value"; -- cgit v1.2.3