Skip to content

Commit be0e8b4

Browse files
author
MarcoFalke
committed
Merge #15713: refactor: Replace chain relayTransactions/submitMemoryPool by higher method
fb62f12 Tidy up BroadcastTransaction() (John Newbery) b8eecf8 Remove unused submitToMemoryPool and relayTransactions Chain interfaces (Antoine Riard) 8753f56 Remove duplicate checks in SubmitMemoryPoolAndRelay (Antoine Riard) 611291c Introduce CWalletTx::SubmitMemoryPoolAndRelay (Antoine Riard) 8c8aa19 Add BroadcastTransaction utility usage in Chain interface (Antoine Riard) Pull request description: Remove CWalletTx::AcceptToMemoryPool Replace CWalletTx::RelayWalletTransaction by SubmitMemoryPoolAndRelay Add a relay flag to broadcastTransaction because wasn't sure of ReacceptWalletTransactions semantic. Obviously, working on implementing bitcoin/bitcoin#14978 (comment) to add the new higher-method in Node interface, will add a commit, just need more thought to do it cleanly ACKs for top commit: MarcoFalke: re-ACK fb62f12 Sjors: re-ACK fb62f12 Tree-SHA512: a7ee48b0545f537fa65cac8ed4cb24e777ab90b877d4eefb87971fa93c6a59bd555b62ad8940c6ffb40592a0bd50787d27587af99f20b56af72b415b6394251f
2 parents d759b5d + fb62f12 commit be0e8b4

File tree

7 files changed

+87
-97
lines changed

7 files changed

+87
-97
lines changed

src/interfaces/chain.cpp

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <net.h>
1212
#include <net_processing.h>
1313
#include <node/coin.h>
14+
#include <node/transaction.h>
1415
#include <policy/fees.h>
1516
#include <policy/policy.h>
1617
#include <policy/rbf.h>
@@ -150,12 +151,6 @@ class LockImpl : public Chain::Lock, public UniqueLock<CCriticalSection>
150151
LockAssertion lock(::cs_main);
151152
return CheckFinalTx(tx);
152153
}
153-
bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) override
154-
{
155-
LockAssertion lock(::cs_main);
156-
return AcceptToMemoryPool(::mempool, state, tx, nullptr /* missing inputs */, nullptr /* txn replaced */,
157-
false /* bypass limits */, absurd_fee);
158-
}
159154

160155
using UniqueLock::UniqueLock;
161156
};
@@ -291,9 +286,13 @@ class ChainImpl : public Chain
291286
auto it = ::mempool.GetIter(txid);
292287
return it && (*it)->GetCountWithDescendants() > 1;
293288
}
294-
void relayTransaction(const uint256& txid) override
289+
bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) override
295290
{
296-
RelayTransaction(txid, *g_connman);
291+
const TransactionError err = BroadcastTransaction(tx, err_string, max_tx_fee, relay, /*wait_callback*/ false);
292+
// Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures.
293+
// Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures
294+
// that Chain clients do not need to know about.
295+
return TransactionError::OK == err;
297296
}
298297
void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override
299298
{

src/interfaces/chain.h

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ class Wallet;
4343
//! asynchronously
4444
//! (https://github.com/bitcoin/bitcoin/pull/10973#issuecomment-380101269).
4545
//!
46-
//! * The relayTransactions() and submitToMemoryPool() methods could be replaced
47-
//! with a higher-level broadcastTransaction method
48-
//! (https://github.com/bitcoin/bitcoin/pull/14978#issuecomment-459373984).
49-
//!
5046
//! * The initMessages() and loadWallet() methods which the wallet uses to send
5147
//! notifications to the GUI should go away when GUI and wallet can directly
5248
//! communicate with each other without going through the node
@@ -127,11 +123,6 @@ class Chain
127123

128124
//! Check if transaction will be final given chain height current time.
129125
virtual bool checkFinalTx(const CTransaction& tx) = 0;
130-
131-
//! Add transaction to memory pool if the transaction fee is below the
132-
//! amount specified by absurd_fee. Returns false if the transaction
133-
//! could not be added due to the fee or for another reason.
134-
virtual bool submitToMemoryPool(const CTransactionRef& tx, CAmount absurd_fee, CValidationState& state) = 0;
135126
};
136127

137128
//! Return Lock interface. Chain is locked when this is called, and
@@ -164,8 +155,10 @@ class Chain
164155
//! Check if transaction has descendants in mempool.
165156
virtual bool hasDescendantsInMempool(const uint256& txid) = 0;
166157

167-
//! Relay transaction.
168-
virtual void relayTransaction(const uint256& txid) = 0;
158+
//! Transaction is added to memory pool, if the transaction fee is below the
159+
//! amount specified by max_tx_fee, and broadcast to all peers if relay is set to true.
160+
//! Return false if the transaction could not be added due to the fee or for another reason.
161+
virtual bool broadcastTransaction(const CTransactionRef& tx, std::string& err_string, const CAmount& max_tx_fee, bool relay) = 0;
169162

170163
//! Calculate mempool ancestor and descendant counts for the given transaction.
171164
virtual void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) = 0;

src/node/transaction.cpp

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,30 @@
1414

1515
#include <future>
1616

17-
TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx, std::string& err_string, const CAmount& highfee)
17+
TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback)
1818
{
19+
assert(g_connman);
1920
std::promise<void> promise;
20-
hashTx = tx->GetHash();
21+
uint256 hashTx = tx->GetHash();
22+
bool callback_set = false;
2123

2224
{ // cs_main scope
2325
LOCK(cs_main);
26+
// If the transaction is already confirmed in the chain, don't do anything
27+
// and return early.
2428
CCoinsViewCache &view = *pcoinsTip;
25-
bool fHaveChain = false;
26-
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
29+
for (size_t o = 0; o < tx->vout.size(); o++) {
2730
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
28-
fHaveChain = !existingCoin.IsSpent();
31+
// IsSpent doesnt mean the coin is spent, it means the output doesnt' exist.
32+
// So if the output does exist, then this transaction exists in the chain.
33+
if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN;
2934
}
30-
bool fHaveMempool = mempool.exists(hashTx);
31-
if (!fHaveMempool && !fHaveChain) {
32-
// push to local node and sync with wallets
35+
if (!mempool.exists(hashTx)) {
36+
// Transaction is not already in the mempool. Submit it.
3337
CValidationState state;
3438
bool fMissingInputs;
3539
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
36-
nullptr /* plTxnReplaced */, false /* bypass_limits */, highfee)) {
40+
nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) {
3741
if (state.IsInvalid()) {
3842
err_string = FormatStateMessage(state);
3943
return TransactionError::MEMPOOL_REJECTED;
@@ -44,33 +48,37 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, uint256& hashTx,
4448
err_string = FormatStateMessage(state);
4549
return TransactionError::MEMPOOL_ERROR;
4650
}
47-
} else {
48-
// If wallet is enabled, ensure that the wallet has been made aware
49-
// of the new transaction prior to returning. This prevents a race
50-
// where a user might call sendrawtransaction with a transaction
51-
// to/from their wallet, immediately call some wallet RPC, and get
52-
// a stale result because callbacks have not yet been processed.
51+
}
52+
53+
// Transaction was accepted to the mempool.
54+
55+
if (wait_callback) {
56+
// For transactions broadcast from outside the wallet, make sure
57+
// that the wallet has been notified of the transaction before
58+
// continuing.
59+
//
60+
// This prevents a race where a user might call sendrawtransaction
61+
// with a transaction to/from their wallet, immediately call some
62+
// wallet RPC, and get a stale result because callbacks have not
63+
// yet been processed.
5364
CallFunctionInValidationInterfaceQueue([&promise] {
5465
promise.set_value();
5566
});
67+
callback_set = true;
5668
}
57-
} else if (fHaveChain) {
58-
return TransactionError::ALREADY_IN_CHAIN;
59-
} else {
60-
// Make sure we don't block forever if re-sending
61-
// a transaction already in mempool.
62-
promise.set_value();
6369
}
6470

6571
} // cs_main
6672

67-
promise.get_future().wait();
68-
69-
if (!g_connman) {
70-
return TransactionError::P2P_DISABLED;
73+
if (callback_set) {
74+
// Wait until Validation Interface clients have been notified of the
75+
// transaction entering the mempool.
76+
promise.get_future().wait();
7177
}
7278

73-
RelayTransaction(hashTx, *g_connman);
79+
if (relay) {
80+
RelayTransaction(hashTx, *g_connman);
81+
}
7482

7583
return TransactionError::OK;
7684
}

src/node/transaction.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@
1111
#include <util/error.h>
1212

1313
/**
14-
* Broadcast a transaction
14+
* Submit a transaction to the mempool and (optionally) relay it to all P2P peers.
15+
*
16+
* Mempool submission can be synchronous (will await mempool entry notification
17+
* over the CValidationInterface) or asynchronous (will submit and not wait for
18+
* notification), depending on the value of wait_callback. wait_callback MUST
19+
* NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid
20+
* deadlock.
1521
*
1622
* @param[in] tx the transaction to broadcast
17-
* @param[out] &txid the txid of the transaction, if successfully broadcast
1823
* @param[out] &err_string reference to std::string to fill with error string if available
19-
* @param[in] highfee Reject txs with fees higher than this (if 0, accept any fee)
24+
* @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee)
25+
* @param[in] relay flag if both mempool insertion and p2p relay are requested
26+
* @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
2027
* return error
2128
*/
22-
NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, uint256& txid, std::string& err_string, const CAmount& highfee);
29+
NODISCARD TransactionError BroadcastTransaction(CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback);
2330

2431
#endif // BITCOIN_NODE_TRANSACTION_H

src/rpc/rawtransaction.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,14 +814,14 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request)
814814
max_raw_tx_fee = fr.GetFee((weight+3)/4);
815815
}
816816

817-
uint256 txid;
818817
std::string err_string;
819-
const TransactionError err = BroadcastTransaction(tx, txid, err_string, max_raw_tx_fee);
818+
AssertLockNotHeld(cs_main);
819+
const TransactionError err = BroadcastTransaction(tx, err_string, max_raw_tx_fee, /*relay*/ true, /*wait_callback*/ true);
820820
if (TransactionError::OK != err) {
821821
throw JSONRPCTransactionError(err, err_string);
822822
}
823823

824-
return txid.GetHex();
824+
return tx->GetHash().GetHex();
825825
}
826826

827827
static UniValue testmempoolaccept(const JSONRPCRequest& request)

src/wallet/wallet.cpp

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,8 +2134,7 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
21342134
std::map<int64_t, CWalletTx*> mapSorted;
21352135

21362136
// Sort pending wallet transactions based on their initial wallet insertion order
2137-
for (std::pair<const uint256, CWalletTx>& item : mapWallet)
2138-
{
2137+
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
21392138
const uint256& wtxid = item.first;
21402139
CWalletTx& wtx = item.second;
21412140
assert(wtx.GetHash() == wtxid);
@@ -2150,32 +2149,32 @@ void CWallet::ReacceptWalletTransactions(interfaces::Chain::Lock& locked_chain)
21502149
// Try to add wallet transactions to memory pool
21512150
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
21522151
CWalletTx& wtx = *(item.second);
2153-
CValidationState state;
2154-
wtx.AcceptToMemoryPool(locked_chain, state);
2152+
std::string unused_err_string;
2153+
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
21552154
}
21562155
}
21572156

2158-
bool CWalletTx::RelayWalletTransaction(interfaces::Chain::Lock& locked_chain)
2157+
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
21592158
{
21602159
// Can't relay if wallet is not broadcasting
21612160
if (!pwallet->GetBroadcastTransactions()) return false;
2162-
// Don't relay coinbase transactions outside blocks
2163-
if (IsCoinBase()) return false;
21642161
// Don't relay abandoned transactions
21652162
if (isAbandoned()) return false;
2166-
// Don't relay conflicted or already confirmed transactions
2167-
if (GetDepthInMainChain(locked_chain) != 0) return false;
2168-
// Don't relay transactions that aren't accepted to the mempool
2169-
CValidationState unused_state;
2170-
if (!InMempool() && !AcceptToMemoryPool(locked_chain, unused_state)) return false;
2171-
// Don't try to relay if the node is not connected to the p2p network
2172-
if (!pwallet->chain().p2pEnabled()) return false;
2173-
2174-
// Try to relay the transaction
2175-
pwallet->WalletLogPrintf("Relaying wtx %s\n", GetHash().ToString());
2176-
pwallet->chain().relayTransaction(GetHash());
21772163

2178-
return true;
2164+
// Submit transaction to mempool for relay
2165+
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString());
2166+
// We must set fInMempool here - while it will be re-set to true by the
2167+
// entered-mempool callback, if we did not there would be a race where a
2168+
// user could call sendmoney in a loop and hit spurious out of funds errors
2169+
// because we think that this newly generated transaction's change is
2170+
// unavailable as we're not yet aware that it is in the mempool.
2171+
//
2172+
// Irrespective of the failure reason, un-marking fInMempool
2173+
// out-of-order is incorrect - it should be unmarked when
2174+
// TransactionRemovedFromMempool fires.
2175+
bool ret = pwallet->chain().broadcastTransaction(tx, err_string, pwallet->m_default_max_tx_fee, relay);
2176+
fInMempool |= ret;
2177+
return ret;
21792178
}
21802179

21812180
std::set<uint256> CWalletTx::GetConflicts() const
@@ -2366,7 +2365,7 @@ void CWallet::ResendWalletTransactions()
23662365
if (m_best_block_time < nLastResend) return;
23672366
nLastResend = GetTime();
23682367

2369-
int relayed_tx_count = 0;
2368+
int submitted_tx_count = 0;
23702369

23712370
{ // locked_chain and cs_wallet scope
23722371
auto locked_chain = chain().lock();
@@ -2378,12 +2377,13 @@ void CWallet::ResendWalletTransactions()
23782377
// only rebroadcast unconfirmed txes older than 5 minutes before the
23792378
// last block was found
23802379
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
2381-
if (wtx.RelayWalletTransaction(*locked_chain)) ++relayed_tx_count;
2380+
std::string unused_err_string;
2381+
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count;
23822382
}
23832383
} // locked_chain and cs_wallet
23842384

2385-
if (relayed_tx_count > 0) {
2386-
WalletLogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed_tx_count);
2385+
if (submitted_tx_count > 0) {
2386+
WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count);
23872387
}
23882388
}
23892389

@@ -3322,12 +3322,10 @@ bool CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
33223322

33233323
if (fBroadcastTransactions)
33243324
{
3325-
// Broadcast
3326-
if (!wtx.AcceptToMemoryPool(*locked_chain, state)) {
3327-
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", FormatStateMessage(state));
3325+
std::string err_string;
3326+
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
3327+
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
33283328
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
3329-
} else {
3330-
wtx.RelayWalletTransaction(*locked_chain);
33313329
}
33323330
}
33333331
}
@@ -4662,18 +4660,6 @@ bool CWalletTx::IsImmatureCoinBase(interfaces::Chain::Lock& locked_chain) const
46624660
return GetBlocksToMaturity(locked_chain) > 0;
46634661
}
46644662

4665-
bool CWalletTx::AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state)
4666-
{
4667-
// We must set fInMempool here - while it will be re-set to true by the
4668-
// entered-mempool callback, if we did not there would be a race where a
4669-
// user could call sendmoney in a loop and hit spurious out of funds errors
4670-
// because we think that this newly generated transaction's change is
4671-
// unavailable as we're not yet aware that it is in the mempool.
4672-
bool ret = locked_chain.submitToMemoryPool(tx, pwallet->m_default_max_tx_fee, state);
4673-
fInMempool |= ret;
4674-
return ret;
4675-
}
4676-
46774663
void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type)
46784664
{
46794665
if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {

src/wallet/wallet.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -579,11 +579,8 @@ class CWalletTx
579579

580580
int64_t GetTxTime() const;
581581

582-
// Pass this transaction to the node to relay to its peers
583-
bool RelayWalletTransaction(interfaces::Chain::Lock& locked_chain);
584-
585-
/** Pass this transaction to the mempool. Fails if absolute fee exceeds absurd fee. */
586-
bool AcceptToMemoryPool(interfaces::Chain::Lock& locked_chain, CValidationState& state);
582+
// Pass this transaction to node for mempool insertion and relay to peers if flag set to true
583+
bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay);
587584

588585
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
589586
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation

0 commit comments

Comments
 (0)