wallet: Disallow abandon of conflicted txes
This commit is contained in:
parent
598a9c4e4d
commit
fa795cf9c5
4 changed files with 19 additions and 9 deletions
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue