diff --git a/lbrynet/extras/daemon/Daemon.py b/lbrynet/extras/daemon/Daemon.py index 0b23ac3a7..4b92bea18 100644 --- a/lbrynet/extras/daemon/Daemon.py +++ b/lbrynet/extras/daemon/Daemon.py @@ -2768,6 +2768,26 @@ class Daemon(AuthJSONRPCServer): page, page_size ) + @requires(WALLET_COMPONENT) + def jsonrpc_utxo_release(self, account_id=None): + """ + When spending a UTXO it is locally locked to prevent double spends; + occasionally this can result in a UTXO being locked which ultimately + did not get spent (failed to broadcast, spend transaction was not + accepted by blockchain node, etc). This command releases the lock + on all UTXOs in your account. + + Usage: + utxo_release [ | --account_id=] + + Options: + --account_id= : (str) id of the account to query + + Returns: + None + """ + return self.get_account_or_default(account_id).release_all_outputs() + @requires(WALLET_COMPONENT) def jsonrpc_block_show(self, blockhash=None, height=None): """ diff --git a/lbrynet/extras/wallet/account.py b/lbrynet/extras/wallet/account.py index 441e7ee3a..05316a9f2 100644 --- a/lbrynet/extras/wallet/account.py +++ b/lbrynet/extras/wallet/account.py @@ -236,3 +236,6 @@ class Account(BaseAccount): ) return tx + + async def release_all_outputs(self): + await self.ledger.db.release_all_outputs(self) diff --git a/lbrynet/extras/wallet/database.py b/lbrynet/extras/wallet/database.py index b81e88d0a..9b8ef3fb9 100644 --- a/lbrynet/extras/wallet/database.py +++ b/lbrynet/extras/wallet/database.py @@ -117,3 +117,11 @@ class WalletDatabase(BaseDatabase): channel.private_key = private_key certificates.append(channel) return certificates + + async def release_all_outputs(self, account): + await self.db.execute( + "UPDATE txo SET is_reserved = 0 WHERE" + " is_reserved = 1 AND txo.address IN (" + " SELECT address from pubkey_address WHERE account = ?" + " )", [account.public_key.address] + ) diff --git a/tests/integration/wallet/test_commands.py b/tests/integration/wallet/test_commands.py index 247347e8a..50a5f16d6 100644 --- a/tests/integration/wallet/test_commands.py +++ b/tests/integration/wallet/test_commands.py @@ -846,3 +846,14 @@ class TransactionCommandsTestCase(CommandTestCase): # inexistent result = await self.daemon.jsonrpc_transaction_show('0'*64) self.assertFalse(result['success']) + + async def test_utxo_release(self): + sendtxid = await self.blockchain.send_to_address( + await self.account.receiving.get_or_create_usable_address(), 1 + ) + await self.confirm_tx(sendtxid) + await self.assertBalance(self.account, '11.0') + await self.ledger.reserve_outputs(await self.account.get_utxos()) + await self.assertBalance(self.account, '0.0') + await self.daemon.jsonrpc_utxo_release() + await self.assertBalance(self.account, '11.0')