diff --git a/.gitignore b/.gitignore index 11fa4d5f5..0f537102b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ cypress/videos cypress/screenshots node_modules btcd-conn.json +elmd-conn.json tests/bitcoin tests/bitcoin.binary tests/bitcoin.compile diff --git a/cypress.json b/cypress.json index 6e9ec8b91..5223a4c42 100644 --- a/cypress.json +++ b/cypress.json @@ -3,9 +3,11 @@ "spec_setup_wizard.js", "spec_setup_tor.js", "spec_empty_specter_home.js", + "spec_configures_nodes.js", "spec_node_configured.js", "spec_wallet_send.js", - "spec_wallet_utxo.js" + "spec_wallet_utxo.js", + "spec_elm_wallet_send.js" ], "baseUrl": "http://localhost:25444" } diff --git a/cypress/integration/spec_configures_nodes.js b/cypress/integration/spec_configures_nodes.js new file mode 100644 index 000000000..4dec7349d --- /dev/null +++ b/cypress/integration/spec_configures_nodes.js @@ -0,0 +1,89 @@ + +describe('Configuring nodes', () => { + + it('Configures the bitcoin-node in Specter', () => { + cy.viewport(1200,660) + cy.visit('/') + cy.get('#node-switch-icon').click() + cy.get('[href="/nodes/node/default/"]').first().click() + cy.get('#datadir-container').then(($datadir) => { + cy.log($datadir) + if (!Cypress.dom.isVisible($datadir)) { + cy.get('.slider').click() + } + }) + cy.get('.slider').click() + cy.get('#username').clear() + cy.get('#username').type("bitcoin") + cy.get('#password').clear() + cy.get('#password').type("wrongPassword") // wrong Password + cy.get('#host').clear() + // This is hopefully correct for some longer time. If the connection fails, check the + // output of python3 -m cryptoadvance.specter bitcoind (in the CI-output !!) for a better ip-address. + // AUtomating that is probably simply not worth it. + cy.readFile('btcd-conn.json').then((conn) => { + cy.get('#host').type("http://"+conn["host"]) + }) + cy.get('#port').clear() + cy.get('#port').type("18443") + cy.get('[value="test"]').click() + cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green + cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(255, 0, 0)') // Credentials: red + cy.get('message-box').shadow().find('div.error > a').click() + cy.get('#password').clear() + cy.get('#password').type("secret") + cy.get('[value="test"]').click() + cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green + cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Credentials: green + cy.get(':nth-child(8) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Version green + cy.get(':nth-child(11) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Walletsenabled green + cy.get('message-box').shadow().find('div.main > a').click() + cy.get('[value="save"]').click() + + }) + + it('Configures the elements-node in Specter', () => { + cy.viewport(1200,660) + cy.visit('/') + cy.get('#node-switch-icon').click() + cy.get('#btn_new_node').click() + cy.get('[href="/nodes/new_node/"]').click() + cy.get('#name').clear() + cy.get('#name').type("Elements Node") + cy.get('.slider').click() + cy.readFile('elmd-conn.json').then((conn) => { + cy.get('#username').clear() + cy.get('#username').type("liquid") + cy.get('#password').clear() + cy.get('#password').type("wrongPassword") // wrong Password + cy.get('#host').clear() + cy.get('#host').type("http://"+conn["host"]) + cy.get('#port').clear() + cy.get('#port').type(conn["port"]) + cy.get('[value="test"]').click() + cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green + cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(255, 0, 0)') // Credentials: red + cy.get('message-box').shadow().find('div.error > a').click() + cy.get('#password').clear() + cy.get('#password').type("secret") + cy.get('[value="test"]').click() + cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green + cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Credentials: green + cy.get(':nth-child(8) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Version green + cy.get(':nth-child(11) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Walletsenabled green + cy.get('message-box').shadow().find('div.main > a').click() + cy.get('[value="save"]').click() + + }) + }) + + + it('Choose Bitcoin Core Node', () => { + // switch back to bitcoin-node + cy.viewport(1200,660) + cy.visit('/') + cy.get('#node-switch-icon').click() + cy.get('#default-select-node-form > .item > div').click() + }) + + }) \ No newline at end of file diff --git a/cypress/integration/spec_elm_wallet_send.js b/cypress/integration/spec_elm_wallet_send.js new file mode 100644 index 000000000..6e023ed91 --- /dev/null +++ b/cypress/integration/spec_elm_wallet_send.js @@ -0,0 +1,29 @@ +describe('Send transactions from elements wallets', () => { + it('Creates a single sig elements hot wallet on specter and send transaction', () => { + cy.viewport(1200,660) + cy.visit('/') + cy.get('#node-switch-icon').click() + cy.get('#elements_node-select-node-form > .item > div').click() + // empty so far + cy.addHotDevice("Hot Elements Device 1","elements") + //cy.addHotWallet("Test Elements Hot Wallet","elements") + cy.addHotWallet("Test Elements Hot Wallet 1","elements") + + cy.get('#btn_send').click() + cy.get('#address_0').type("el1qqdsywea5scrn7t9q83fd540pw447h0uae30pdp82rzgkl7yzvjz6gra9ls8qu6sslw4s0ck48we06zhqd6kwjy2quh69zwxwn") + cy.get('#label_0').type("Burn address") + cy.get('#send_max_0').click() + cy.get('#create_psbt_btn').click() + // To be done: + /* cy.get('body').contains("Paste signed transaction") + cy.get('#hot_elements_device_1_tx_sign_btn').click() + cy.get('#hot_elements_device_1_hot_sign_btn').click() + cy.get('#hot_enter_passphrase__submit').click() + cy.get('#broadcast_local_btn').click() + cy.get('#fullbalance_amount') + .should(($div) => { + const n = parseFloat($div.text()) + expect(n).to.be.equals(0) + }) */ + }) +}) \ No newline at end of file diff --git a/cypress/integration/spec_empty_specter_home.js b/cypress/integration/spec_empty_specter_home.js index f191d783d..007bce460 100644 --- a/cypress/integration/spec_empty_specter_home.js +++ b/cypress/integration/spec_empty_specter_home.js @@ -27,47 +27,12 @@ describe('Completely empty specter-home', () => { cy.addDevice("Some Device") }) - it('Configures the node in Specter', () => { + it('Dummytest to enforce remove of device', () => { cy.viewport(1200,660) cy.visit('/') - cy.get('#node-switch-icon').click() - cy.get('[href="/nodes/node/default/"]').first().click() - cy.get('#datadir-container').then(($datadir) => { - cy.log($datadir) - if (!Cypress.dom.isVisible($datadir)) { - cy.get('.slider').click() - } - }) - cy.get('.slider').click() - cy.get('#username').clear() - cy.get('#username').type("bitcoin") - cy.get('#password').clear() - cy.get('#password').type("wrongPassword") // wrong Password - cy.get('#host').clear() - // This is hopefully correct for some longer time. If the connection fails, check the - // output of python3 -m cryptoadvance.specter bitcoind (in the CI-output !!) for a better ip-address. - // AUtomating that is probably simply not worth it. - cy.readFile('btcd-conn.json').then((conn) => { - cy.get('#host').type("http://"+conn["host"]) - }) - cy.get('#port').clear() - cy.get('#port').type("18443") - cy.get('[value="test"]').click() - cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green - cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(255, 0, 0)') // Credentials: red - cy.get('message-box').shadow().find('div.error > a').click() - cy.get('#password').clear() - cy.get('#password').type("secret") - cy.get('[value="test"]').click() - cy.get(':nth-child(2) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // connectable: green - cy.get(':nth-child(5) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Credentials: green - cy.get(':nth-child(8) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Version green - cy.get(':nth-child(11) > button > div').should('have.css', 'color', 'rgb(0, 128, 0)') // Walletsenabled green - cy.get('message-box').shadow().find('div.main > a').click() - cy.get('[value="save"]').click() - }) - }) + + diff --git a/cypress/integration/spec_node_configured.js b/cypress/integration/spec_node_configured.js index 2d2b49803..da47fe697 100644 --- a/cypress/integration/spec_node_configured.js +++ b/cypress/integration/spec_node_configured.js @@ -20,7 +20,7 @@ describe('Node Configured', () => { // Download PDF // unfortunately this results in weird effects in cypress run //cy.get('#pdf-wallet-download > img').click() - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) cy.get('#btn_continue').click() cy.get('#btn_transactions').click() diff --git a/cypress/integration/spec_setup_tor.js b/cypress/integration/spec_setup_tor.js index 15f2bfe35..2ce625899 100644 --- a/cypress/integration/spec_setup_tor.js +++ b/cypress/integration/spec_setup_tor.js @@ -1,22 +1,25 @@ describe('Setup Tor and test connection', () => { - it('Setup Tor', () => { - cy.viewport(1200,660) - cy.visit('/settings/tor') + // Tor testing is flaky on cirrus + if (Cypress.env("CI")) { + it('Setup Tor', () => { + cy.viewport(1200,660) + cy.visit('/settings/tor') - cy.get('#setup-tor-button').click() + cy.get('#setup-tor-button').click() - cy.contains('Setup Tor daemon') - cy.get('#setup-tor-button').click() + cy.contains('Setup Tor daemon') + cy.get('#setup-tor-button').click() - cy.wait(60000) - cy.get('#tor-status-text').contains('Status: Running') - cy.get('[value="test_tor"]').click({ timeout: 60000 }) - cy.contains('Tor requests test completed successfully!') - cy.get('[value="stoptor"]').click() - cy.get('#tor-status-text').contains('Status: Down') - cy.get('[value="starttor"]').click() - cy.get('#tor-status-text').contains('Status: Running') - cy.get('[value="uninstalltor"]').click() - cy.get('#setup-tor-button').click() - }) + cy.wait(60000) + cy.get('#tor-status-text').contains('Status: Running') + cy.get('[value="test_tor"]').click({ timeout: 60000 }) + cy.contains('Tor requests test completed successfully!') + cy.get('[value="stoptor"]').click() + cy.get('#tor-status-text').contains('Status: Down') + cy.get('[value="starttor"]').click() + cy.get('#tor-status-text').contains('Status: Running') + cy.get('[value="uninstalltor"]').click() + cy.get('#setup-tor-button').click() + }) + } }) \ No newline at end of file diff --git a/cypress/integration/spec_setup_wizard.js b/cypress/integration/spec_setup_wizard.js index 18af72809..bd4af2aca 100644 --- a/cypress/integration/spec_setup_wizard.js +++ b/cypress/integration/spec_setup_wizard.js @@ -1,77 +1,80 @@ describe('Setup wizard', () => { - it('Setup Bitcoin Core and Tor', () => { - cy.viewport(1200,660) - cy.visit('/about') + // Tor testing is flaky on cirrus + if (Cypress.env("CI")) { + it('Setup Bitcoin Core and Tor', () => { + cy.viewport(1200,660) + cy.visit('/about') - cy.get('[href="/setup/start/"]').click() - cy.contains('Specter Quickstart!') - cy.get('#start-setup-btn').click() - cy.contains('Setup Tor daemon') - cy.get('#setup-tor-button').click() - cy.contains('Would you like to setup a new Bitcoin node or connect to an existing one?', { timeout: 60000 }) - cy.get('#setup-node-btn').click() - cy.contains('Setup Bitcoin Core') - cy.get('#setup-bitcoind-button').click() - cy.contains('Configure your node', { timeout: 60000 }) - cy.get('#quicksync-switch').click() - cy.get('#setup-bitcoind-dir-button').click() - cy.contains('Setup Completed Successfully!', { timeout: 60000 }) - cy.get('#finish-setup-btn').click() - cy.contains('Connect Specter with Bitcoin Core node.') + cy.get('[href="/setup/start/"]').click() + cy.contains('Specter Quickstart!') + cy.get('#start-setup-btn').click() + cy.contains('Setup Tor daemon') + cy.get('#setup-tor-button').click() + cy.contains('Would you like to setup a new Bitcoin node or connect to an existing one?', { timeout: 60000 }) + cy.get('#setup-node-btn').click() + cy.contains('Setup Bitcoin Core') + cy.get('#setup-bitcoind-button').click() + cy.contains('Configure your node', { timeout: 60000 }) + cy.get('#quicksync-switch').click() + cy.get('#setup-bitcoind-dir-button').click() + cy.contains('Setup Completed Successfully!', { timeout: 60000 }) + cy.get('#finish-setup-btn').click() + cy.contains('Connect Specter with Bitcoin Core node.') - cy.get('#node-switch-icon').click() - cy.get('#btn_new_node').click() - cy.get('#setup-node-btn').click() - cy.get('#toggle_advanced').click() - cy.get('#select-network-btn').click() - cy.get('[href="/setup/bitcoind_datadir/signet"]').click() - cy.get('#setup-bitcoind-dir-button').click() - cy.contains('Specter Signet', { timeout: 60000 }) - cy.get('#node-switch-icon').click() - cy.get('#specter_signet-select-node-form').click() - cy.contains('Built in Bitcoin Node Status: Running') - cy.get('#active-node').click() - cy.get('#node-info-specter-chain').contains('signet') - cy.get('#page_overlay_popup_cancel_button').click() - cy.get('#node-switch-icon').click() - cy.get('#specter_bitcoin-select-node-form').click() + cy.get('#node-switch-icon').click() + cy.get('#btn_new_node').click() + cy.get('#setup-node-btn').click() + cy.get('#toggle_advanced').click() + cy.get('#select-network-btn').click() + cy.get('[href="/setup/bitcoind_datadir/signet"]').click() + cy.get('#setup-bitcoind-dir-button').click() + cy.contains('Specter Signet', { timeout: 60000 }) + cy.get('#node-switch-icon').click() + cy.get('#specter_signet-select-node-form').click() + cy.contains('Built in Bitcoin Node Status: Running') + cy.get('#active-node').click() + cy.get('#node-info-specter-chain').contains('signet') + cy.get('#page_overlay_popup_cancel_button').click() + cy.get('#node-switch-icon').click() + cy.get('#specter_bitcoin-select-node-form').click() - - cy.get('#active-node').click() - cy.get('#node-info-specter-chain').contains('main') - cy.get('#active-node-settings-btn').click() - cy.contains('Built in Bitcoin Node Status: Running') - cy.get('[value="stopbitcoind"]').click() - cy.wait(10000) - cy.reload() - cy.contains('Built in Bitcoin Node Status: Down') + + cy.get('#active-node').click() + cy.get('#node-info-specter-chain').contains('main') + cy.get('#active-node-settings-btn').click() + cy.contains('Built in Bitcoin Node Status: Running') + cy.get('[value="stopbitcoind"]').click() + cy.wait(10000) + cy.reload() + cy.contains('Built in Bitcoin Node Status: Down') - cy.get('[value="startbitcoind"]').click({force: true, timeout: 60000}) - cy.contains('Built in Bitcoin Node Status: Running') - cy.get('[name="remove_datadir"]').click() - cy.get('[value="forget"]').click() + cy.get('[value="startbitcoind"]').click({force: true, timeout: 60000}) + cy.contains('Built in Bitcoin Node Status: Running') + cy.get('[name="remove_datadir"]').click() + cy.get('[value="forget"]').click() - cy.get('#node-switch-icon').click() - cy.get('#specter_signet-select-node-form').click() - cy.get('[name="remove_datadir"]').click() - cy.get('[value="uninstall_bitcoind"]').click() + cy.get('#node-switch-icon').click() + cy.get('#specter_signet-select-node-form').click() + cy.get('[name="remove_datadir"]').click() + cy.get('[value="uninstall_bitcoind"]').click() - cy.get('#active-node').click() - cy.get('#title').contains('Bitcoin Core') + cy.get('#active-node').click() + cy.get('#title').contains('Bitcoin Core') - cy.visit('/settings/tor') - cy.get('#tor-status-text').contains('Status: Running') - cy.get('[value="stoptor"]').click() - cy.get('#tor-status-text').contains('Status: Down') - cy.get('[value="starttor"]').click() - cy.get('#tor-status-text').contains('Status: Running') - cy.get('[value="test_tor"]').click({ timeout: 60000 }) - cy.contains('Tor requests test completed successfully!') - cy.get('[value="uninstalltor"]').click() - cy.get('#setup-tor-button').click() + cy.visit('/settings/tor') + cy.get('#tor-status-text').contains('Status: Running') + cy.get('[value="stoptor"]').click() + cy.get('#tor-status-text').contains('Status: Down') + cy.get('[value="starttor"]').click() + cy.get('#tor-status-text').contains('Status: Running') + cy.get('[value="test_tor"]').click({ timeout: 60000 }) + cy.contains('Tor requests test completed successfully!') + cy.get('[value="uninstalltor"]').click() + cy.get('#setup-tor-button').click() - cy.visit('/about') + cy.visit('/about') - cy.get('[href="/setup/start/"]').click() - }) + cy.get('[href="/setup/start/"]').click() + }) + } }) \ No newline at end of file diff --git a/cypress/integration/spec_wallet_send.js b/cypress/integration/spec_wallet_send.js index e66635fbe..950fac0de 100644 --- a/cypress/integration/spec_wallet_send.js +++ b/cypress/integration/spec_wallet_send.js @@ -1,9 +1,9 @@ -describe('Send transactions from wallets', () => { - it('Creates a single sig wallet on specter and send transaction', () => { +describe('Send transactions from bitcoin hotwallets', () => { + it('Creates a single sig bitcoin hotwallet on specter and send transaction', () => { cy.viewport(1200,660) cy.visit('/') // empty so far - cy.addHotDevice("Hot Device 1") + cy.addHotDevice("Hot Device 1","bitcoin") cy.get('body').then(($body) => { if ($body.text().includes('Test Hot Wallet 1')) { cy.get('#wallets_list > .item > svg').click() @@ -23,7 +23,7 @@ describe('Send transactions from wallets', () => { //cy.get('#pdf-wallet-download > img').click() cy.get('#btn_continue').click() cy.get('#btn_transactions').click() - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) cy.get('#wallets-loading-done-refresh', { timeout: 10000 }).click() cy.get('#fullbalance_amount') @@ -70,7 +70,7 @@ describe('Send transactions from wallets', () => { cy.get('body').contains("New wallet was created successfully!") cy.get('#page_overlay_popup_cancel_button').click() // Send transaction - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) cy.get('#btn_transactions').click() cy.get('#wallets-loading-done-refresh', { timeout: 10000 }).click() diff --git a/cypress/integration/spec_wallet_utxo.js b/cypress/integration/spec_wallet_utxo.js index 10b814a23..32e85fee2 100644 --- a/cypress/integration/spec_wallet_utxo.js +++ b/cypress/integration/spec_wallet_utxo.js @@ -1,11 +1,11 @@ describe('Send transactions from wallets', () => { it('Freeze and unfreeze UTXO', () => { cy.viewport(1200,660) - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) - cy.task("node:mine") + cy.task("btc:mine") cy.wait(10000) cy.visit('/wallets/wallet/test_hot_wallet_1/history') diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 3c3b451ec..cf36ed907 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -19,12 +19,14 @@ const fs = require('fs'); module.exports = (on, config) => { // `config` is the resolved Cypress config - const conn_file = fs.readFileSync('btcd-conn.json'); - const conn = JSON.parse(conn_file); + const btc_conn_file = fs.readFileSync('btcd-conn.json'); + const btc_conn = JSON.parse(btc_conn_file); + const elm_conn_file = fs.readFileSync('elmd-conn.json'); + const elm_conn = JSON.parse(elm_conn_file); on('task', { 'clear:specter-home': () => { - console.log('Removing and recreating Specter-data-folder %s', conn["specter_data_folder"]) - const specter_home=conn["specter_data_folder"]; + console.log('Removing and recreating Specter-data-folder %s', btc_conn["specter_data_folder"]) + const specter_home=btc_conn["specter_data_folder"]; var rimraf = require("rimraf"); rimraf.sync(specter_home); fs.mkdirSync(specter_home); @@ -36,11 +38,32 @@ module.exports = (on, config) => { }) on('task', { - 'node:mine': () => { + 'delete:elements-hotwallet': (name) => { + console.log('connection details: %s', elm_conn) + const elements_data_dir=elm_conn["elements_data_dir"]; + var rimraf = require("rimraf"); + console.log('Removing all wallets in %s', elements_data_dir+"/elreg/wallets/specter123456_hotstorage") + rimraf.sync(elements_data_dir+"/elreg/wallets/specter123456_hotstorage"); + return null + } + }) + + on('task', { + 'btc:mine': () => { // sending the bitcoind-process a signal SIGUSR1 (10) will cause mining towards all specter-wallets // See the signal-handler in bitcoind - console.log('Sending SIGUSR1 to '+conn["pid"]) - process.kill(parseInt(conn["pid"], 10), 'SIGUSR1'); + console.log('Sending SIGUSR1 to '+btc_conn["pid"]+ ' to mine some btc') + process.kill(parseInt(btc_conn["pid"], 10), 'SIGUSR1'); + return null + } + }) + + on('task', { + 'elm:mine': () => { + // sending the bitcoind-process a signal SIGUSR1 (10) will cause mining towards all specter-wallets + // See the signal-handler in bitcoind + console.log('Sending SIGUSR1 to '+elm_conn["pid"] + ' to mine some lbtc') + process.kill(parseInt(elm_conn["pid"], 10), 'SIGUSR1'); return null } }) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c6a73beec..6fbbefd42 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -53,20 +53,55 @@ Cypress.Commands.add("addDevice", (name) => { }) }) -Cypress.Commands.add("addHotDevice", (name) => { +Cypress.Commands.add("addHotDevice", (name, node_type) => { + // node_type is either elements or bitcoin cy.get('body').then(($body) => { + cy.task("delete:elements-hotwallet") if ($body.text().includes(name)) { cy.get('#devices_list > .item > div').click() cy.get('#forget_device').click() } cy.get('#side-content').click() cy.get('#btn_new_device').click() - // Creating a Device cy.contains('Select Your Device Type') - cy.get('#bitcoincore_device_card').click() + cy.get(`#${node_type}core_device_card`).click() cy.get('#submit-mnemonic').click() cy.get('#device_name').type(name) cy.get('#submit-keys').click() cy.get('#devices_list > .item > div').contains(name) }) +}) + +Cypress.Commands.add("addHotWallet", (name, node_type, single_multi) => { + cy.get('body').then(($body) => { + if ($body.text().includes(name)) { + cy.contains(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() + cy.get('#hot_elements_device_1').click() + cy.get('#wallet_name').type(name) + 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.get('#btn_transactions').click() + cy.task("elm:mine") + cy.wait(4000) + cy.reload() + cy.get('#fullbalance_amount') + .should(($div) => { + const n = parseFloat($div.text()) + expect(n).to.be.gt(0).and.be.lte(50) + }) + + }) }) \ No newline at end of file diff --git a/src/cryptoadvance/specter/cli/cli_noded.py b/src/cryptoadvance/specter/cli/cli_noded.py index c536f1b5d..c05c2c649 100644 --- a/src/cryptoadvance/specter/cli/cli_noded.py +++ b/src/cryptoadvance/specter/cli/cli_noded.py @@ -287,7 +287,7 @@ def noded( echo(f"user, password: { my_node.rpcconn.rpcuser }, secret") echo(f" host, port: localhost, {my_node.rpcconn.rpcport}") echo( - f" {node_impl}-cli: {node_impl}-cli -regtest -rpcport={my_node.rpcconn.rpcport} -rpcuser={ node_impl } -rpcpassword=secret getblockchaininfo " + f" {node_impl}-cli: {node_impl}-cli -regtest -rpcport={my_node.rpcconn.rpcport} -rpcuser={ my_node.rpcconn.rpcuser } -rpcpassword=secret getblockchaininfo " ) if create_conn_json: @@ -296,6 +296,7 @@ def noded( conn["specter_data_folder"] = config_obj[ "SPECTER_DATA_FOLDER" ] # e.g. cypress might want to know where we're mining to + conn[f"{node_impl}_data_dir"] = data_dir conn_file = f"{'btcd' if node_impl == 'bitcoin' else 'elmd'}-conn.json" with open(conn_file, "w") as file: file.write(json.dumps(conn)) diff --git a/src/cryptoadvance/specter/config.py b/src/cryptoadvance/specter/config.py index b1e186f20..04fb262c5 100644 --- a/src/cryptoadvance/specter/config.py +++ b/src/cryptoadvance/specter/config.py @@ -130,6 +130,10 @@ class CypressTestConfig(TestConfig): "BTCD_REGTEST_DATA_DIR", "/tmp/specter_cypress_btc_regtest_plain_datadir" ) + BTCD_REGTEST_DATA_DIR = os.getenv( + "BTCD_REGTEST_DATA_DIR", "/tmp/specter_cypress_elm_regtest_plain_datadir" + ) + class ProductionConfig(BaseConfig): SECRET_KEY = secrets.token_urlsafe(16) diff --git a/src/cryptoadvance/specter/internal_node.py b/src/cryptoadvance/specter/internal_node.py index 362c8303b..702f6dd61 100644 --- a/src/cryptoadvance/specter/internal_node.py +++ b/src/cryptoadvance/specter/internal_node.py @@ -65,17 +65,16 @@ class InternalNode(Node): "/testnet3" ): self.datadir = os.path.join(self.datadir, "testnet3") - write_node(self, self.fullpath) elif self.bitcoind_network == "regtest" and not self.datadir.endswith( "/regtest" ): self.datadir = os.path.join(self.datadir, "regtest") - write_node(self, self.fullpath) elif self.bitcoind_network == "signet" and not self.datadir.endswith( "/signet" ): self.datadir = os.path.join(self.datadir, "signet") - write_node(self, self.fullpath) + logger.info(f"persisting {self} in __init__") + write_node(self, self.fullpath) @classmethod def from_json(cls, node_dict, manager, default_alias="", default_fullpath=""): diff --git a/src/cryptoadvance/specter/liquid/rpc.py b/src/cryptoadvance/specter/liquid/rpc.py index a735f8a1d..bf0d4efd5 100644 --- a/src/cryptoadvance/specter/liquid/rpc.py +++ b/src/cryptoadvance/specter/liquid/rpc.py @@ -409,6 +409,9 @@ class LiquidRPC(BitcoinRPC): logger.warn(f"Failed at unblinding transaction: {e}") return obj + def __repr__(self) -> str: + return f"" + @classmethod def from_bitcoin_rpc(cls, rpc): """Convert BitcoinRPC to LiquidRPC""" diff --git a/src/cryptoadvance/specter/managers/node_manager.py b/src/cryptoadvance/specter/managers/node_manager.py index befe18fbd..c72bb177b 100644 --- a/src/cryptoadvance/specter/managers/node_manager.py +++ b/src/cryptoadvance/specter/managers/node_manager.py @@ -111,6 +111,7 @@ class NodeManager: setup_bitcoind_thread(specter, version) for node in (node for node in self.nodes.values() if not node.external_node): node.version = version + logger.info(f"persisting {node} in update_bitcoind_version") write_node(node, node.fullpath) for node_alias in stopped_nodes: self.get_by_alias(node_alias).start(timeout=60) @@ -153,6 +154,7 @@ class NodeManager: fullpath, self, ) + logger.info(f"persisting {node} in add_node") write_node(node, fullpath) self.update() # reload files logger.info("Added new node {}".format(node.alias)) @@ -192,6 +194,7 @@ class NodeManager: network, self.internal_bitcoind_version, ) + logger.info(f"persisting {node} in add_internal_node") write_node(node, fullpath) self.update() # reload files logger.info("Added new internal node {}".format(node.alias)) diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py index 2c7e82bc2..7849f712b 100644 --- a/src/cryptoadvance/specter/managers/wallet_manager.py +++ b/src/cryptoadvance/specter/managers/wallet_manager.py @@ -100,8 +100,13 @@ class WalletManager: if self.chain is not None and self.data_folder is not None: self.working_folder = os.path.join(self.data_folder, self.chain) pathlib.Path(self.working_folder).mkdir(parents=True, exist_ok=True) - if rpc is not None: + if rpc is not None and rpc.test_connection(): self.rpc = rpc + else: + if rpc: + logger.error( + f"Prevented Trying to update wallet_Manager with broken {rpc}" + ) self.wallets_update_list = {} if self.working_folder is not None and self.rpc is not None: wallets_files = load_jsons(self.working_folder, key="name") @@ -286,6 +291,21 @@ class WalletManager: def wallets_names(self): return sorted(self.wallets.keys()) + @property + def rpc(self): + if not hasattr(self, "_rpc"): + return None + else: + return self._rpc + + @rpc.setter + def rpc(self, value): + if hasattr(self, "_rpc") and self._rpc != value: + logger.debug(f"Updating WalletManager rpc {self._rpc} with {value}") + if hasattr(self, "_rpc") and value == None: + logger.debug(f"Updating WalletManager rpc {self._rpc} with None") + self._rpc = value + def create_wallet(self, name, sigs_required, key_type, keys, devices): try: walletsindir = [ diff --git a/src/cryptoadvance/specter/node.py b/src/cryptoadvance/specter/node.py index 7d71d7f0a..83b520bef 100644 --- a/src/cryptoadvance/specter/node.py +++ b/src/cryptoadvance/specter/node.py @@ -18,9 +18,11 @@ logger = logging.getLogger(__name__) class Node: - """A NodeManager represents the connection to a Bitcoin and/o Liquid Node (Full-) node. + """A Node represents the connection to a Bitcoin and/or Liquid (Full-) node. It can be created via Constructor or from_json, and mainly it can give you A RPC-object to use the API. + On top of the RPC-connection it manages the stability of the rpc. It will only + persist healthy connections. One or many Nodes are managed via the NodeManager """ @@ -141,6 +143,8 @@ class Node: rpc = BitcoinRPC( **rpc_conf_arr[0], proxy_url=self.proxy_url, only_tor=self.only_tor ) + # autodetect won't result in any logging, even if None + return rpc else: # if autodetect is disabled and port is not defined # we use default port 8332 @@ -157,6 +161,7 @@ class Node: ) if rpc == None: + logger.error(f"connection results to None in get_rpc") return None # check if it's liquid try: @@ -166,7 +171,13 @@ class Node: rpc = LiquidRPC.from_bitcoin_rpc(rpc) except Exception as e: return rpc - return rpc + if rpc.test_connection(): + return rpc + else: + logger.debug( + f"connection {rpc} fails test_connection() returning None in get_rpc" + ) + return None def update_rpc( self, @@ -178,44 +189,57 @@ class Node: host=None, protocol=None, ): + """Changes the attributes of that node but only persists it, if the rpc.test_connection succeeds""" update_rpc = self.rpc is None or not self.rpc.test_connection() if autodetect is not None and self.autodetect != autodetect: + logger.debug(f"{self} updating autodetect to {autodetect}") self.autodetect = autodetect update_rpc = True if datadir is not None and self.datadir != datadir: + logger.debug(f"{self} updating datadir to {datadir}") self.datadir = datadir update_rpc = True if user is not None and self.user != user: + logger.debug(f"{self} updating user to {user}") self.user = user update_rpc = True if password is not None and self.password != password: + logger.debug(f"{self} updating password to XXXXXXXX") self.password = password update_rpc = True if port is not None and self.port != port: + logger.debug(f"{self} updating port to {port}") self.port = port update_rpc = True if host is not None and self.host != host: + logger.debug(f"{self} updating host to {host}") self.host = host update_rpc = True if protocol is not None and self.protocol != protocol: + logger.debug(f"{self} updating protocol to {protocol}") self.protocol = protocol update_rpc = True if update_rpc: self.rpc = self.get_rpc() - write_node(self, self.fullpath) + if self.rpc and self.rpc.test_connection(): + logger.info(f"persisting {self} in update_rpc") + write_node(self, self.fullpath) + else: + logger.error(f"not persisting broken {self.rpc} in update_rpc") self.check_info() return False if not self.rpc else self.rpc.test_connection() def rename(self, new_name): logger.info("Renaming {}".format(self.alias)) self.name = new_name + logger.info(f"persisting {self} in rename") write_node(self, self.fullpath) self.manager.update() def check_info(self): self._is_configured = self.rpc is not None self._is_running = False - if self._is_configured: + if self.rpc is not None and self.rpc.test_connection(): try: res = [ r["result"] @@ -252,8 +276,28 @@ class Node: self._info = {"chain": None} self._network_info = {"subversion": "", "version": 999999} self._network_parameters = get_network("main") - logger.error("Exception %s while check_info()" % e) + logger.error(f"connection {self.rpc} could not suceed check_info") + logger.exception("Exception %s while check_info()" % e) else: + if self.rpc is None: + logger.error(f"connection of {self} is None in check_info") + elif not self.rpc.test_connection(): + logger.error( + f"connection {self.rpc} failed test_connection in check_info:" + ) + try: + self.rpc.multi( + [ + ("getblockchaininfo", None), + ("getnetworkinfo", None), + ("getmempoolinfo", None), + ("uptime", None), + ("getblockhash", 0), + ("scantxoutset", "status", []), + ] + ) + except Exception as e: + logger.error(e) self._info = {"chain": None} self._network_info = {"subversion": "", "version": 999999} @@ -332,6 +376,9 @@ class Node: def check_blockheight(self): return self.info["blocks"] != self.rpc.getblockcount() + def is_liquid(self): + return is_liquid(self.chain) + @property def is_running(self): return self._is_running @@ -374,5 +421,19 @@ class Node: return is_testnet(self.chain) @property - def is_liquid(self): - return is_liquid(self.chain) + def rpc(self): + if not hasattr(self, "_rpc"): + return None + else: + return self._rpc + + @rpc.setter + def rpc(self, value): + if hasattr(self, "_rpc") and self._rpc != value: + logger.debug(f"Updating {self}.rpc {self._rpc} with {value} (setter)") + if hasattr(self, "_rpc") and value == None: + logger.debug(f"Updating {self}.rpc {self._rpc} with None (setter)") + self._rpc = value + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} name={self.name} fullpath={self.fullpath}>" diff --git a/src/cryptoadvance/specter/process_controller/node_controller.py b/src/cryptoadvance/specter/process_controller/node_controller.py index 7dc4fa651..0677ec869 100644 --- a/src/cryptoadvance/specter/process_controller/node_controller.py +++ b/src/cryptoadvance/specter/process_controller/node_controller.py @@ -17,7 +17,7 @@ from ..util.shell import which, get_last_lines_from_file from ..rpc import RpcError from ..rpc import BitcoinRPC from ..helpers import load_jsons -from ..specter_error import ExtProcTimeoutException +from ..specter_error import ExtProcTimeoutException, SpecterError from urllib3.exceptions import NewConnectionError, MaxRetryError from requests.exceptions import ConnectionError @@ -119,7 +119,7 @@ class NodeController: Specify a longer timeout for slower devices (e.g. Raspberry Pi) """ if self.check_existing() != None: - logger.warn(f"Reusing existing {self.node_impl}d") + logger.warning(f"Reusing existing {self.node_impl}d") return self.rpcconn logger.debug(f"Starting {self.node_impl}d") @@ -228,7 +228,9 @@ class NodeController: logger.debug("balance:" + str(balance)) default_address = default_rpc.getnewaddress("") if self.node_impl == "elements": - default_address = rpc.getaddressinfo(default_address)["unconfidential"] + default_address = default_rpc.getaddressinfo(default_address)[ + "unconfidential" + ] if balance < amount: rpc.generatetoaddress(102, default_address) default_rpc.sendtoaddress(address, amount) @@ -373,7 +375,7 @@ class NodePlainController(NodeController): ) time.sleep(0.2) # sleep 200ms (catch stdout of stupid errors) if not self.node_proc.poll() is None: - raise Exception(f"Could not start node due to:" + self.get_debug_log()) + raise SpecterError(f"Could not start node due to:" + self.get_debug_log()) logger.debug( f"Running {self.node_impl}d-process with pid {self.node_proc.pid} in datadir {datadir}" ) diff --git a/src/cryptoadvance/specter/rpc.py b/src/cryptoadvance/specter/rpc.py index 430868e4b..4f68f391d 100644 --- a/src/cryptoadvance/specter/rpc.py +++ b/src/cryptoadvance/specter/rpc.py @@ -208,6 +208,7 @@ class RpcError(Exception): self.error_code = error["error"]["code"] self.error_msg = error["error"]["message"] except Exception as e: + self.error_code = -99 self.error = "UNKNOWN API-ERROR:%s" % response.text @@ -352,6 +353,9 @@ class BitcoinRPC: return fn + def __repr__(self) -> str: + return f"" + if __name__ == "__main__": diff --git a/src/cryptoadvance/specter/specter.py b/src/cryptoadvance/specter/specter.py index ad2531c2c..3b21c8291 100644 --- a/src/cryptoadvance/specter/specter.py +++ b/src/cryptoadvance/specter/specter.py @@ -175,6 +175,8 @@ class Specter: try: return self.node_manager.active_node except SpecterError as e: + logger.error("SpecterError while accessing active_node") + logger.exception(e) self.update_active_node(list(self.node_manager.nodes.values())[0].alias) return self.node_manager.active_node @@ -629,6 +631,7 @@ class Specter: "mainnet", "0.20.1", ) + logger.info(f"persisting {internal_node} in migrate_old_node_format") write_node( internal_node, os.path.join( @@ -654,6 +657,7 @@ class Specter: os.path.join(os.path.join(self.data_folder, "nodes"), "default.json"), self, ) + logger.info(f"persisting {node} in migrate_old_node_format") write_node( node, os.path.join(os.path.join(self.data_folder, "nodes"), "default.json"), diff --git a/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja b/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja index cc0ed8750..707caf350 100644 --- a/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja +++ b/src/cryptoadvance/specter/templates/wallet/settings/wallet_settings.jinja @@ -252,7 +252,7 @@

Delete wallet

- +
diff --git a/tests/conftest.py b/tests/conftest.py index 168f2e74a..ca970ece8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,9 @@ from cryptoadvance.specter.process_controller.bitcoind_controller import ( from cryptoadvance.specter.process_controller.bitcoind_docker_controller import ( BitcoindDockerController, ) +from cryptoadvance.specter.process_controller.elementsd_controller import ( + ElementsPlainController, +) from cryptoadvance.specter.server import create_app, init_app from cryptoadvance.specter.specter import Specter @@ -88,9 +91,41 @@ def instantiate_bitcoind_controller(docker, request, rpcport=18543, extra_args=[ return bitcoind_controller +def instantiate_elementsd_controller(request, rpcport=18643, extra_args=[]): + if os.path.isfile("tests/elements/src/elementsd"): + elementsd_controller = ElementsPlainController( + elementsd_path="tests/elements/src/elementsd", rpcport=rpcport + ) # always prefer the self-compiled bitcoind if existing + elif os.path.isfile("tests/elements/bin/elementsd"): + elementsd_controller = ElementsPlainController( + elementsd_path="tests/elements/bin/elementsd", rpcport=rpcport + ) # next take the self-installed binary if existing + else: + elementsd_controller = ElementsPlainController( + rpcport=rpcport + ) # Alternatively take the one on the path for now + elementsd_controller.start_elementsd( + cleanup_at_exit=True, cleanup_hard=True, extra_args=extra_args + ) + running_version = elementsd_controller.version() + requested_version = request.config.getoption("--elementsd-version") + assert running_version == requested_version, ( + "Please make sure that the elementsd-version (%s) matches with the version in pytest.ini (%s)" + % (running_version, requested_version) + ) + return elementsd_controller + + @pytest.fixture(scope="module") def bitcoin_regtest(docker, request): - return instantiate_bitcoind_controller(docker, request, extra_args=None) + bitcoind_regtest = instantiate_bitcoind_controller(docker, request, extra_args=None) + yield bitcoind_regtest + bitcoin_regtest: BitcoindPlainController.stop_bitcoind() + + +@pytest.fixture(scope="module") +def elements_elreg(request): + return instantiate_elementsd_controller(request, extra_args=None) @pytest.fixture @@ -267,8 +302,6 @@ def device_manager(devices_filled_data_folder): @pytest.fixture def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder): # Make sure that this folder never ever gets a reasonable non-testing use-case - data_folder = "./test_specter_data_3456778" - shutil.rmtree(data_folder, ignore_errors=True) config = { "rpc": { "autodetect": False, @@ -287,7 +320,6 @@ def specter_regtest_configured(bitcoin_regtest, devices_filled_data_folder): specter.check() assert not specter.wallet_manager.working_folder is None yield specter - shutil.rmtree(data_folder, ignore_errors=True) @pytest.fixture diff --git a/tests/test_cli_bitcoind.py b/tests/test_cli_bitcoind.py index 7812d953e..2cf7ea9c4 100644 --- a/tests/test_cli_bitcoind.py +++ b/tests/test_cli_bitcoind.py @@ -38,7 +38,7 @@ def test_elements(caplog): print(result.exception, file=sys.stderr) assert result.exit_code == 0 assert ( - "elements-cli: elements-cli -regtest -rpcport=18555 -rpcuser=elements -rpcpassword=secret getblockchaininfo" + "elements-cli: elements-cli -regtest -rpcport=18555 -rpcuser=liquid -rpcpassword=secret getblockchaininfo" in result.output ) # This might take a lot of time because we're waiting on the bitcoind to terminate diff --git a/tests/test_managers_node.py b/tests/test_managers_node.py new file mode 100644 index 000000000..f4fa044f9 --- /dev/null +++ b/tests/test_managers_node.py @@ -0,0 +1,45 @@ +from enum import auto +import tempfile +from cryptoadvance.specter.managers.node_manager import NodeManager +from cryptoadvance.specter.process_controller.bitcoind_controller import ( + BitcoindPlainController, +) +from cryptoadvance.specter.process_controller.elementsd_controller import ( + ElementsPlainController, +) + + +def test_NodeManager( + bitcoin_regtest: BitcoindPlainController, elements_elreg: ElementsPlainController +): + with tempfile.TemporaryDirectory("_some_datafolder_tmp") as data_folder: + print(f"data_folder={data_folder}") + nm = NodeManager(data_folder=data_folder) + nm.add_node( + "bitcoin_regtest", + False, + "", + bitcoin_regtest.rpcconn.rpcuser, + bitcoin_regtest.rpcconn.rpcpassword, + bitcoin_regtest.rpcconn.rpcport, + bitcoin_regtest.rpcconn._ipaddress, + "http", + external_node=True, + ) + assert nm.nodes_names == ["Bitcoin Core", "bitcoin_regtest"] + nm.switch_node("bitcoin_regtest") + assert nm.active_node.get_rpc().getblockchaininfo()["chain"] == "regtest" + nm.add_node( + "elements_elreg", + False, + "", + elements_elreg.rpcconn.rpcuser, + elements_elreg.rpcconn.rpcpassword, + elements_elreg.rpcconn.rpcport, + elements_elreg.rpcconn._ipaddress, + "http", + external_node=True, + ) + assert nm.nodes_names == ["Bitcoin Core", "bitcoin_regtest", "elements_elreg"] + nm.switch_node("elements_elreg") + assert nm.active_node.get_rpc().getblockchaininfo()["chain"] == "elreg" diff --git a/tests/test_node.py b/tests/test_node.py new file mode 100644 index 000000000..70445e6a1 --- /dev/null +++ b/tests/test_node.py @@ -0,0 +1,125 @@ +import os +import tempfile + +from cryptoadvance.specter.node import Node +from mock import MagicMock, call, patch + + +def test_Node_btc(bitcoin_regtest): + with tempfile.TemporaryDirectory("_some_datafolder_tmp") as data_folder: + node = Node.from_json( + { + "autodetect": False, + "datadir": "", + "user": bitcoin_regtest.rpcconn.rpcuser, + "password": bitcoin_regtest.rpcconn.rpcpassword, + "port": bitcoin_regtest.rpcconn.rpcport, + "host": bitcoin_regtest.rpcconn.ipaddress, + "protocol": "http", + }, + manager=MagicMock(), + default_fullpath=os.path.join(data_folder, "a_testfile.json"), + ) + result = node.test_rpc() + + assert result["tests"]["connectable"] == True + assert result["tests"]["recent_version"] == True + assert result["tests"]["credentials"] == True + assert result["tests"]["wallets"] == True + + node_json = node.json + del node_json["fullpath"] # This is very different because of the tempfile + assert node_json == { + "name": "", + "alias": "", + "autodetect": False, + "datadir": "", + "user": "bitcoin", + "password": "secret", + "port": 18543, + "host": "localhost", + "protocol": "http", + "external_node": True, # 'fullpath': '' + } + + rpc = node.get_rpc() + assert rpc.getblockchaininfo()["chain"] == "regtest" + node.rename("some_new_name") + assert node.json["name"] == "some_new_name" + node.check_info() + assert node.is_configured == True + assert node.is_running == True + assert node.is_testnet == True + print(f"info = {node.info}") + # something like: + # {'chain': 'regtest', 'blocks': 100, 'headers': 100, 'bestblockhash': '5277c27b3b5e8aad8e079a928e4675931ea1638a28c7a33bae4ef26425402259', 'difficulty': 4.656542373906925e-10, 'mediantime': 1622635607, 'verificationprogress': 1, 'initialblockdownload': False, 'chainwork': '00000000000000000000000000000000000000000000000000000000000000ca', 'size_on_disk': 30477, 'pruned': False, 'softforks': {'bip34': {'type': 'buried', 'active': False, 'height': 500}, 'bip66': {'type': 'buried', 'active': False, 'height': 1251}, 'bip65': {'type': 'buried', 'active': False, 'height': 1351}, 'csv': {'type': 'buried', 'active': False, 'height': 432}, 'segwit': {'type': 'buried', 'active': True, 'height': 0}, 'testdummy': {'type': 'bip9', 'bip9': {'status': 'defined', 'start_time': 0, 'timeout': 9223372036854775807, 'since': 0}, 'active': False}}, 'warnings': '', 'mempool_info': {'loaded': True, 'size': 0, 'bytes': 0, 'usage': 0, 'maxmempool': 300000000, 'mempoolminfee': 1e-05, 'minrelaytxfee': 1e-05}, 'uptime': 480, 'blockfilterindex': False, 'utxorescan': None} + assert node.info["chain"] == "regtest" + + print(f"network_info = {node.network_info}") + # something like: + # {'version': 200100, 'subversion': '/Satoshi:0.20.1/', 'protocolversion': 70015, 'localservices': '0000000000000409', 'localservicesnames': ['NETWORK', 'WITNESS', 'NETWORK_LIMITED'], 'localrelay': True, 'timeoffset': 0, 'networkactive': True, 'connections': 0, 'networks': [{'name': 'ipv4', 'limited': False, 'reachable': True, 'proxy': '', 'proxy_randomize_credentials': False}, {'name': 'ipv6', 'limited': False, 'reachable': True, 'proxy': '', 'proxy_randomize_credentials': False}, {'name': 'onion', 'limited': True, 'reachable': False, 'proxy': '', 'proxy_randomize_credentials': False}], 'relayfee': 1e-05, 'incrementalfee': 1e-05, 'localaddresses': [{'address': '2a02:810d:d00:7700:233e:a7e:ded8:f2da', 'port': 18542, 'score': 1}, {'address': '2a02:810d:d00:7700:29ec:5c5b:196b:78b2', 'port': 18542, 'score': 1}], 'warnings': ''} + assert node.network_info["connections"] == 0 + assert node.network_info["warnings"] == "" + + +def test_Node_elm(elements_elreg): + with tempfile.TemporaryDirectory("_some_datafolder_tmp") as data_folder: + node = Node.from_json( + { + "autodetect": False, + "datadir": "", + "user": elements_elreg.rpcconn.rpcuser, + "password": elements_elreg.rpcconn.rpcpassword, + "port": elements_elreg.rpcconn.rpcport, + "host": elements_elreg.rpcconn.ipaddress, + "protocol": "http", + }, + manager=MagicMock(), + default_fullpath=os.path.join(data_folder, "a_testfile.json"), + ) + result = node.test_rpc() + + assert result["tests"]["connectable"] == True + assert result["tests"]["recent_version"] == True + assert result["tests"]["credentials"] == True + assert result["tests"]["wallets"] == True + + node_json = node.json + del node_json["fullpath"] # This is very different because of the tempfile + print(f"node.json = {node.json}") + assert node_json == { + "name": "", + "alias": "", + "autodetect": False, + "datadir": "", + "user": "liquid", + "password": "secret", + "port": 18643, + "host": "localhost", + "protocol": "http", + "external_node": True, # 'fullpath': '' + } + + rpc = node.get_rpc() + assert rpc.getblockchaininfo()["chain"] == "elreg" + node.rename("some_new_name") + assert node.json["name"] == "some_new_name" + node.check_info() + assert node.is_configured == True + assert node.is_running == True + assert node.is_testnet == True + print(f"info = {node.info}") + # something like: + # {'chain': 'regtest', 'blocks': 100, 'headers': 100, 'bestblockhash': '5277c27b3b5e8aad8e079a928e4675931ea1638a28c7a33bae4ef26425402259', 'difficulty': 4.656542373906925e-10, 'mediantime': 1622635607, 'verificationprogress': 1, 'initialblockdownload': False, 'chainwork': '00000000000000000000000000000000000000000000000000000000000000ca', 'size_on_disk': 30477, 'pruned': False, 'softforks': {'bip34': {'type': 'buried', 'active': False, 'height': 500}, 'bip66': {'type': 'buried', 'active': False, 'height': 1251}, 'bip65': {'type': 'buried', 'active': False, 'height': 1351}, 'csv': {'type': 'buried', 'active': False, 'height': 432}, 'segwit': {'type': 'buried', 'active': True, 'height': 0}, 'testdummy': {'type': 'bip9', 'bip9': {'status': 'defined', 'start_time': 0, 'timeout': 9223372036854775807, 'since': 0}, 'active': False}}, 'warnings': '', 'mempool_info': {'loaded': True, 'size': 0, 'bytes': 0, 'usage': 0, 'maxmempool': 300000000, 'mempoolminfee': 1e-05, 'minrelaytxfee': 1e-05}, 'uptime': 480, 'blockfilterindex': False, 'utxorescan': None} + assert node.info["chain"] == "elreg" + + print(f"network_info = {node.network_info}") + # something like: + # {'version': 200100, 'subversion': '/Satoshi:0.20.1/', 'protocolversion': 70015, 'localservices': '0000000000000409', 'localservicesnames': ['NETWORK', 'WITNESS', 'NETWORK_LIMITED'], 'localrelay': True, 'timeoffset': 0, 'networkactive': True, 'connections': 0, 'networks': [{'name': 'ipv4', 'limited': False, 'reachable': True, 'proxy': '', 'proxy_randomize_credentials': False}, {'name': 'ipv6', 'limited': False, 'reachable': True, 'proxy': '', 'proxy_randomize_credentials': False}, {'name': 'onion', 'limited': True, 'reachable': False, 'proxy': '', 'proxy_randomize_credentials': False}], 'relayfee': 1e-05, 'incrementalfee': 1e-05, 'localaddresses': [{'address': '2a02:810d:d00:7700:233e:a7e:ded8:f2da', 'port': 18542, 'score': 1}, {'address': '2a02:810d:d00:7700:29ec:5c5b:196b:78b2', 'port': 18542, 'score': 1}], 'warnings': ''} + assert node.network_info["connections"] == 0 + # currently: + assert ( + node.network_info["warnings"] + == "This is a pre-release test build - use at your own risk - do not use for mining or merchant applications" + ) + # assert node.network_info["warnings"] == "" diff --git a/utils/test-cypress.sh b/utils/test-cypress.sh index dda30c4a7..39acfab85 100755 --- a/utils/test-cypress.sh +++ b/utils/test-cypress.sh @@ -6,6 +6,8 @@ PORT=25444 # This needs to be the same than in config.py CypressTestConfig BTCD_REGTEST_DATA_DIR # As we don't want to speculate here, we're injecting it via Env-var export BTCD_REGTEST_DATA_DIR=/tmp/specter_cypress_btc_regtest_plain_datadir +# same with this +export ELMD_REGTEST_DATA_DIR=/tmp/specter_cypress_elm_regtest_plain_datadir # same with SPECTER_DATA_FOLDER export SPECTER_DATA_FOLDER=~/.specter-cypress # We'll might change that on the "dev-function" @@ -96,47 +98,76 @@ function send_signal() { fi } -function start_bitcoind { + +function start_node { + addopts="" while [[ $# -gt 0 ]]; do arg="$1" case $arg in + --bitcoin) + node_impl=bitcoind + shift + ;; + --elements) + node_impl=elementsd + shift + ;; --reset) - echo "--> Purging $BTCD_REGTEST_DATA_DIR" - rm -rf $BTCD_REGTEST_DATA_DIR + RESET=true shift ;; --cleanuphard) - addopts="--cleanuphard" + CLEANUPHARD=true shift ;; *) - echo "unrecognized argument for start_bitcoind: $1 " + echo "unrecognized argument for start_node: $1 " exit 1 shift ;; esac done - if [ "$1" = "--reset" ]; then + if [ "$RESET" = "true" ]; then + if [ "$node_impl" = "bitcoind" ]; then echo "--> Purging $BTCD_REGTEST_DATA_DIR" rm -rf $BTCD_REGTEST_DATA_DIR + else + echo "--> Purging $ELMD_REGTEST_DATA_DIR" + rm -rf $ELMD_REGTEST_DATA_DIR + fi fi - if [ "$1" = "--cleanuphard" ]; then + if [ "$CLEANUPHARD" = "true" ]; then addopts="--cleanuphard" fi if [ "$DOCKER" != "true" ]; then + if [ "$node_impl" != "elementsd" ]; then # no docker for elementsd yet addopts="$addopts --nodocker" + fi fi - echo "--> Starting bitcoind with $addopts..." - python3 -m cryptoadvance.specter $DEBUG bitcoind $addopts --create-conn-json --config $SPECTER_CONFIG & - bitcoind_pid=$! + echo "--> Starting $node_impl with $addopts ..." + python3 -m cryptoadvance.specter $DEBUG $node_impl $addopts --create-conn-json --config $SPECTER_CONFIG & + if [ "$node_impl" = "bitcoind" ]; then + bitcoind_pid=$! + else + elementsd_pid=$! + fi + while ! [ -f ./btcd-conn.json ] ; do sleep 0.5 done } +function start_bitcoind { + start_node --bitcoin $* +} + +function start_elementsd { + start_node --elements $* +} + function stop_bitcoind { if [ ! -z ${bitcoind_pid+x} ]; then echo "--> Killing/Terminating bitcoindwrapper with PID $bitcoind_pid ..." @@ -146,6 +177,15 @@ function stop_bitcoind { fi } +function stop_elementsd { + if [ ! -z ${elementsd_pid+x} ]; then + echo "--> Killing/Terminating elementsdwrapper with PID $elementsd_pid ..." + send_signal SIGTERM $elementsd_pid + wait $elementsd_pid + unset elementsd_pid + fi +} + function start_specter { if [ "$1" = "--reset" ]; then echo "--> Purging $SPECTER_DATA_FOLDER" @@ -154,6 +194,8 @@ function start_specter { echo "--> Starting specter ..." python3 -m cryptoadvance.specter $DEBUG server --config $SPECTER_CONFIG --debug > /dev/null & specter_pid=$! + # Simulate slower machines with uncommenting this (-l 10 means using 10% cpu): + #cpulimit -p $specter_pid -l 10 -b $(npm bin)/wait-on http://localhost:${PORT} } @@ -168,8 +210,9 @@ function stop_specter { function cleanup() { - stop_specter || : - stop_bitcoind || : + stop_specter || : + stop_bitcoind || : + stop_elementsd || : } @@ -200,13 +243,20 @@ function restore_snapshot { exit 1 fi done + rm -rf ${BTCD_REGTEST_DATA_DIR} mkdir ${BTCD_REGTEST_DATA_DIR} - rm -rf $SPECTER_DATA_FOLDER - mkdir $SPECTER_DATA_FOLDER echo "--> Unpacking ./cypress/fixtures/${spec_file}_btcdir.tar.gz ... " tar -xzf ./cypress/fixtures/${spec_file}_btcdir.tar.gz -C ${BTCD_REGTEST_DATA_DIR} --strip-components=1 + + rm -rf ${ELMD_REGTEST_DATA_DIR} + mkdir ${ELMD_REGTEST_DATA_DIR} + echo "--> Unpacking ./cypress/fixtures/${spec_file}_elmdir.tar.gz ... " + tar -xzf ./cypress/fixtures/${spec_file}_elmdir.tar.gz -C ${ELMD_REGTEST_DATA_DIR} --strip-components=1 + echo "--> Unpacking ./cypress/fixtures/${spec_file}_specterdir.tar.gz ... " + rm -rf $SPECTER_DATA_FOLDER + mkdir $SPECTER_DATA_FOLDER tar -xzf ./cypress/fixtures/${spec_file}_specterdir.tar.gz -C $SPECTER_DATA_FOLDER --strip-components=1 } @@ -223,9 +273,11 @@ function sub_dev { if [ -n "${spec_file}" ]; then restore_snapshot ${spec_file} start_bitcoind --cleanuphard + start_elementsd --cleanuphard start_specter else start_bitcoind --reset + stop_elementsd --reset start_specter --reset fi open http://localhost:${PORT} @@ -237,9 +289,11 @@ function sub_open { if [ -n "${spec_file}" ]; then restore_snapshot ${spec_file} start_bitcoind --cleanuphard --reset + start_elementsd --cleanuphard --reset start_specter else start_bitcoind --reset + start_elementsd --reset start_specter --reset fi start_specter @@ -251,11 +305,15 @@ function sub_run { if [ -f ./cypress/integration/${spec_file} ]; then restore_snapshot ${spec_file} start_bitcoind --cleanuphard --reset + start_elementsd --cleanuphard --reset start_specter - # Run $spec_file and all of the others coming later which come later! - $(npm bin)/cypress run --spec $(./utils/calc_cypress_test_spec.py --run $spec_file) + # Run $spec_file and all of the others coming later! + #$(npm bin)/cypress run --spec $(./utils/calc_cypress_test_spec.py --run $spec_file) + # Run $spec_file and only that spec-file! + $(npm bin)/cypress run --spec ./cypress/integration/${spec_file} else start_bitcoind --reset + start_elementsd --reset start_specter --reset $(npm bin)/cypress run fi @@ -270,15 +328,21 @@ function sub_snapshot { exit 2 fi start_bitcoind --reset + start_elementsd --reset start_specter --reset $(npm bin)/cypress run --spec $(./utils/calc_cypress_test_spec.py $spec_file) echo "--> stopping specter" stop_specter echo "--> stopping bitcoind gracefully ... won't take long ..." stop_bitcoind - echo "--> Creating snapshot $BTCD_REGTEST_DATA_DIR)" + echo "--> stopping elementsd gracefully ... won't take long ..." + stop_elementsd + echo "--> Creating snapshot $BTCD_REGTEST_DATA_DIR)" rm ./cypress/fixtures/${spec_file}_btcdir.tar.gz 2> /dev/null 1>&2 || : tar -czf ./cypress/fixtures/${spec_file}_btcdir.tar.gz -C /tmp $(basename $BTCD_REGTEST_DATA_DIR) + echo "--> Creating snapshot $ELMD_REGTEST_DATA_DIR)" + rm ./cypress/fixtures/${spec_file}_elmdir.tar.gz 2> /dev/null 1>&2 || : + tar -czf ./cypress/fixtures/${spec_file}_elmdir.tar.gz -C /tmp $(basename $ELMD_REGTEST_DATA_DIR) echo "--> Creating snapshot of $SPECTER_DATA_FOLDER" rm ./cypress/fixtures/${spec_file}_specterdir.tar.gz 2> /dev/null 1>&2 || : tar -czf ./cypress/fixtures/${spec_file}_specterdir.tar.gz -C ~ $(basename $SPECTER_DATA_FOLDER)