From a3ce7d3186185706eacc00430499650bbd2dff97 Mon Sep 17 00:00:00 2001 From: yogendra sankhla <54116506+yogendra-17@users.noreply.github.com> Date: Tue, 19 Jul 2022 13:19:32 +0530 Subject: [PATCH] Feature: Rescan button for empty wallet (#1779) * Prompting rescan hint within tx-table when no transactions appear * Animation of rescan part after being forwarded * Basics option added in test-cypress for faster development on cypress tests on slower machines * addWallet command added in Cypress * Page limit in addresses view fixed Co-authored-by: moneymanolis --- cypress.json | 1 + cypress/integration/spec_devices.js | 15 ++- .../integration/spec_empty_specter_home.js | 27 ++--- cypress/integration/spec_ghost_machine.js | 23 +---- cypress/integration/spec_plugins.js | 4 +- cypress/integration/spec_qr_signing.js | 4 +- cypress/integration/spec_rescan.js | 29 ++++++ cypress/integration/spec_wallet_utxo.js | 2 +- cypress/support/commands.js | 70 +++++++++---- cypress_basics.json | 11 +++ src/cryptoadvance/specter/cli/cli_noded.py | 14 +-- .../process_controller/node_controller.py | 12 ++- .../specter/server_endpoints/wallets.py | 2 + src/cryptoadvance/specter/static/styles.css | 10 +- .../templates/includes/addresses-table.html | 2 +- .../templates/includes/page-limit-select.html | 3 - .../specter/templates/includes/tx-table.html | 99 ++++++++++++++----- .../wallet/history/wallet_history.jinja | 4 +- .../wallet/settings/wallet_settings.jinja | 38 +++++-- utils/cypress_basics.py | 26 +++++ utils/test-cypress.sh | 8 ++ 21 files changed, 279 insertions(+), 125 deletions(-) create mode 100644 cypress/integration/spec_rescan.js create mode 100644 cypress_basics.json create mode 100755 utils/cypress_basics.py diff --git a/cypress.json b/cypress.json index b5caa606e..4bf293805 100644 --- a/cypress.json +++ b/cypress.json @@ -6,6 +6,7 @@ "spec_configures_nodes.js", "spec_devices.js", "spec_ghost_machine.js", + "spec_rescan.js", "spec_qr_signing.js", "spec_wallet_send.js", "spec_wallet_utxo.js", diff --git a/cypress/integration/spec_devices.js b/cypress/integration/spec_devices.js index e928f5cc9..07f455631 100644 --- a/cypress/integration/spec_devices.js +++ b/cypress/integration/spec_devices.js @@ -1,15 +1,26 @@ describe('Test adding different devices', () => { before(() => { - cy.viewport(1200,660) - cy.visit('/') Cypress.config('includeShadowDom', true) }) // Keeps the session cookie alive, Cypress by default clears all cookies before each test beforeEach(() => { + cy.viewport(1200,660) + cy.visit('/') Cypress.Cookies.preserveOnce('session') }) + it('Filter devices', () => { + cy.get('#toggle_devices_list').click() + cy.get('#btn_new_device').click() + cy.contains('Select Your Device Type') + cy.get('#device-type-searchbar').type("Specter") + cy.get('#trezor_device_card').should('not.be.visible') + cy.get('#specter_device_card').should('be.visible') + cy.get('#specter_device_card').click() + cy.contains('Connect your Specter-DIY') + }) + it('Electrum device', () => { cy.get('#toggle_devices_list').click() cy.get('#btn_new_device').click() diff --git a/cypress/integration/spec_empty_specter_home.js b/cypress/integration/spec_empty_specter_home.js index f731e1e9a..5ac14ed95 100644 --- a/cypress/integration/spec_empty_specter_home.js +++ b/cypress/integration/spec_empty_specter_home.js @@ -1,11 +1,12 @@ describe('Completely empty specter-home', () => { - beforeEach(() => { + before(() => { cy.task("clear:specter-home") }) - it('Visits specter and clicks around', () => { + + it('Click around on the welcome page', () => { cy.viewport(1200,660) - cy.visit('/') + cy.visit('/welcome/about') cy.contains('Welcome to Specter Desktop') cy.get('#node-switch-icon').click() cy.get('[href="/nodes/node/default/"]').first().click() @@ -16,28 +17,16 @@ describe('Completely empty specter-home', () => { cy.contains('Authentication:') cy.get('[href="/settings/hwi"]').click() cy.contains('Hardware Devices Bridge') - // Hidden in Cypress behind the price - // cy.get('[href="/settings/tor"]').click() - // cy.contains('Tor configurations') + cy.get('main').scrollTo('top') + cy.contains('Tor').click({ scrollBehavior: false }) + cy.contains('Tor configurations') cy.contains("Choose plugins") cy.get('#btn_plugins').click() cy.contains("Plugins in Production") - - - }) - - it('Creates a device in Specter', () => { - cy.viewport(1200,660) - cy.visit('/') - cy.addDevice("Some Device") - }) - - it('Dummytest to enforce remove of device', () => { - cy.viewport(1200,660) - cy.visit('/') }) }) + diff --git a/cypress/integration/spec_ghost_machine.js b/cypress/integration/spec_ghost_machine.js index e6b8c33cd..e9c39693d 100644 --- a/cypress/integration/spec_ghost_machine.js +++ b/cypress/integration/spec_ghost_machine.js @@ -2,26 +2,7 @@ describe('Ghost machine', () => { it('Create a DIY device with ghost machine keys and a wallet', () => { cy.viewport(1200,660) cy.visit('/') - // addDevice is creating a DIY device with ghost machine keys - cy.addDevice("DIY ghost") - cy.get('body').then(($body) => { - if ($body.text().includes('Wallet ghost')) { - cy.contains('Wallet ghost').click() - cy.get('#btn_settings').click({"force": true}) - cy.get('#advanced_settings_tab_btn').click() - cy.get('#delete_wallet').click() - } - }) - cy.get('#btn_new_wallet').click() - cy.get('[href="./simple/"]').click() - cy.get('#diy_ghost').click() - cy.get('#wallet_name').type("Wallet ghost") - cy.get('#keysform > .centered').click() - cy.get('body').contains("New wallet was created successfully!") - // Download PDF - // unfortunately this results in weird effects in cypress run - //cy.get('#pdf-wallet-download > img').click() - cy.get('#btn_continue').click() - cy.mine2wallet("btc") + cy.addDevice('DIY ghost', 'Specter-DIY', 'ghost_machine') + cy.addWallet('Ghost wallet', null, 'segwit', 'funded', 'btc') }) }) diff --git a/cypress/integration/spec_plugins.js b/cypress/integration/spec_plugins.js index dad1feba6..ef0dd4eb3 100644 --- a/cypress/integration/spec_plugins.js +++ b/cypress/integration/spec_plugins.js @@ -11,7 +11,7 @@ describe('Test plugins', () => { it('Associate an address with a service', () => { // choose address - cy.selectWallet("Wallet ghost") + cy.selectWallet("Ghost wallet") cy.get('main').contains('Addresses').click() // Click on the first address cy.contains('td', '#0').siblings().contains('bcrt').click() @@ -24,7 +24,7 @@ describe('Test plugins', () => { cy.contains("Admin password successfully updated") // Choose address again - cy.selectWallet("Wallet ghost") + cy.selectWallet("Ghost wallet") cy.get('main').contains('Addresses').click() cy.contains('td', '#0').siblings().contains('bcrt').click() cy.get('#associate-btn').click() diff --git a/cypress/integration/spec_qr_signing.js b/cypress/integration/spec_qr_signing.js index 0d16b2355..e37c19678 100644 --- a/cypress/integration/spec_qr_signing.js +++ b/cypress/integration/spec_qr_signing.js @@ -12,7 +12,7 @@ describe('Test QR code signing flow', () => { }) it('Message signing with Specter DIY', () => { - cy.get('.side').contains('Wallet ghost').click() + cy.get('.side').contains('Ghost wallet').click() cy.get('main').contains('Addresses').click() // Click on the first address cy.contains('td', '#0').siblings().contains('bcrt').click() @@ -33,7 +33,7 @@ describe('Test QR code signing flow', () => { cy.get('#diy_ghost_qr_sign_msg_btn').should('not.exist') cy.get('#diy_ghost_usb_sign_msg_btn').should('exist') cy.get('#page_overlay_popup_cancel_button').click() - cy.contains("Wallet ghost").click() + cy.contains("Ghost wallet").click() cy.get('main').contains('Addresses').click() cy.contains('td', '#0').siblings().contains('bcrt').click() cy.get('#msg-signing-btn').should('exist') diff --git a/cypress/integration/spec_rescan.js b/cypress/integration/spec_rescan.js new file mode 100644 index 000000000..0976949e1 --- /dev/null +++ b/cypress/integration/spec_rescan.js @@ -0,0 +1,29 @@ +describe('Test the UI related to a blockchain rescan', () => { + before(() => { + Cypress.config('includeShadowDom', true) + }) + + // Keeps the session cookie alive, Cypress by default clears all cookies before each test + beforeEach(() => { + cy.visit('/') + cy.viewport(1200,660) + Cypress.Cookies.preserveOnce('session') + }) + + it('Go to the rescan section from a fresh wallet', () => { + // Create a completely fresh wallet which is not receiving funds from the continous mining + cy.addDevice('Trezor hold', 'Trezor', 'hold_accident') + cy.addWallet('Fresh wallet', 'Trezor hold', 'segwit', false) + cy.get('#btn_transactions').click() + cy.get('#go-to-rescan-btn').click() + cy.get('#blockchain-rescan').should('be.visible') + // TODO: Do we keep this wallet and this device or teardown? + }) + + it('Check that there is no button for a used wallet', () => { + // Only works if the ghost wallet was created with funded option + cy.selectWallet('Ghost wallet') + cy.get('#rescan-hint > p').should('not.be.visible') + cy.get('#rescan-hint > .btn').should('not.be.visible') + }) +}) \ No newline at end of file diff --git a/cypress/integration/spec_wallet_utxo.js b/cypress/integration/spec_wallet_utxo.js index 0e53c1467..dd60da4d0 100644 --- a/cypress/integration/spec_wallet_utxo.js +++ b/cypress/integration/spec_wallet_utxo.js @@ -98,7 +98,7 @@ describe('Test the actions in UTXO list', () => { it('Managing unsigned transactions', () => { // Make an unsigned tx - cy.get('#address_0').type("bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs") // address from "Wallet ghost" + cy.get('#address_0').type("bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs") // address from "Ghost wallet" cy.get('#send_max_0').click() cy.get('#create_psbt_btn').click() // Check the labeling of the unsigned UTXO diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c106b73e2..6b1f5835c 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -26,7 +26,7 @@ import 'cypress-wait-until'; -Cypress.Commands.add("addDevice", (name) => { +Cypress.Commands.add("addDevice", (name, device_type, mnemonic) => { cy.get('body').then(($body) => { if ($body.text().includes(name)) { cy.get('#toggle_devices_list').click() @@ -38,25 +38,23 @@ Cypress.Commands.add("addDevice", (name) => { cy.get('#toggle_devices_list').click() } cy.get('#btn_new_device').click() - // Creating a Device - cy.contains('Select Your Device Type') - cy.get('#trezor_device_card') - cy.get('#device-type-searchbar').type("specter") - cy.contains('Select Your Device Type') - cy.get('#trezor_device_card').should('not.have.class', 'disabled') - cy.get('#specter_device_card').click() - cy.get('h2 > input').type(name) - cy.go('back') - cy.get('#device-type-container > .note').click() + cy.contains('Manual configuration').click() cy.get('#device_name').type(name) - cy.get('#device_type').select("Specter-DIY") - cy.get('#txt').type("[8c24a510/84h/1h/0h]vpub5Y24kG7ZrCFRkRnHia2sdnt5N7MmsrNry1jMrP8XptMEcZZqkjQA6bc1f52RGiEoJmdy1Vk9Qck9tAL1ohKvuq3oFXe3ADVse6UiTHzuyKx") - cy.get('#txt').type("\n[8c24a510/49h/1h/0h]upub5DCn7wm4SgVmzmtdoi8DVVfxhBJkqL1L6mmKHNgVky1Fj5VyBxV6NzKD957sr5fWXkY5y8THtqSVWWpjLnomBYw4iXpxaPbkXg5Gn6s5tQf") - cy.get('#txt').type("\n[8c24a510/48h/1h/0h/1h]Upub5S2BXfT5rv2bc2i4Hr8NaBzcu243ztEMJ7LUDK4A9UKRtVmr9EFNdNdPz8rAXQnZDeAoHA8KcR7grVjREWKpBr69bev4rkvxytLZ6fN3sUv") - cy.get('#txt').type("\n[8c24a510/48h/1h/0h/2h]Vpub5krSqL811ba5VJdUoP42TFmfRkAaR6h4uxdDThCvDd24PR5gXWPHCvASLbEKevdQQjGx3i1WG7ueEARb8Hpo2u4HikY3wnvwvF1VSakkjew") - cy.get('#cold_device > [type="submit"]').click() - cy.get('#toggle_devices_list').click() - cy.get('#devices_list > .item > div').contains(name) + // Device types are the names to select from, such as Trezor, Specter-DIY or Electrum + cy.get('#device_type').select(device_type) + if (mnemonic === "ghost_machine" || mnemonic === null) { + cy.get('#txt').type("[8c24a510/84h/1h/0h]vpub5Y24kG7ZrCFRkRnHia2sdnt5N7MmsrNry1jMrP8XptMEcZZqkjQA6bc1f52RGiEoJmdy1Vk9Qck9tAL1ohKvuq3oFXe3ADVse6UiTHzuyKx") + cy.get('#txt').type("\n[8c24a510/49h/1h/0h]upub5DCn7wm4SgVmzmtdoi8DVVfxhBJkqL1L6mmKHNgVky1Fj5VyBxV6NzKD957sr5fWXkY5y8THtqSVWWpjLnomBYw4iXpxaPbkXg5Gn6s5tQf") + cy.get('#txt').type("\n[8c24a510/48h/1h/0h/1h]Upub5S2BXfT5rv2bc2i4Hr8NaBzcu243ztEMJ7LUDK4A9UKRtVmr9EFNdNdPz8rAXQnZDeAoHA8KcR7grVjREWKpBr69bev4rkvxytLZ6fN3sUv") + cy.get('#txt').type("\n[8c24a510/48h/1h/0h/2h]Vpub5krSqL811ba5VJdUoP42TFmfRkAaR6h4uxdDThCvDd24PR5gXWPHCvASLbEKevdQQjGx3i1WG7ueEARb8Hpo2u4HikY3wnvwvF1VSakkjew") + } + if (mnemonic === "hold_accident") { + cy.get('#txt').type("[ccf2e5c3/84h/1h/0h]vpub5YkPJgRQsev79YZM1NRDKJWDjLFcD2xSFAt6LehC5iiMMqQgMHyCFQzwsu16Rx9rBpXZVXPjWAxybuCpsayaw8qCDZtjwH9vifJ7WiQkHwu") + cy.get('#txt').type("\n[ccf2e5c3/49h/1h/0h]upub5DH3pJxqyFKA9Xu8mbKie67UvJ5VWsDDtEg2YR98Yy99UGFNnBa6VSk36zW1ZWTrbYa1Nk6zrxSvzL2hdzjbRUatmwaVUPPYzyEniauECJy") + cy.get('#txt').type("\n[ccf2e5c3/48h/1h/0h/1h]Upub5RyWnpxermQY5L7knm9gDPMQGcFKqat9pDKxhNGoVQHFDubgitAESzyS6QH65ebd7KCs6njXEL1kh1iCweiodWT1xtq69VNx2Cwog97WEDt") + cy.get('#txt').type("\n[ccf2e5c3/48h/1h/0h/2h]Vpub5kon6Vda1Sx1xfPziprUxjBU9PjxGisfosRz3yvWz9odArUVF4embnoB4rEN76CVc5r1UB5JYXxzHTSQS6M1mAob8Wsw6FXk57RubBaizov") + } + cy.contains("Continue").click() }) }) @@ -151,6 +149,40 @@ Cypress.Commands.add("addHotWallet", (wallet_name, device_name, node_type, walle }) }) +Cypress.Commands.add("addWallet", (wallet_name, device_name, wallet_type, funded, node_type) => { + if (wallet_type == null) { + wallet_type = "segwit" + } + if (device_name == null) { + device_name = "DIY ghost" + } + cy.get('body').then(($body) => { + if ($body.text().includes(wallet_name)) { + cy.contains(wallet_name).click() + cy.get('#btn_settings' ).click( {force: true}) + cy.get('#advanced_settings_tab_btn').click() + cy.get('#delete_wallet').click() + } + cy.get('#side-content').click() + cy.get('#btn_new_wallet').click() + cy.get('[href="./simple/"]').click() + var device_button = "#"+device_name.toLowerCase().replace(/ /g,"_") + cy.get(device_button).click() + cy.get('#wallet_name').type(wallet_name) + if (wallet_type == "nested_segwit") { + cy.get('#type_nested_segwit_btn').click() + } + if (wallet_type == "taproot") { + cy.get('#type_taproot_btn').click() + } + cy.get('#keysform > .centered').click() + cy.get('body').contains("New wallet was created successfully!") + cy.get('#btn_continue').click() + if (funded) { + cy.mine2wallet(node_type) + } + }) +}) Cypress.Commands.add("deleteWallet", (name) => { cy.get('body').then(($body) => { if ($body.text().includes(name)) { diff --git a/cypress_basics.json b/cypress_basics.json new file mode 100644 index 000000000..8701aafa0 --- /dev/null +++ b/cypress_basics.json @@ -0,0 +1,11 @@ +{ + "testFiles": [ + "spec_configures_nodes.js", + "spec_ghost_machine.js" + ], + "baseUrl": "http://localhost:25444", + "env": { + "broadcast_timeout":"8000" + }, + "includeShadowDom": false +} diff --git a/src/cryptoadvance/specter/cli/cli_noded.py b/src/cryptoadvance/specter/cli/cli_noded.py index db9600739..9ab8c182e 100644 --- a/src/cryptoadvance/specter/cli/cli_noded.py +++ b/src/cryptoadvance/specter/cli/cli_noded.py @@ -444,18 +444,18 @@ def miner_loop(node_impl, my_node, data_folder, mining_every_x_seconds, echo): def mine_2_specter_wallets(node_impl, my_node, data_folder, echo): - """Get each specter-wallet some coins""" + """Sending coins to all Specter wallets, except for "Fresh wallet" which is not supposed to have any tx""" from ..process_controller.node_controller import fetch_wallet_addresses_for_mining + # Using the dict key, not the wallet name + exception = "fresh_wallet" try: - - for address in fetch_wallet_addresses_for_mining(node_impl, data_folder): - echo("") + for address in fetch_wallet_addresses_for_mining( + node_impl, data_folder, exception + ): echo(f"Mining to address {address}") my_node.testcoin_faucet(address) - # my_node.mine(address=address) - # my_node.mine(block_count=100) except FileNotFoundError: - # might happen if there no ~/.specter folder yet + # might happen if there is no ~/.specter folder yet pass diff --git a/src/cryptoadvance/specter/process_controller/node_controller.py b/src/cryptoadvance/specter/process_controller/node_controller.py index 0261c25c9..6b86dc898 100644 --- a/src/cryptoadvance/specter/process_controller/node_controller.py +++ b/src/cryptoadvance/specter/process_controller/node_controller.py @@ -555,9 +555,10 @@ def find_node_executable(node_impl): return which(f"{node_impl}d") -def fetch_wallet_addresses_for_mining(node_impl, data_folder): - """parses all the wallet-jsons in the folder (default ~/.specter/wallets/regtest) - and returns an array with the addresses +def fetch_wallet_addresses_for_mining(node_impl, data_folder, exception=None): + """ + Parses all the wallet jsons in the folder (default ~/.specter/wallets/regtest) and returns an array with the addresses. + Pass a wallet name via the exception argument so that this wallet's addresses are not included. """ print(f"{data_folder}/wallets") print(os.listdir(f"{data_folder}")) @@ -569,6 +570,11 @@ def fetch_wallet_addresses_for_mining(node_impl, data_folder): f"{data_folder}/{folder}/{'regtest' if node_impl == 'bitcoin' else 'elreg'}" ) wallets = load_jsons(wallet_folder) + if exception and exception in wallets: + print( + f"Deleting wallet {exception} from wallets that receive faucet coins in testing." + ) + del wallets[exception] address_array = [value["address"] for key, value in wallets.items()] # remove duplicates address_array = list(dict.fromkeys(address_array)) diff --git a/src/cryptoadvance/specter/server_endpoints/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets.py index 01ca7abf6..3c445fac4 100644 --- a/src/cryptoadvance/specter/server_endpoints/wallets.py +++ b/src/cryptoadvance/specter/server_endpoints/wallets.py @@ -828,6 +828,7 @@ def settings(wallet_alias): else: app.specter.wallet_manager.rename_wallet(wallet, wallet_name) + scroll_to_rescan_blockchain = request.args.get("rescan_blockchain") return render_template( "wallet/settings/wallet_settings.jinja", purposes=purposes, @@ -836,4 +837,5 @@ def settings(wallet_alias): specter=app.specter, rand=rand, error=error, + scroll_to_rescan_blockchain=scroll_to_rescan_blockchain, ) diff --git a/src/cryptoadvance/specter/static/styles.css b/src/cryptoadvance/specter/static/styles.css index bb2826856..7277a3393 100644 --- a/src/cryptoadvance/specter/static/styles.css +++ b/src/cryptoadvance/specter/static/styles.css @@ -345,7 +345,7 @@ nav .separator{ background: var(--cmap-border); border: 1px solid transparent; border-radius: 4px; - padding: 8px 15px 8px 10px; + padding: 8px 10px 8px 10px; color: #fff; font-size: 0.85em; text-align: center; @@ -742,7 +742,7 @@ th{ font-weight: inherit; border-bottom: 3px solid var(--cmap-bg-lightest); } -tbody tr:hover{ +tbody tr:not(.nohover):hover{ background: rgba(255,255,255,0.03); color: #fff; } @@ -832,12 +832,6 @@ table a:hover, .address-link:hover{ color: #fff; text-decoration: underline; } -table .btn{ - margin: -10px 0; - padding: 5px 10px; - width: auto; - min-width: 0; -} table .btn.hovering{ opacity: 0.2; } diff --git a/src/cryptoadvance/specter/templates/includes/addresses-table.html b/src/cryptoadvance/specter/templates/includes/addresses-table.html index 0b67fb686..023da51dd 100644 --- a/src/cryptoadvance/specter/templates/includes/addresses-table.html +++ b/src/cryptoadvance/specter/templates/includes/addresses-table.html @@ -109,7 +109,7 @@
-
+
{% include "includes/page-limit-select.html" %}
{% else %} {{ _("Rescan blockchain from block:") }}
-
+
{% if specter.info.chain == "main" %} {% if wallet.is_taproot %} {% set startblock=(709632 if not specter.info['pruned'] or specter.info['pruneheight'] < 709632 else specter.info['pruneheight']) %} @@ -240,16 +248,16 @@ {% else %} {% set startblock=(0 if not specter.info['pruned'] else specter.info['pruneheight']) %} {% endif %} - - + +
{% if specter.info['pruned'] %} -
{{ _("Note: You are using a pruned node. Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}
+
{{ _("Note: You are using a pruned node. Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}
{% endif %} {% if wallet.is_taproot %} -
709632 {{ _("was the first Taproot block.") }}
+
709632 {{ _("was the first Taproot block.") }}
{% else %} -
481824 {{ _("was the first Segwit block.") }}
+
481824 {{ _("was the first Segwit block.") }}
{% endif %} {% endif %} @@ -261,10 +269,10 @@
{% else %} -
+
{{ _("Or rescan only unspent transactions:") }}
-