def interrupt_restore(self): ''' User wants the restoration interrupted. This is not recommended because it almost always leaves the blockchain in unusable shape. >>> from blockchain_backup.bitcoin.tests import utils as test_utils >>> test_utils.init_database() >>> restore_dir = os.path.join(gettempdir(), 'bitcoin/data/testnet3/backups/level1') >>> restore_task = RestoreTask(restore_dir) >>> restore_task.manager = BitcoinManager(restore_task.log_name) >>> test_utils.start_fake_restore() >>> restore_task.interrupt_restore() True ''' max_secs = 3 seconds = 0 try: bin_dir = os.path.join(virtualenv_dir(), 'bin') args = [ os.path.join(bin_dir, 'killmatch'), constants.RESTORE_PROGRAM ] args = [ os.path.join('/usr/local/bin', 'killmatch'), constants.RESTORE_PROGRAM ] self.log(f'args: {args}') attempts = 0 while bitcoin_utils.is_restore_running() and attempts < 5: command.run(*args) if bitcoin_utils.is_restore_running(): sleep(3) attempts += 1 except CalledProcessError as cpe: self.log(cpe) self.log(format_exc()) # a new page was displayed so give socketio time to connect while seconds < max_secs: self.manager.update_header(self.STOPPED_RESTORE) self.manager.update_subnotice(self.STOP_RESTORE_NOT_COMPLETE) self.manager.notify_done() sleep(1) seconds += 1 # return value is for testing purposes only return not bitcoin_utils.is_restore_running()
def wait_for_restore(self, restore_process): ''' Wait for the restore to finish and display data while waiting. >>> from blockchain_backup.bitcoin.tests import utils as test_utils >>> test_utils.init_database() >>> restore_task = RestoreTask(os.path.join(gettempdir(), 'bitcoin/data/testnet3/backups/level1')) >>> restore_task.manager = BitcoinManager(restore_task.log_name) >>> restore_process = restore_task.start_restore() >>> restore_process is not None True >>> restore_task.wait_for_restore(restore_process) >>> test_utils.stop_restore() >>> restore_task.wait_for_restore(None) >>> test_utils.stop_restore() ''' def show_line(line): if line is not None and line.startswith('Copying:'): index = line.rfind(os.sep) if index > 0: line = f'<strong>Copying: </strong>{line[index + 1:]}' self.manager.update_progress(line) self.log('starting to wait for restore') if restore_process is None: log_path = os.path.join(BASE_LOG_DIR, whoami(), 'bcb-restore.log') # wait until the log appears while bitcoin_utils.is_restore_running( ) and not self.is_interrupted(): if not os.path.exists(log_path): sleep(1) # then display the restore details while bitcoin_utils.is_restore_running( ) and not self.is_interrupted(): with open(log_path, 'rt') as restore_log: show_line(restore_log.readline()) else: while (restore_process.poll() is None and not self.is_interrupted()): show_line(restore_process.stdout.readline()) self.log('finished waiting for restore')
def check_for_conflicts(self, request): ''' Check if there are other apps/tasks running. ''' response = None # check that no other bitcoin-core app is running if bitcoin_utils.is_bitcoin_core_running(): log(f'bitcoind running: {bitcoin_utils.is_bitcoind_running()}') log(f'bitcoin_qt running: {bitcoin_utils.is_bitcoin_qt_running()}') log(f'bitcoin_tx running: {bitcoin_utils.is_bitcoin_tx_running()}') response = warn_core_running(request) # tell user if backup is running elif bitcoin_utils.is_backup_running(): log('backup running') response = warn_bcb_app_running(request, app=constants.BACKUP_PROGRAM) # tell user if restore is already running elif bitcoin_utils.is_restore_running(): log('restore running') response = self.restore() # tell user if another task is running elif accessing_wallet() or updating() or backing_up(): log('task running') response = warn_bcb_task_running(request) return response
def test_restore_with_missing_blocks(self): ''' Test restore with missing blocks in blockchain. ''' MINUTE = 60 MAX_SECONDS = 2 * MINUTE # remove a block from the blockchain data_dir = preferences.get_data_dir() block_filename = os.path.join(data_dir, 'blocks', 'blk00000.dat') if os.path.exists(block_filename): os.remove(block_filename) __, preselected_date_dir = state.get_backup_dates_and_dirs() request = self.factory.get('/bitcoin/restore/') response = views.Restore.as_view()(request) self.assertEqual(response.status_code, 200) self.assertTrue(b"<title>\nAre you sure you want to restore the Bitcoin blockchain? | Blockchain Backup\n</title>" in response.content) self.assertTrue(b'Select backup to restore:' in response.content) self.assertTrue(b'<input type="submit" value="Yes, start restoring" name="yes-start-restoring-button" id="yes-start-restoring-id" alt="Yes, start restoring" class="btn btn-primary font-weight-bold " role="button" title="Restore the blockchain now"/>' in response.content) self.assertTrue(b'<input type="submit" value="No, cancel restore" name="no-cancel-restore-button" id="no-cancel-restore-id" alt="No, cancel restore" class="btn btn-secondary font-weight-bold " role="button" title="Do not restore the blockchain."/>' in response.content) client = Client() response = client.post('/bitcoin/restore/', {'backup_dates_with_dirs': preselected_date_dir[0] }) self.assertEqual(response.status_code, 200) self.assertTrue(b"<title>\nRestoring Bitcoin Blockchain | Blockchain Backup\n</title>" in response.content) self.assertTrue(b'WARNING: Do not shut down your computer until the restore finishes.' in response.content) self.assertTrue(b'Starting to restore the blockchain' in response.content) self.assertFalse(b'<input type="submit" value="Yes, start restoring" name="yes-start-restoring-button" id="yes-start-restoring-id" alt="Yes, start restoring" class="btn btn-primary font-weight-bold " role="button" title="Restore the blockchain now"/>' in response.content) # wait until the restore finishes seconds = 0 while not is_restore_running() and seconds < MAX_SECONDS: sleep(MINUTE) seconds += MINUTE # wait until bitcoind starts test_utils.start_bitcoind() seconds = 0 while not is_bitcoind_running() and seconds < MAX_SECONDS: sleep(MINUTE) seconds += MINUTE self.assertTrue(is_bitcoind_running()) # allow bitcoind to run for a while seconds = 0 while is_bitcoind_running() and seconds < MAX_SECONDS: sleep(MINUTE) seconds += MINUTE if is_bitcoind_running(): test_utils.stop_bitcoind() shutdown, error_message = test_utils.check_bitcoin_log(is_bitcoind_running) self.assertTrue(shutdown) self.assertTrue(error_message is None)
def restore_files_and_dirs(self): ''' Restore files and directories to a previous state of the blockchain. >>> from blockchain_backup.bitcoin.tests import utils as test_utils >>> test_utils.init_database() >>> restore_dir = os.path.join(gettempdir(), 'bitcoin/data/testnet3/backups/level1') >>> restore_task = RestoreTask(restore_dir) >>> restore_task.manager = BitcoinManager(restore_task.log_name) >>> restore_task.restore_files_and_dirs() True >>> test_utils.stop_restore() >>> test_utils.start_fake_restore() >>> restore_task = RestoreTask(restore_dir) >>> restore_task.manager = BitcoinManager(restore_task.log_name) >>> restore_task.restore_files_and_dirs() True >>> test_utils.stop_restore() ''' ok = True try: if bitcoin_utils.is_restore_running(): restore_pid = get_pid(constants.RESTORE_PROGRAM) restore_process = None self.log('{} is already running using pid: {}'.format( constants.RESTORE_PROGRAM, restore_pid)) else: self.log('starting restore') restore_process = self.start_restore() restore_pid = None if restore_process is not None or restore_pid is not None: self.wait_for_restore(restore_process) self.stop_restore(restore_process, restore_pid) else: self.manager.update_progress(self.RESTORE_UNABLE_TO_START) ok = False except: # 'bare except' because it catches more than "except Exception" ok = False self.log(format_exc()) if not ok: self.manager.update_progress(self.RESTORE_ERROR) return ok
def stop_restore(): ''' Stop the restore. >>> init_database() >>> stop_restore() ''' while bitcoin_utils.is_restore_running(): sleep(5) bin_dir = os.path.join(virtualenv_dir(), 'bin') excluded_files = bitcoin_utils.get_excluded_files() args = [os.path.join(bin_dir, 'killmatch'), f'"{constants.RESTORE_PROGRAM} --exclude {excluded_files}"'] result = command.run(*args).stdout log(f'killing restore result: {result}')
def tearDown(self): # make sure nothing is left running test_utils.stop_bitcoin_core_apps() if is_restore_running(): views.InterruptRestore.as_view()(self.factory.get('/bitcoin/interrupt_restore/')) sleep(20) if is_backup_running(): views.InterruptBackup.as_view()(self.factory.get('/bitcoin/interrupt_backup/')) sleep(20) test_utils.stop_bitcoind() sleep(20) # some tests might create a .bitcon in the home dir test_utils.delete_home_bitcoin_subdir(self.home_bitcoin_subdir_exists)
def stop_restore(self, restore_process, restore_pid): ''' Stop restore. >>> from blockchain_backup.bitcoin.tests import utils as test_utils >>> test_utils.init_database() >>> restore_task = RestoreTask(os.path.join(gettempdir(), 'bitcoin/data/testnet3/backups/level1')) >>> restore_task.manager = BitcoinManager(restore_task.log_name) >>> restore_process = restore_task.start_restore() >>> restore_process is not None True >>> restore_task.stop_restore(restore_process, None) >>> test_utils.start_fake_restore() >>> restore_pid = get_pid(constants.RESTORE_PROGRAM) >>> restore_task.stop_restore(None, restore_pid) ''' try: if restore_process is None: if bitcoin_utils.is_restore_running(): bin_dir = os.path.join(virtualenv_dir(), 'bin') args = [ os.path.join(bin_dir, 'killmatch'), '"{} --exclude {}"'.format( constants.RESTORE_PROGRAM, bitcoin_utils.get_excluded_files()) ] result = command.run(*args).stdout self.log(f'killing restore result: {result}') try: pid, returncode = os.waitpid(restore_pid, os.P_WAIT) self.log(f'waitpid {pid} return code: {returncode}') except ChildProcessError: self.log('restore_pid already dead') else: # if bcb-restore hasn't stopped yet, then kill it if restore_process.poll() is None: self.log('killing restore') restore_process.terminate() # wait until restore terminates restore_process.wait() self.log(f'restore return code: {restore_process.returncode}') except: # 'bare except' because it catches more than "except Exception" self.log(f'error while stopping restore\n{format_exc()}') self.log(f'error while stopping restore\n{format_exc()}')
def test_restore_with_bad_backup(self): ''' Test restore from a bad back up. ''' MINUTE = 60 MAX_SECONDS = 2 * MINUTE # change the data and backup dirs prefs = test_utils.get_preferences() prefs.data_dir = '/tmp/bitcoin/data-with-missing-file/testnet3/' prefs.backup_dir = '/tmp/bitcoin/data-with-missing-file/testnet3/backups/' preferences.save_preferences(prefs) __, preselected_date_dir = state.get_backup_dates_and_dirs() request = self.factory.get('/bitcoin/restore/') response = views.Restore.as_view()(request) self.assertEqual(response.status_code, 200) self.assertTrue(b"<title>\nAre you sure you want to restore the Bitcoin blockchain? | Blockchain Backup\n</title>" in response.content) self.assertTrue(b'Select backup to restore:' in response.content) self.assertTrue(b'<input type="submit" value="Yes, start restoring" name="yes-start-restoring-button" id="yes-start-restoring-id" alt="Yes, start restoring" class="btn btn-primary font-weight-bold " role="button" title="Restore the blockchain now"/>' in response.content) self.assertTrue(b'<input type="submit" value="No, cancel restore" name="no-cancel-restore-button" id="no-cancel-restore-id" alt="No, cancel restore" class="btn btn-secondary font-weight-bold " role="button" title="Do not restore the blockchain."/>' in response.content) client = Client() response = client.post('/bitcoin/restore/', {'backup_dates_with_dirs': preselected_date_dir[0] }) self.assertEqual(response.status_code, 200) self.assertTrue(b"<title>\nRestoring Bitcoin Blockchain | Blockchain Backup\n</title>" in response.content) self.assertTrue(b'WARNING: Do not shut down your computer until the restore finishes.' in response.content) self.assertTrue(b'Starting to restore the blockchain' in response.content) self.assertFalse(b'<input type="submit" value="Yes, start restoring" name="yes-start-restoring-button" id="yes-start-restoring-id" alt="Yes, start restoring" class="btn btn-primary font-weight-bold " role="button" title="Restore the blockchain now"/>' in response.content) # wait until the restore finishes seconds = 0 while not is_restore_running() and seconds < MAX_SECONDS: sleep(MINUTE) seconds += MINUTE self.assertFalse(is_bitcoind_running()) self.assertFalse(state.get_backups_enabled()) name = 'last-updated-2020-01-17 14:41' last_updated_file = os.path.join(prefs.backup_dir, 'level1', name) self.assertFalse(os.path.exists(last_updated_file))
def get_page(self, request): global backup_task log('backing up blockchain') clear_action_updates() # check that no other bitcoin-core app is running if bitcoin_utils.is_bitcoin_core_running(): message = NO_BACKUP_IF_CORE_RUNNING response = warn_core_running(request, message=message) # tell user if another app is running elif bitcoin_utils.is_restore_running(): response = warn_bcb_app_running(request, app=constants.RESTORE_PROGRAM) # tell user if another task is running elif accessing_wallet() or updating() or restoring(): response = warn_bcb_task_running(request) # tell user if backups have been disabled elif not state.get_backups_enabled(): response = HttpResponseRedirect('/bitcoin/change_backup_status/') log('tried to backup when backups disabled') log(f'response: {response}') else: SUBNOTICE1 = 'If you need to stop the backup, then <a class="btn btn-secondary " href="/bitcoin/interrupt_backup/"' SUBNOTICE2 = 'role="button" id="stop-button" title="Click to stop backing up the blockchain">click here</a>.' SUBNOTICE = f'{SUBNOTICE1} {SUBNOTICE2}' context = bitcoin_utils.get_blockchain_context() data_dir_ok, error = preferences.data_dir_ok() if not preferences.bin_dir_ok(): context['notice'] = BAD_BIN_DIR context['subnotice'] = '' elif not data_dir_ok: context['notice'] = BAD_DATA_DIR context['subnotice'] = error else: context['header'] = "Backing up bitcoin's blockchain" context[ 'notice'] = 'WARNING: Stopping the backup could damage the ability to restore the blockchain.' context['subnotice'] = SUBNOTICE context['progress'] = 'Starting to back up the blockchain' context['update_interval'] = '1000' if backing_up(): log('already backing_up blockchain') else: from blockchain_backup.bitcoin.backup import BackupTask backup_task = BackupTask() backup_task.start() log('backup started') # make sure the button doesn't appear any more bitcoin_utils.send_socketio_message('button', ' ') response = render(request, 'bitcoin/backup.html', context=context) return response
def get_page(self, request): global update_task log('trying to update blockchain') clear_action_updates() # check that no other bitcoin-core app is running if (bitcoin_utils.is_bitcoin_qt_running() or bitcoin_utils.is_bitcoin_tx_running() or (bitcoin_utils.is_bitcoind_running() and not updating())): response = warn_core_running(request) # tell user if another blockchain_backup app is running elif bitcoin_utils.is_backup_running( ) or bitcoin_utils.is_restore_running(): response = warn_bcb_app_running(request) # tell user if another task is running elif accessing_wallet() or backing_up() or restoring(): response = warn_bcb_task_running(request) else: NOTICE1 = 'WARNING: Don\'t shut down your computer before you <a class="btn btn-secondary " role="button"' NOTICE2 = 'id="stop_button" href="/bitcoin/interrupt_update/" title="Click to stop updating the blockchain">Stop update</a>' NOTICE = f'{NOTICE1} {NOTICE2}' context = bitcoin_utils.get_blockchain_context() data_dir_ok, error = preferences.data_dir_ok() if not preferences.bin_dir_ok(): context['notice'] = BAD_BIN_DIR log(BAD_BIN_DIR) elif not data_dir_ok: context['notice'] = BAD_DATA_DIR log(error) else: context['header'] = "Updating Bitcoin's blockchain" context[ 'notice'] = 'WARNING: Don\'t shut down your computer before you <a class="btn btn-secondary " role="button" id="stop_button" href="/bitcoin/interrupt_update/" title="Click to stop updating the blockchain">Stop update</a>' context[ 'subnotice'] = 'Shutting down before this window says it is safe <em>could damage the blockchain</em>.' context['progress'] = 'Starting to update the blockchain' context['update_interval'] = '5000' if updating(): log('already updating blockchain') else: from blockchain_backup.bitcoin.update import UpdateTask update_task = UpdateTask() update_task.start() log('UpdateTask started') # make sure the button doesn't appear any more bitcoin_utils.send_socketio_message('button', ' ') log(f'context: {context}') response = render(request, 'bitcoin/update.html', context=context) return response
def get_page(self, request): global accessing_wallet_task log('accessing bitcoin interactively') clear_action_updates() # it's ok if bitcoin-qt is running in our task if (bitcoin_utils.is_bitcoind_running() or bitcoin_utils.is_bitcoin_tx_running() or (bitcoin_utils.is_bitcoin_qt_running() and not accessing_wallet())): response = warn_core_running(request) # tell user if another app is running elif bitcoin_utils.is_backup_running( ) or bitcoin_utils.is_restore_running(): response = warn_bcb_app_running(request) # tell user if another task is running elif updating() or backing_up() or restoring(): response = warn_bcb_task_running(request) else: SUBNOTICE1 = "Waiting until you exit Bitcoin-QT.<p>Bitcoin-QT will start in another window." SUBNOTICE2 = "You can send and receive transactions from that window." SUBNOTICE3 = "After you exit Bitcoin-QT, Blockchain Backup will continue updating the blockchain until it's time to back it up." SUBNOTICE4 = "Don't forget to back up your wallet routinely." SUBNOTICE = f'{SUBNOTICE1} {SUBNOTICE2} {SUBNOTICE3} {SUBNOTICE4}' context = bitcoin_utils.get_blockchain_context() data_dir_ok, error = preferences.data_dir_ok() if not preferences.bin_dir_ok(): context['notice'] = BAD_BIN_DIR elif not data_dir_ok: context['notice'] = BAD_DATA_DIR context['subnotice'] = error else: context[ 'header'] = "Accessing Bitcoin Core Wallet Interactively" context[ 'notice'] = 'WARNING: Do not shut down your computer until Bitcoin-QT stops completely.' context['subnotice'] = SUBNOTICE if accessing_wallet(): log('already accessing wallet') else: from blockchain_backup.bitcoin.access_wallet import AccessWalletTask accessing_wallet_task = AccessWalletTask() accessing_wallet_task.start() log('access wallet started') # make sure the button doesn't appear any more bitcoin_utils.send_socketio_message('button', ' ') response = render(request, 'bitcoin/access_wallet.html', context=context) return response