wallet: Disallow abandon of conflicted txes

This commit is contained in:
MarcoFalke 2018-01-27 17:45:32 -05:00
parent 598a9c4e4d
commit fa795cf9c5
No known key found for this signature in database
GPG key ID: CE2B75697E69A548
4 changed files with 19 additions and 9 deletions

View file

@ -2190,14 +2190,14 @@ UniValue abandontransaction(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || request.params.size() != 1) if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error( throw std::runtime_error(
"abandontransaction \"txid\"\n" "abandontransaction \"txid\"\n"
"\nMark in-wallet transaction <txid> as abandoned\n" "\nMark in-wallet transaction <txid> as abandoned\n"
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n" "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
"It only works on transactions which are not included in a block and are not currently in the mempool.\n" "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
"It has no effect on transactions which are already conflicted or abandoned.\n" "It has no effect on transactions which are already abandoned.\n"
"\nArguments:\n" "\nArguments:\n"
"1. \"txid\" (string, required) The transaction id\n" "1. \"txid\" (string, required) The transaction id\n"
"\nResult:\n" "\nResult:\n"
@ -2205,6 +2205,7 @@ UniValue abandontransaction(const JSONRPCRequest& request)
+ HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
); );
}
ObserveSafeMode(); ObserveSafeMode();

View file

@ -1083,7 +1083,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{ {
LOCK2(cs_main, cs_wallet); LOCK2(cs_main, cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx); const CWalletTx* wtx = GetWalletTx(hashTx);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 && !wtx->InMempool(); return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool();
} }
bool CWallet::AbandonTransaction(const uint256& hashTx) bool CWallet::AbandonTransaction(const uint256& hashTx)
@ -1099,7 +1099,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(hashTx); auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& origtx = it->second; CWalletTx& origtx = it->second;
if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) { if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false; return false;
} }

View file

@ -8,11 +8,12 @@
descendants as abandoned which allows their inputs to be respent. It can be descendants as abandoned which allows their inputs to be respent. It can be
used to replace "stuck" or evicted transactions. It only works on transactions used to replace "stuck" or evicted transactions. It only works on transactions
which are not included in a block and are not currently in the mempool. It has which are not included in a block and are not currently in the mempool. It has
no effect on transactions which are already conflicted or abandoned. no effect on transactions which are already abandoned.
""" """
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import * from test_framework.util import *
class AbandonConflictTest(BitcoinTestFramework): class AbandonConflictTest(BitcoinTestFramework):
def set_test_params(self): def set_test_params(self):
self.num_nodes = 2 self.num_nodes = 2
@ -28,6 +29,11 @@ class AbandonConflictTest(BitcoinTestFramework):
sync_mempools(self.nodes) sync_mempools(self.nodes)
self.nodes[1].generate(1) self.nodes[1].generate(1)
# Can not abandon non-wallet transaction
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: self.nodes[0].abandontransaction(txid='ff' * 32))
# Can not abandon confirmed transaction
assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: self.nodes[0].abandontransaction(txid=txA))
sync_blocks(self.nodes) sync_blocks(self.nodes)
newbalance = self.nodes[0].getbalance() newbalance = self.nodes[0].getbalance()
assert(balance - newbalance < Decimal("0.001")) #no more than fees lost assert(balance - newbalance < Decimal("0.001")) #no more than fees lost

View file

@ -231,13 +231,16 @@ def test_unconfirmed_not_spendable(rbf_node, rbf_node_address):
assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == bumpid], []) assert_equal([t for t in rbf_node.listunspent(minconf=0, include_unsafe=False) if t["txid"] == bumpid], [])
# submit a block with the rbf tx to clear the bump tx out of the mempool, # submit a block with the rbf tx to clear the bump tx out of the mempool,
# then call abandon to make sure the wallet doesn't attempt to resubmit the # then invalidate the block so the rbf tx will be put back in the mempool.
# bump tx, then invalidate the block so the rbf tx will be put back in the # This makes it possible to check whether the rbf tx outputs are
# mempool. this makes it possible to check whether the rbf tx outputs are
# spendable before the rbf tx is confirmed. # spendable before the rbf tx is confirmed.
block = submit_block_with_tx(rbf_node, rbftx) block = submit_block_with_tx(rbf_node, rbftx)
rbf_node.abandontransaction(bumpid) # Can not abandon conflicted tx
assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: rbf_node.abandontransaction(txid=bumpid))
rbf_node.invalidateblock(block.hash) rbf_node.invalidateblock(block.hash)
# Call abandon to make sure the wallet doesn't attempt to resubmit
# the bump tx and hope the wallet does not rebroadcast before we call.
rbf_node.abandontransaction(bumpid)
assert bumpid not in rbf_node.getrawmempool() assert bumpid not in rbf_node.getrawmempool()
assert rbfid in rbf_node.getrawmempool() assert rbfid in rbf_node.getrawmempool()